目录
1.前言
承继初篇:Unity C#常用排序算法(冒泡、选择、插入)的泛型写法(一)
今天讲一下:归并排序和快速排序
2.归并排序
归并排序的思路:先将序列分成 n / 2份,进行两两比较排序,然后再对两个相邻的有序队列进行两两排序,以此类推直到排序完成;可以看成是一个完全二叉树,先对叶子节点进行两两排序,然后向上对子节点序列做排序,以此类推直到根节点结束;
从思路上看这是一个典型的递归实现过程,所以递归是它的一种解决方案
1.递归写法:
/// <summary>
/// 归并排序递归写法(升序)时间复杂度O(nlogn)(稳定排序),空间复杂度O(n + log2n)
/// </summary>
public static List<T> SortMargeRecursion<T>(List<T> list) where T : IComparerSort<T>
{
List<T> tempList = new List<T>(list);
SortMargeRecursion(list, tempList, 0, list.Count - 1);
return list;
}
private static void SortMargeRecursion<T>(List<T> list, List<T> tempList, int start, int end) where T : IComparerSort<T>
{
if (start >= end)
return;
int len = end - start;
int mid = start + len / 2;
SortMargeRecursion(list, tempList, start, mid);
SortMargeRecursion(list, tempList, mid + 1, end);
int i = start;
int j = mid + 1;
int k = start;
while(i <= mid && j <= end)
{
if (list[i].Compare(list[i], list[j]) < 0)
tempList[k++] = list[i++];
else
tempList[k++] = list[j++];
}
while(i <= mid)
{
tempList[k++] = list[i++];
}
while (j <= end)
{
tempList[k++] = list[j++];
}
for(i = start ; i <= end ; i++)
{
list[i] = tempList[i];
}
}
对于递归写法来说比较好理解,但是毕竟还有递归调用的时间和空间占用,所以下面还有一种非递归调用的写法
2.非递归写法:
/// <summary>
/// 归并排序(升序)时间复杂度O(nlogn)(稳定排序),空间复杂度O(n)
/// </summary>
public static List<T> SortMarge<T>(List<T> list) where T : IComparerSort<T>
{
int n = list.Count;
List<T> tempList = new List<T>(list);
List<T> temp;
int low = 0;
int mid = 0;
int high = 0;
int i = 0;
int j = 0;
int k = 0;
for (int increase = 1 ; increase < n ; increase += increase)
{
for(int start = 0 ; start < n ; start += increase + increase)
{
low = Min(start, n - 1);
mid = Min(start + increase, n - 1);
high = Min(start + increase + increase, n);
i = low;
j = mid;
k = low;
while(i < mid && j < high)
{
if (list[i].Compare(list[i], list[j]) < 0)
tempList[k++] = list[i++];
else
tempList[k++] = list[j++];
}
while(i < mid)
{
tempList[k++] = list[i++];
}
while(j < high)
{
tempList[k++] = list[j++];
}
}
temp = list;
list = tempList;
tempList = temp;
}
tempList.Clear();
tempList = null;
temp = null;
return list;
}
归并排序的最坏、最好、平均时间复杂度都是O(nlogn),空间复杂度是O(n+logn),归并排序是稳定排序,但是会有比较多的空间占用,在使用时要有取舍
3.快速排序
快速排序思路:通过一趟遍历找出一个枢纽位,使得枢纽位的左边都比它小,右边都比它大(左右这两个部分中并不需要有序),并返回这个枢纽位;然后对左右这两个部分再继续循环以上过程;
通过分析这显然也是一个递归的过程,这里枢纽位的选取有多种方式:取第一位、取最后一位、随机一位、找出前中后三位中处于中间数值的枢纽位
这里我们只介绍取第一个元素做枢纽位的写法:
/// <summary>
/// 快速排序(升序)时间复杂度O(nlogn)(非稳定排序),枢纽默认是第一个元素low
/// </summary>
public static List<T> SortQuick<T>(List<T> list) where T : IComparerSort<T>
{
SortQuick(list, 0, list.Count - 1);
return list;
}
private static void SortQuick<T>(List<T> list, int start, int end) where T : IComparerSort<T>
{
if (start < end)
{
int low = start;
int high = end;
int pivot = low;
T value = list[pivot];
// 将枢纽pivot左右做简单排序,左的都比它小,右的都比它大
while (low < high)
{
while (pivot < high && list[high].Compare(list[high], value) >= 0)
high--;
if(pivot < high)
{
list[pivot] = list[high];
pivot = high;
}
while (pivot > low && list[low].Compare(list[low], value) <= 0)
low++;
if(pivot > low)
{
list[pivot] = list[low];
pivot = low;
}
}
list[pivot] = value;
SortQuick(list, start, pivot - 1);
SortQuick(list, pivot + 1, end);
}
}
注意:这里在将枢纽位左右做简单排序时,一定不要忘记 == 等于比较(如上 >= 0 和 <= 0),否则在有相同值元素的情况下会死循环
快排的时间复杂度是O(nlogn),它是非稳定排序,对于不强调稳定排序的情况下它一般来说都是最好的选择,否则用归并排序
4.总结
用三篇来大致描述了常用的7种排序泛型写法,这里主要是对思路做介绍,实现部分并不详尽也是希望大家可以在了解思路的情况下自行尝试去实现,理解更深刻。
7种排序的实现各自不同,但思路又有相同之处,例如:
1.希尔排序是基于直接插入排序的优化;
2.堆排可以看做选择排序的优化(大顶堆标记堆顶元素后,与末尾元素互换和简单选择排序的标记互换思路一样);
3.快排又可以看做是对冒泡的思路优化(在找枢纽位的同时,将大的元素向后冒泡,小的元素向前冒泡);
这里用到了如:二叉堆(大顶堆/小顶堆)、递归、分而治之、先粗(排序)后细(排序)等思路;这些思路不仅仅局限于排序算法,在功能开发中很多时候都可以用类似的思路来解决实际问题