目录
1.前言
承继上篇:Unity C#常用排序算法(冒泡、选择、插入)的泛型写法(一)
今天讲一下希尔排序和堆排序
2.希尔排序
希尔排序是D.L.Shell于1959年提出的一种排序算法,它是第一批突破O(n2)时间复杂度的排序算法,希尔排序的时间复杂度是O(n3/2)(相当于O(n1.5)),好的时候甚至可以达到O(nlogn)
排序思路:希尔排序是对直接插入排序的优化,在一个序列基本有序(小的普遍在前,大的普遍在后)的情况下,对它进行插入排序将非常高效。所以希尔排序就是先将序列通过几轮跳跃性的粗排将序列变得基本有序,最后在进行一遍插入排序
写法:
/// <summary>
/// 希尔排序(升序)时间复杂度O(n3/2) n的1.5次方(增量num为3的估算),第一批突破n2的排序算法(非稳定排序)
/// </summary>
/// <param name="num">算法增量,默认3,不能小于2</param>
public static List<T> SortShell<T>(List<T> list, int num = 3) where T : IComparerSort<T>
{
num = num < 2 ? 2 : num;
T temp;
int n = list.Count;
int increase = n;
int k = 0;
do
{
if (increase > num)
increase /= num;
else
increase = 1; // 最后一轮的增量必须是1
// 每轮进行直接插入排序
for(int i = increase ; i < n ; i++)
{
if (list[i].Compare(list[i], list[i - increase]) < 0)
{
temp = list[i];
for (k = i - increase; k >= 0 && list[k].Compare(list[k], temp) > 0; k -= increase)
{
list[k + increase] = list[k];
}
list[k + increase] = temp;
}
}
} while (increase > 1);
return list;
}
注:用来做粗排的变量num因人而异,个人通过对2~9的变量做测试比较出3综合情况更好,具体使用时可以自行选择;粗排后的最后一轮一定要进行一次直接插入排序(即 increase = 1; 的这段代码)做收尾
3.堆排序
在希尔排序之后,出现了一批时间复杂度突破O(n2)的排序算法,堆排就是其中之一
堆排序的思路(升序):利用二叉堆的大顶堆特性,每次生成一个大顶堆后,将堆顶元素与最后一个元素互换,并将最后一个元素移出下一次的比较;接着再循环以上过程直到排序完成
写法:
/// <summary>
/// 堆排序(升序)时间复杂度O(nlogn)(非稳定排序)
/// </summary>
public static List<T> SortHeap<T>(List<T> list) where T : IComparerSort<T>
{
// 因为堆是完全二叉树,所以从最后一个非叶子节点开始构建一个大顶堆
int n = list.Count;
for(int i = n / 2 - 1 ; i >= 0 ; i--)
{
list = AdjustHeap(list, i, n);
}
// 依次将堆顶与堆的最后一个元素交换,并将除堆最后一个元素之外的元素重新构建一个大顶堆,来实现堆排
for (int i = n - 1 ; i > 0 ; i--)
{
Swap(list, 0, i);
list = AdjustHeap(list, 0, i);
}
return list;
}
/// <summary>
/// 构建一个大顶堆(非叶子节点索引逆序调用)
/// </summary>
/// <param name="pos">堆顶索引</param>
/// <param name="length">堆的长度</param>
private static List<T> AdjustHeap<T>(List<T> list, int pos, int length) where T : IComparerSort<T>
{
int n = list.Count;
if(n < length)
{
Debug.LogError($"Error : [Sort.AdjustHeap()] list.Count < length !");
return list;
}
for(int i = pos * 2 + 1 ; i < length ; i = i * 2 + 1)
{
if (i + 1 < length && list[i].Compare(list[i], list[i + 1]) < 0)
i++; // 右孩子比左孩子大,标记索引为右孩子
if (list[pos].Compare(list[pos], list[i]) < 0)
{
Swap(list, pos, i);
pos = i;
}
else
break;
}
return list;
}
private static void Swap<T>(List<T> list, int a, int b)
{
T temp = list[a];
list[a] = list[b];
list[b] = temp;
}
堆排的时间复杂度是O(nlogn),它的最坏、最好、平均时间复杂度都是O(nlogn),它比简单排序(冒泡、选择、插入)有明细的效率提升,但是它并非稳定排序这个要注意