• C++数据结构X篇_22_希尔排序(不稳定的排序)


    希尔排序又叫递减增量排序算法,它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的;希尔排序是一种不稳定的排序算法。

    1. 希尔排序的基本原理

    我们之前讲过直接插入排序,它的算法复杂度为O(n^2),整体来说它的效率很低;但是在两种情况下它表现得很好,我们这里将这两种情况归纳为直接插入排序的两种性质:

    • 当待排序的原序列中大多数元素都已有序的情况下,此时进行的元素比较和移动的次数较少;

    • 当原序列的长度很小时,即便它的所有元素都是无序的,此时进行的元素比较和移动的次数还是很少。

    希尔排序去模拟这两种条件,让插入排序效率更高,这就涉及到分组插入排序。
    它首先将待排序的原序列划分成很多小的序列,称为子序列。然后对这些子序列进行直接插入排序,因为每个子序列中的元素较少,所以在它们上面应用直接插入排序效率较高(利用了上面的性质2)。这样的过程可能会进行很多次,每一次都称为一趟,每一趟都将前一趟得到的整个序列划分为不同的子序列,并再次对这些子序列进行直接插入排序。最后由这些子序列组成的整个序列中的所有元素就基本有序了,此时再在整个序列上进行最后一次的直接插入排序(利用了上面的性质1),此后整个序列的排序就完成了。

    2. 子序列的划分方式

    2.1 平均划分不可行的原因

    希尔排序最关键的地方就是如何对整个序列进行划分。我们最直接的想法可能就是按顺序对整个序列进行平均划分,比如有n个元素的序列,我们要把它划分成i个子序列,每个子序列有m个元素(假设n = i * m,当n不能被i整除时可以让最后一个子序列的元素少于其它子序列)。该想法就是让原序列的第0 ~ m-1个元素为第一个子序列(第一个元素的下标为0),第m ~ 2m-1个元素为第二个子序列,以此类推,最后的第n-m ~ n-1个元素为最后一个子序列。

    这样的划分虽然简单但是它却不适合希尔排序,这样划分并对子序列进行直接插入排序后,虽然每个子序列中的元素都是有序的,但整个原序列依旧是很无序的。为了便于理解为什么这种方式不行,我们用下图来对它进行说明。
    在这里插入图片描述
    在图1中,原序列有9个元素,我们将它按顺序划分为3个子序列。即最前面的3个元素为第一个子序列,中间3个元素为第二个子序列,最后3个元素为最后一个子序列;图中我们用不同的颜色表示不同的子序列。

    可以看到,对每个子序列应用直接插入排序后,虽然每个子序列都是有序的,但整个序列还是很无序的。此时,在整个序列上进行直接插入排序的效率还是很低。整个序列依旧无序的原因是每个元素只在它所在的子序列中移动,它的新位置离它的最终位置(即整个序列排好序后的位置)还是很远。

    因此,子序列划分的方法必须保证对子序列进行排序后,每个元素在整个序列中的移动范围更大。这样跳跃式的位置移动,才可能让每个元素离它的最终位置较近,因而整个序列才是比较有序的。

    2.2 增量方式进行子序列划分

    希尔排序采用的是按增量的方式进行子序列划分,将整个序列中下标值相差固定值(增量)的所有元素划分到同一个子序列中。比如,整个序列有9个元素,而增量为3,那么第0、3、6个元素属于第一个子序列,第1、4、7个元素属于第二个子序列,第2、5、8个元素属于最后一个子序列。

    为了便于理解,我们同样用图2来展示这种增量划分方式。我们同样用不同的颜色表示属于不同子序列的元素,并标出了每个子序列的元素的下标值。可以看到,以增量方式划分的子序列在整个序列中是交错出现的。
    在这里插入图片描述
    按照增量划分的时候,假设增量为increment,那么整个序列也将划分为increment个子序列。可以这样理解,我们遍历整个序列中的每个元素,并为每个元素指定它所属的子序列。首先是下标为0的元素,它属于第一个子序列;然后是下标为1的元素,它属于第二个子序列;以此类推,可知前increment个元素(下标为0 ~ increment-1)属于不同的子序列,共计increment个。从下标为increment的元素开始,每一个元素的下标值减去increment都大于或等于0,即这些元素都属于一个已存在的子序列。因此,整个序列将被划分为increment个子序列。

    在实际的应用中,待排序的原序列可能有很多个元素,成千上万甚至上亿。此时,为了充分利用希尔排序的效率,应该进行多趟排序,每一趟用不同的(严格说是递减的)增量对整个序列进行划分。即首先用增量i1对原序列进行划分,并对每个子序列进行直接插入排序;然后对前一步得到的整个序列用一个新的且较小的增量i2(i2小于i1)进行划分,并对每个子序列进行直接插入排序;然后又对前一步得到的整个序列用一个更小的增量i3(i3小于i2)进行划分,并对每个子序列进行直接插入排序。以此类推,直到最后增量为1,此时可以认为整个序列就是一个唯一的子序列,对它进行直接插入排序之后整个原始序列都是有序的了,希尔排序算法结束。

    3. 希尔排序的代码实现

    • 先分组,递减增量使得每一组元素变少,降低元素个数
    • 然后对每一组分别进行插入排序
    • 整体更趋近于元素序列基本有序
    • 最后一次的增量值必为1,进行直接插入排序

    3.1 核心代码

    //希尔排序
    void shell_sort(int arr[], int length)//升序
    {
    	int increasement = length;
    	int i, j, k;
    	do
    	{
    		//确定分组增量,递减,每次计算increasement,最后肯定为1,再对所有数据进行一次插入排序
    		increasement = increasement / 3 + 1;
    		for (i = 0; i < increasement; i++) //确定每一组第一个元素
    		{
    			//j开始为一组的第二个元素,跨度为increasement
    			for (j = i + increasement; j < length; j += increasement)
    			{
    				if (arr[j] < arr[j - increasement])
    				{
    					int temp = arr[j];
    					for (k = j - increasement; k >= 0; k -= increasement)
    					{
    						if (arr[k] > temp)
    						{
    							arr[k + increasement] = arr[k];
    						}
    						else
    						{
    							break;
    						}
    					}
    					arr[k + increasement] = temp;
    				}
    			}
    		}
    	} while (increasement > 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3.2 整体代码实现

    #include 
    using namespace std;
    
    void swap(int* a, int* b)
    {
    	int temp = *a;
    	*a = *b;
    	*b = temp;
    }
    
    //打印数组
    void printArr(int arr[])
    {
    	for (int i = 0; i < 10; i++)
    	{
    		cout << arr[i] << endl;
    	}
    }
    
    //希尔排序
    void shell_sort(int arr[], int length)//升序
    {
    	int increasement = length;
    	int i, j, k;
    	do
    	{
    		//确定分组增量,递减,每次计算increasement,最后肯定为1,再对所有数据进行一次插入排序
    		increasement = increasement / 3 + 1;
    		for (i = 0; i < increasement; i++) //确定每一组第一个元素
    		{
    			//j开始为一组的第二个元素,跨度为increasement
    			for (j = i + increasement; j < length; j += increasement)
    			{
    				if (arr[j] < arr[j - increasement])
    				{
    					int temp = arr[j];
    					for (k = j - increasement; k >= 0; k -= increasement)
    					{
    						if (arr[k] > temp)
    						{
    							arr[k + increasement] = arr[k];
    						}
    						else
    						{
    							break;
    						}
    					}
    					arr[k + increasement] = temp;
    				}
    			}
    		}
    	} while (increasement > 1);
    
    	printArr(arr);
    }
    
    int main()
    {
    	int arr[] = { 8,2,3,9,6,4,7,1,5,10 };
    	shell_sort(arr, 10);
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    运行结果:
    在这里插入图片描述

    1. 希尔排序思路希尔排序代码参考文章1常见的几种排序(C++)
    2. 优秀博文:十大经典排序算法-希尔排序算法详解
  • 相关阅读:
    DA2--获取网站用户数据集的大小
    gurobi 安装/license激活 记录
    Django笔记三十二之session登录验证操作
    LAPM概述及配置
    (web前端网页制作课作业)使用HTML+CSS制作非物质文化遗产专题网页设计与实现
    安装typescript环境并开启VSCode自动监视编译ts文件为js文件
    如何开发自己的npm包并上传到npm官网可以下载
    MySQL的数据类型
    6.Flink实时项目之业务数据分流
    python优雅写出文件之csv
  • 原文地址:https://blog.csdn.net/Dasis/article/details/134022459