【手撕插入排序和希尔排序】

简介: 插入排序概念直接插入排序是从一个有序的序列中选择一个合适的位置进行插入,这个合适的位置取决于是要升序排序还是降序排序。每一次进行排序之后,这段数据都是有序的。

插入排序概念

直接插入排序是从一个有序的序列中选择一个合适的位置进行插入,这个合适的位置取决于是要升序排序还是降序排序。

每一次进行排序之后,这段数据都是有序的。

插入排序分为2种

一 .直接插入排序

直接插入排序是从一段数据中将一个数据在合适的位置插入。

案例:

一张图弄懂直接插入排序

dfd76a71934d44fbbf03c49ccfa49c71.png

9913f60e0f214114927e776e0a566a93.png

代码如下:

void InsertSort(int * a,int n )
{
  for(int i =0;i<n-1;i++)
  {
    int end = i;
    //保存待插入元素
    int tmp = a[end+1];
    while(end>=0)
    {
      if(a[end]>tmp)
      {
        //把end往后挪
        a[end+1] = a[end];
        //end再往前走 
        end--;
      }
      else
      {
        break;
      }
    }
    //由于不管是在中间的任意地方插入还是在end的末尾插入(即tmp是最大的情况),
    //都是在end后面的位置插入,所以来到这里进行合并
    a[end+1] = tmp;
  }
}

直接插入排序时间复杂度

直接插入排序的时间复杂度为:O(N^2),因为最坏的情况是逆序的情况

4e8446149b214f74b1ea02f80815caf5.png

每一次插入需要挪动的次数为:1+2+3+4+…+n-2+n-1 = n*n/2

所以最坏情况下的时间复杂度为O(n^2)

二.希尔排序

希尔排序可以被认为是优化后的直接插入排序。

具体优化过程如下:

给定一个gap,这个gap是把待插入的数据分成gap组,每组之间的间隔为gap长度

给定一个gap,这个gap是把待插入的数据分成gap组,每组之间的间隔为gap长度

给定一个gap,这个gap是把待插入的数据分成gap组,每组之间的间隔为gap长度


重要的事情说三遍。


比如:

c149e78132a64f7fb532a05e89233b06.png

gap = 3,即待插入的数据的间隔为3,不同于直接插入排序,直接插入排序是第一个和第二个数据的间隔永远为1,而对于希尔排序,当gap = 3时,第一个数据和第二个数据的间隔为3。

当我们把该组的元素两两比较时,大的元素就会更快地往后走。

17fc3049ae0147e79f9fb061315d9665.png

第二轮是将待插入元素8和9比较,因为9后面的第一个元素不再是7,而是9+gap的位置处的数据,即8

再将9和8进行比较,将8插入到9位置处。

当然,这是每组组内的比较,

放眼整个希尔排序来说,是多组同时进行的

可以发现,

当gap越大,大的元素越快挪到后面
当gap越小,小的元素越慢挪到后面

当gap == 1时,就相当于上面提到的直接插入排序。

回到上面的案例,gap = 3,所以需要将数据分成3组,每组的间隔为3个长度。

8859e7d3548542a598b7cd411a9aadf3.png

如上图:此时每个元素都可以被覆盖到。


1fe655b1d14145f48065cf581a058472.png

相当于同时把gap组中大的元素更快挪到后面

我们把上面的过程成为:预排序

也就是说,完成上面的操作之后,整个数据并不是有序的,而是 接近有序

比如上面的案例,完成预排序后,整组数据为:

a0801cd308824a3392f9e8c66fc2309a.png

此时是接近有序,所以此时令gap = 1,即最后接近有序的时候进行直接插入排序即可。

注意:gap的取值是不确定的:

gap取值越大,大的数据越快挪到后面,但越不接近有序

gap取值越小,大的数据越慢挪到后面,但越接近有序

总之gap是一定不能固定,并且gap的取值最后必须为1。

gap的取值应该是从大逐渐到小过渡的。

gap的取值一般是:

初始化gap = n,

进入轮回时:

gap = gap/3+1 或者 gap = gap/2,每次轮完一轮后,gap都会减小。

当gap的取值是gap = gap/2时,时间复杂度为:O(N*logN),logN是以2为底N的对数

最坏情况同样为逆序:

73ec8c57243845508c9b63cadd3da5b4.png

最后一轮gap = 1,此时为直接插入排序,则N/2/2/2/…/2 = 1,

每次轮回一次gap,gap都会/2,最后一次gap = 1,则需要比较的次数是logN(以2为底N的对数)

希尔排序时间复杂度

总的时间复杂度为遍历整组元素的次数:O(N)*每次遍历进行插入的次数O(logN)

—> O(N * logN)

同理:当gap的变化是gap = gap/3-1时, 最坏情况下(逆序)每次轮回需要插入的次数是


(((N/3+1) /3+1)/3+1)… = 1

对于时间复杂度:可忽略掉+1项,所以每次轮回插入次数log3 (N) ,以3为底N的对数


总时间复杂度为O(N*log3 (N))


经过前人计算,希尔排序平均时间复杂度为:

O(N^1.3)

cc4f7b24f9094f5b9291695720af0c27.png

实现代码:

void ShellSort(int* a, int n)
{
  //当gap越大,大的值越快到达最后的位置,但越不接近有序
  //当gap越小,大的值越慢到达最后的位置,但越接近有序
  //当gap值越接近1,排序越接近顺序
  //刚gap == 1时,就是直接插入排序
  int gap = n;
  while (gap > 1)
  {
    //两种方式均可,gap可以任取任何值,但是都必须保证gap最后一定为1
    //gap = gap / 2;
    gap = gap / 3 + 1;
    //在这里就是把间隔多组的数据同时排列
    for (int i = 0; i < n - gap; i++)
    {
      int end = i;
      int tmp = a[end + gap];
      while (end >= 0)
      {
        //小于的情况,需要挪动数据
        if (a[end] > tmp)
        {
          a[end + gap] = a[end];
          end -= gap;
        }
        //大于或者等于的情况,直接插入end后面
        else
        {
          break;
        }
      }
      //由于最终都需要插入end后面,所以在循环之外插入
      a[end + gap] = tmp;
    }
  }
}

总结:希尔排序是在直接插入排序的基础上引入一个gap,这个gap把数据分成了gap组,并且每组元素之间的间隔也为gap。
gap每次都会逐渐减小,并且最后gap一定为1,当gap为1时,代表完成了预排序,
最后一步进行直接插入排序。

效率比较

void TestOP()
{
  srand(time(0));
  const int N = 1000000;
  int* a1 = (int*)malloc(sizeof(int) * N);
  int* a2 = (int*)malloc(sizeof(int) * N);
  assert(a1 && a2);
  for (int i = 0; i < N; ++i)
  {
    a1[i] = rand();
    a2[i] = a1[i];
  }
  //计算到这个走位置的时间(ms)
  int begin1 = clock();
  InsertSort(a1, N);
  int end1 = clock();
  //末位置-初位置就是时间差
  int begin2 = clock();
  ShellSort(a2, N);
  int end2 = clock();
  printf("InsertSort:%d\n", end1 - begin1);
  printf("ShellSort:%d\n", end2 - begin2);
  free(a1);
  free(a2);
}
int main()
{
  TestOP();
  return 0;
}

ba5ff8ba63cb471ea512f06de718de41.png

可以看到,当排序数据为100w个时,直接插入排序和希尔排序差距超过了2000倍。

直接插入排序和希尔排序的效率相比,数据越多,希尔排序较于直接插入排序的优化越大。

相关文章
|
4月前
|
人工智能 算法
蓝桥杯真题宝藏排序详解 | 冒泡排序 选择排序 插入排序
蓝桥杯真题宝藏排序详解 | 冒泡排序 选择排序 插入排序
|
9月前
|
机器学习/深度学习 算法 搜索推荐
【算法训练-排序算法 一】【手撕排序】快速排序、堆排序、归并排序
【算法训练-排序算法 一】【手撕排序】快速排序、堆排序、归并排序
101 0
|
机器学习/深度学习 搜索推荐 算法
【手撕排序算法1:插入排序与希尔排序】
【手撕排序算法1:插入排序与希尔排序】
|
搜索推荐 算法
手撕排序算法2:堆排序和直接选择排序(下)
手撕排序算法2:堆排序和直接选择排序(下)
|
算法 搜索推荐 测试技术
手撕排序算法2:堆排序和直接选择排序(上)
手撕排序算法2:堆排序和直接选择排序(上)
101 0
|
搜索推荐 算法 C语言
手撕排序算法5:快速排序非递归版本和计数排序
手撕排序算法5:快速排序非递归版本和计数排序
|
搜索推荐 算法
手撕排序算法4:归并排序及其非递归版本(上)
手撕排序算法4:归并排序及其非递归版本(上)
|
搜索推荐 算法
手撕排序算法4:归并排序及其非递归版本(下)
手撕排序算法4:归并排序及其非递归版本(下)
|
搜索推荐 算法 C++
【数据结构与算法篇】手撕排序算法之插入排序与希尔排序
【数据结构与算法篇】手撕排序算法之插入排序与希尔排序
89 0
|
机器学习/深度学习 存储 算法
【数据结构与算法篇】 手撕八大排序算法之选择排序
【数据结构与算法篇】 手撕八大排序算法之选择排序
79 0