插入排序之希尔排序
前一篇文章谈到直接插入排序在数据有序度很高的情况下时间复杂度趋近于T(n)
希尔排序正是利用这一特点使数据有序度逐步提高,最后达到完全有序。
如下假设(建议将数据想象成折线图,暂时不考虑代码如何写,这只是思想)
10000个数据直接插入排序要近2500万次比较和移动。n^2/4
2000个数据直接插入排序要近100万次比较和移动。n^2/4
5个2000数据集直接插入排序只要近500万次比较和移动。
那么我们不妨将10000的数据集分成五份先进行排序
- 将数据索引对5取余,余数相同的分为一组,每组两千个。
- 然后对每组的数据分别进行排序。
- 可以想象这五组数据排序完成后其折线图相似度是非常高的。
- 然后将这五组数据放回原来索引的位置(这10000个数据的折线图是较为平滑的上升的(或者是降低))。
我们用了500万次比较得到了这样的基本有序数据集,之后即使我们直接使用直接插入排序。(当然我们可以再将2000数据分为5组进行排序更快一些)
这时候直接插入排序的时间复杂度n的指数是趋近于一的,这就意味着我们甚至只需要几十万次比较便能得到有序序列。
我们发现使用分组的方式可以很好的降低插入排序的时间复杂度
按照这样的思想我们只需要去寻找一个最好的分组策略,然后进行直接插入排序就行了(这也就是希尔排序)
分组有两方面需要考虑的:(实际上当前数学界还无法给出最优分组策略。)
- 分几次
- 每次分机组
现在我们回到希尔排序的代码实现过程中来:
如图是希尔排序的分组方式:
一共分了3次
第一次数据间隔为5的归为一组
第二次数据间隔为3的归为一组
第三次数据间隔为1的归为一组
上面我们谈过希尔排序就是重复调用了直接插入排序,所以其逻辑和直接插入排序差不多,仅仅是原先找到下一个元素索引+1,现在是索引+dt[i];
public static void ShellInsertSort(ref int[] L, int count, bool isUp,int[] dt,int dtCount)
{
int i;
for(i=0;i<dtCount;i++)//n次分组
{
ShellInsert(dt[i], ref L, count, isUp);
}
}
public static void ShellInsert(int dk,ref int[] L,int count,bool isUp)
{
int i,j;
int data;
for (i = dk; i < count; i++)//这里并没有进行dk次直接插入排序,而是合并到了一次插入排序
{
//该代码段逻辑和直接插入排序一模一样
data = L[i];
for (j = i; j >= dk&&((isUp && L[j - dk] > data) || (!isUp && L[j - dk] < data)); j -= dk)
L[j] = L[j - dk];
L[j] = data;
}
}
分组方式的建议取法(2^k-1…… 2^3-1,2^2-1,2^1-1)次是时间复杂度为O(n^(3/2))
算法特点:
- 不稳定
- 只能用于顺序结构,因为其需要隔dk[i]个数据才能找到下一位元素,链表显然太慢了。
- 其速度比直接插入排序快,数据量大的时候比较合适。