对现实中的排序问题,算法有七把利剑可以助你马道成功。
首先排序分为四种:
交换排序: 包括冒泡排序,快速排序。
选择排序: 包括直接选择排序,堆排序。
插入排序: 包括直接插入排序,希尔排序。
合并排序: 合并排序。
一.插入排序
1.1.直接插入排序
URL:http://www.cnblogs.com/tangge/p/5338734.html#ChaRu
总结:直接插入排序最好情况时间复杂度为O(n),最坏情况下(逆序表)时间复杂度为O(n2),因此它只适合于数据量较少的情况使用。
1.2.希尔排序
本算法的关键内容是对远距离而非相邻的数据项进行比较。当算法循环遍历数
据集合的时候,每个数据项间的距离会缩短,直到算法对相邻数据项进行比较时才终止。
基本思想:设定一个元素增量gap,将参加排序的序列按这个间隔数gap从第1个元素开始依次分成若干子序列,对子序列进行排序,然后缩小增量gap,重新将整个序列按照新的间隔数gap进行划分,再分别对每个子序列进行排序,如此将“缩小增量gap——划分序列——将每个子序列进行排序“的操作进行下去,知道间隔数增量gap=1为止;
gap间隔数的选择:如何选择最合适的间隔数序列才能达到最优的排序效果是至今尚未解决的数学难题,我们在一般排序中gap的初始值可设定为N/2(N为排序元素的个数,这里需要注意采用四舍五入的原则),以后每一趟排序,gap值减半,直到gap=1为止;
/// <summary>
/// 希尔算法
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
public static int[] ShellSort(int[] m_SourceArray)
{
int tmp;
int length = m_SourceArray.Length;
//gap如果不能被整除需要+1
int gap = length / 2 + (length % 2 == 0 ? 0 : 1);
while (gap >= 1)
{
for (int i = 0; i + gap < length; i++)
{
if (m_SourceArray[i] > m_SourceArray[i + gap])
{
tmp = m_SourceArray[i];
m_SourceArray[i] = m_SourceArray[i + gap];
m_SourceArray[i + gap] = tmp;
}
}
if (gap == 1) break;
//gap如果不能被整除需要+1
gap = gap / 2 + (gap % 2 == 0 ? 0 : 1);
}
return m_SourceArray;
}
总结:Shell排序适用于待排序记录数量较大的情况,在此情况下,Shell排序一般要比直接插入排序要快。1971年,斯坦福大学的两位教授在大量实验的基础上推导出Shell排序的时间复杂度约为O(n1.3),使得我们终于突破了慢速排序的时代(超越了时间复杂度为O(n2))。
二.交换排序
2.1.冒泡排序
http://www.cnblogs.com/tangge/p/5338734.html#MaoPao
总结:冒泡排序在运行时间方面,待排序的记录越接近有序,算法的执行效率就越高,反之,执行效率则越低,它的平均时间复杂度为O(n2)。
2.2.快速排序
快速排序是由C.A.R Hoare提出并命名的一种排序方法,在目前各种排序方法中,这种方法对元素进行比较的次数较少,因而速度也比较快,被认为是目前最好的排序方法之一。在.NET中的多个集合类所提供的Sort()方法中都使用了快速排序对集合中的元素进行排序。
快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
public class QuickSortClass
{ public void QuickSort(List<int> list, int left, int right)
{ //左下标一定小于右下标,否则就超越了
if (left < right)
{
//对数组进行分割,取出下次分割的基准标号
int i = Division(list, left, right); //对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
QuickSort(list, left, i - 1); //对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
QuickSort(list, i + 1, right);
}
} ///<summary>
/// 分割函数
///</summary>
///<param name="list">待排序的数组</param>
///<param name="left">数组的左下标</param>
///<param name="right"></param>
///<returns></returns>
public int Division(List<int> list, int left, int right)
{ //首先挑选一个基准元素
int baseNum = list[left];
while (left < right)
{
//从数组的右端开始向前找,一直找到比base小的数字为止(包括base同等数)
while (left < right && list[right] >= baseNum)
{
right = right - 1;
}
//最终找到了比baseNum小的元素,要做的事情就是此元素放到base的位置
list[left] = list[right]; //从数组的左端开始向后找,一直找到比base大的数字为止(包括base同等数)
while (left < right && list[left] <= baseNum)
{
left = left + 1;
}
//最终找到了比baseNum大的元素,要做的事情就是将此元素放到最后的位置
list[right] = list[left];
}
//最后就是把baseNum放到该left的位置
list[left] = baseNum; //最终,我们发现left位置的左侧数值部分比left小,left位置右侧数值比left大
//至此,我们完成了第一篇排序
return left;
}
}
调用
List<int> list = new List<int>();
int[] array = new int[] { 20, 40, 50, 10, 60 };
list.AddRange(array);
new QuickSortClass().QuickSort(list, 0, list.Count - 1);
Console.WriteLine();
Console.Write(string.Join(",", list.ToList()));
总结:快速排序的平均时间复杂度为O(nlog2n),在平均时间下,快速排序时目前被认为最好的内部排序方法。但是,如果待排序记录的初始状态有序,则快速排序则会退化为冒泡排序,其时间复杂度为O(n2)。换句话说,待排序记录越无序,基准两侧记录数量越接近,排序速度越快;相反,待排序记录越有序,则排序速度越慢。