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);
}

3.2 整体代码实现

#include <iostream>
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. 希尔排序思路希尔排序代码参考文章1常见的几种排序(C++)
  2. 优秀博文:十大经典排序算法-希尔排序算法详解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十月旧城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值