常见排序算法-希尔排序

Posted 咸鱼成长小站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见排序算法-希尔排序相关的知识,希望对你有一定的参考价值。

在上一节中,我们谈到了插入排序中的直接插入排序。

今天我们来讲讲希尔排序

希尔排序也是插入排序的一种,属于直接插入排序的优化版。

不同于直接插入排序,希尔排序多了一个增量序列,通过增量序列将待排序的数组分为了不同的组,然后通过减小增量,将分组数量减少,直至只剩一组进行插入排序。
增量序列的选择会影响算法的时间复杂度,也就是希尔排序的时间复杂度与增量序列有关。

下面列举两种种增量序列:

希尔增量:N/2,N/4,N/8…,1
Hibbard增量序列:1,3,7,…,2^k-1

这样干说起来可能有点不好理解,我们来举个例子。

对于待排数列[8,7,6,9,5,4,2,1,3],其元素个数为9个。我们选择增量序列为Hibbard序列,即(1,3,7…,2^k-1,k为log2(n+1)向下取整)。log2(10)向下取整为3,2^3-1 = 7。所以根据序列我们可以知道第一趟排序增量delta为7。

由于增量为7,所以我们分为了[8,1]和[7,3]两个分组,分别对这两个分组进行插入排序,排序结束后数组为

[1,3,6,9,5,4,2,8,7]

第二趟排序增量减小,变为了delta / 2即 3。

我们的数组根据增量为3分为了[1,9,2]、[3,5,8]和[6,4,7]三个分组,同样我们分别对它们进行插入排序。最后得到的排序后的数组为

[1,3,4,2,5,6,9,8,7]

第三趟排序增量继续减小,delta = delta/2 = 1。
这时分组就为一组,直接对其进行插入排序就行了。

可能到这里有些朋友就会问了,最后都需要对整个数组进行插入排序,那么我们之前的那些操作有什么用?

我们来分析一下我们使用Hibbard增量的希尔排序和直接插入排序所需要做的交换运算次数。

对于直接插入排序:

[8,7,6,9,5,4,2,1,3]

  • 第一趟排序将7插入到8前面,交换了1次。

  • 第二趟将6插入到7前面,交换了2次。

  • 第三趟将9插入到最后,没有进行交换。

  • 第四趟将5插入到6前面,交换了4次。

  • 第五趟将4插入到5前面,交换了5次。

  • 第六趟将2插入到4前面,交换了6次。

  • 第七趟将1插入到2前面,交换了7次。

  • 最后一趟将3插入到4前面,交换了6次。

累计交换了1+2+4+5+6+7+6 = 31次。

那么对于我们的希尔排序呢:

[8,7,6,9,5,4,2,1,3]

  • 第一趟对[8,1]和[7,3]两个分组进行插入排序。累计交换了1+1 = 2次。

  • 第二趟对[1,9,2]、[3,5,8]和[6,4,7]三个分组分别进行插入排序,累计交换了1+0+1 = 2次

  • 第三趟对[1,3,4,2,5,6,9,8,7]进行插入排序。交换了5次

累计交换了2+2+5 = 9次

可以看到效果是非常明显的!

这样的效果是怎么做到的呢?我们回到我们的直接插入排序,我们说过直接插入排序在原数组有序的时候能够得到最优的时间复杂度O(n)。是一个线性的时间复杂度,而我们的希尔排序,就是通过将无序的数组先分组进行插入排序,将原本无序的数组变为基本有序的数组。

比如我们例子中倒数第二趟排序后得到的数组

[1,3,4,2,5,6,9,8,7]

这个数组就是一个基本有序的数组,可以看到小的数基本都在左边,右边基本都是较大的数。这样,我们就能将直接插入排序的时间复杂度向O(n)级靠齐。

使用Hibbard增量序列来进行希尔排序,可以将时间复杂度变为O(n^3/2)。

我们也终于从O(n^2)级时间复杂度向更快的排序算法迈进。

代码样例

 1#include <iostream>
2#include <cmath>
3
4template <typename T>
5void insertSort(T *arr,int n){
6    if(n < 1)
7        return;
8    int k = log2(n+1);                            
9    for(int delta = pow(2,k)-1;delta > 0;k--,delta = delta/2){      //delta为选取的增量,增量序列为(1,3,7...2^k-1)
10            for(int i = delta;i < n;i ++){
11                if(arr[i] < arr[i-delta]){              //根据增量分组排序
12                    T temp = arr[i];       //临时变量储存第i位的值
13                    int m;
14                    for(m = i-delta;m >=0 && arr[m] > temp;m -= delta){   //将所有大于第i位的元素都向后挪delta位
15                        arr[m+delta] = arr[m];
16                    }
17                    arr[m+delta] = temp;        //将第i位的值插入到有序序列中
18                }
19            }
20    }
21}
22int main(){
23    int arr[9] = {8,7,6,9,5,4,2,1,3};
24    int len = sizeof(arr)/sizeof(arr[0]);
25    insertSort(arr,len);
26    for(int i = 0;i < len;i++){
27        std::cout<<arr[i]<<" ";
28    }
29    std::cout<<std::endl;
30    return 0;
31}


以上是关于常见排序算法-希尔排序的主要内容,如果未能解决你的问题,请参考以下文章

常见排序算法——希尔排序

常见排序算法-----希尔排序

Java常见的排序算法---希尔排序

算法给小码农插入排序洞天,希尔排序轮回

八十五再探希尔排序,桶排序,计数排序和基数排序

几种常见的排序算法分析学习