【排序算法】二、希尔排序(C/C++)

「前言」文章内容是排序算法之希尔排序的讲解。(所有文章已经分类好,放心食用)

「归属专栏」排序算法

「主页链接」个人主页

「笔者」枫叶先生(fy)

目录

  • 希尔排序
    • [1.1 原理](#1.1 原理)
    • [1.2 代码实现(C/C++)](#1.2 代码实现(C/C++))
    • [1.3 特性总结](#1.3 特性总结)

希尔排序

1.1 原理

希尔排序是一种基于直接插入排序的排序算法,也称为"缩小增量排序"

希尔排序法的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序

希尔排序:基于数组(顺序表)的结构进行排序

希尔排序的由来

希尔排序是按其设计者希尔的名字命名的

他对普直接入排序的时间复杂度进行分析,得出了以下结论:

  1. 直接插入排序的时间复杂度最坏情况下为O(N^2),此时待排序列为逆序,或者说接近逆序
  2. 直接插入排序的时间复杂度最好情况下为O(N),此时待排序列为升序,或者说接近升序

于是希尔就想:若是能先将待排序列进行一次预排序 ,使待排序列接近有序(接近我们想要的顺序),然后再对该序列进行一次直接插入排序,此时已经排好序了,并且时间复杂度也降低了

希尔排序 = 预排序 + 一次直接插入排序

因为此时直接插入排序的时间复杂度为O(N)(直接插入排序对排序的数组接近有序的时候,时间复杂度为O(1)),那么只要控制预排序阶段的时间复杂度不超过O(^2),那么整体的时间复杂度就比直接插入排序的时间复杂度低了

希尔排序的步骤如下:

  1. 首先选择一个增量序列,通常选择增量序列gap为n/2,n/4,n/8...1,其中n为数组的长度
  2. 根据增量序列将数组分成若干个子序列,对每个子序列进行插入排序
  3. 逐渐缩小增量序列,重复步骤2,直到增量为1,即对整个数组进行一次插入排序

例如,对该数组使用希尔排序进行排序(升序)

取第一次增量gap为:gap = 10/2,10为数组大小,此时相隔距离为5的元素被分为一组(共分了5组,每组有2个元素),然后分别对每一组 进行直接插入排序

取第二次增量gap为:gap = 5/2,5为第一次增量值,此时相隔距离为2的元素被分为一组(共分了2组,每组有5个元素),然后再分别对每一组 进行直接插入排序

取第三次增量gap为:gap = 2/2,2为第二次增量值,此时gap为1,即整个序列被分为一组,进行一次直接插入排序(对整个数组)

动图演示如下:

为什么要让gap由大到小?

  • gap越大,数据挪动得越快;gap越小,数据挪动得越慢
  • 前期让gap较大,可以让数据更快得移动到自己对应的位置附近,减少挪动次数

注:一般情况下,取序列的一半作为增量,然后依次减半,直到增量为1(也可自己设置)

希尔排序的时空复杂度

希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在不同的书中给出的希尔排序的时间复杂度都不固定

Knuth序列是一种希尔排序中常用的增量序列,由Donald Knuth提出。Knuth序列的计算公式为:h = 3h + 1,其中h是增量序列的值,下面代码gap使用这个公式(有兴趣自己了解)

Knuth进行了大量的试验统计,暂时就按照:O(N^1.25)O(1.6 * N^1.25)来算,按O(N^1.3)计算(最好情况下)

  • 希尔排序时间复杂度O(N*logN) ~ O(N^2),以2为底,最好O(N^1.3),最坏 O(N^2)
  • 希尔排序空间复杂度O(1)

1.2 代码实现(C/C++)

C语言代码如下:(升序)

cpp 复制代码
// 希尔排序(基于直接插入排序)
void ShellSort(int* arr, int n) // arr:需要排序的数组; n:数组的大小
{
	int gap = n;
	while (gap > 1)
	{
		// gap 大于 1, 进行预排序
		// gap == 1, 相当于直接插入排序(注:循环进来 gap 经过 gap = gap / 3 + 1 这才变成 1 )
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i; // 记录有序列的最后一个下标
			int tmp = arr[end + gap]; // 保存等待插入的值

			while (end >= 0)
			{
				if (arr[end] > tmp) // arr[end]比要插入的数大,向后移动
				{
					arr[end + gap] = arr[end]; // 向后移,进行覆盖,tmp已经保存被覆盖的值
					end -= gap;
				}
				else // arr[end]比要插入的数小,已有序,跳出循环
				{
					break; 
				}
			}
			arr[end + gap] = tmp; // 进行插入
		}
	}
}

C++代码:(升序)

cpp 复制代码
// 希尔排序(基于直接插入排序)
void ShellSort(vector<int>& arr)
{
	int n = arr.size();
	int gap = n;
	while (gap > 1)
	{
		// gap 大于 1, 进行预排序
		// gap == 1, 相当于直接插入排序(注:循环进来 gap 经过 gap = gap / 3 + 1 这才变成 1 )
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i; // 记录有序列的最后一个下标
			int tmp = arr[end + gap]; // 保存等待插入的值

			while (end >= 0)
			{
				if (arr[end] > tmp) // arr[end]比要插入的数大,向后移动
				{
					arr[end + gap] = arr[end]; // 向后移,进行覆盖,tmp已经保存被覆盖的值
					end -= gap;
				}
				else // arr[end]比要插入的数小,已有序,跳出循环
				{
					break;
				}
			}
			arr[end + gap] = tmp; // 进行插入
		}
	}
}

1.3 特性总结

希尔排序的特性总结

  • 希尔排序是对直接插入排序的优化
  • gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。整体而言,可以达到优化的效果
  • 希尔排序时间复杂度O(N*logN) ~ O(N^2),以2为底
  • 空间复杂度O(1)
  • 稳定性:不稳定

--------------------- END ----------------------

cpp 复制代码
「 作者 」 枫叶先生
「 更新 」 2024.1.9
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。
相关推荐
小俊俊的博客21 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
嵌入式科普35 分钟前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
_WndProc36 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_37 分钟前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_7482400238 分钟前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
qq_433554541 小时前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
努力学习编程的伍大侠1 小时前
基础排序算法
数据结构·c++·算法
yuyanjingtao2 小时前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
闻缺陷则喜何志丹2 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141912 小时前
C++ STL CookBook
开发语言·c++·stl·c++20