【数据结构之排序】-详解希尔排序

希尔排序由来

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing IncrementSort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959年提出而得名。–源自百度百科

  • 希尔排序其实就是一种经过优化的直接插入算法,这种优化使得这种插入排序算法的时间复杂度得到了质变,(这点我们会在下文验证),但其在一些特殊情况下也存在缺陷。

希尔排序思想(升序举例)

  • 要想更好地理解希尔排序,就必须先理解直接插入排序,相比于希尔排序,直接插入排序的思想就简单地多,我们几乎都完成过直接插入排序,逢年过节和三五朋友常常使用棋牌或者麻将进行娱乐,当我们摸到牌以后会自然地去将牌面进行排序,而整理好牌面后再次摸牌得到一张牌插入到牌堆中,这种排序就是直接插入排序。

给定一个整型升序数组a,向其中插入一数字x,如下:

int a[]={2,3,4,6,8,9}
//向a中插入x=5应该如何做?

显然,遍历数组a,找到比x小的第一个值,然后将其插入至x后,同时不影响数组中其它值,故从数组末尾向头遍历
为了方便理解,我们先实现插入一个数字,就使用上述用例:

int end;//数组尾下标
int x;//要插入的数字
while(end>=0)//数组只剩一个元素或者没有元素
{ //倒序遍历
	if(a[end]>x) //如果元素大于x则后移
	{
		a[end+1]=a[end];
		end--;
	}
	else    
	{
		break;   //当元素小于x或者数组走到尽头时直接将end的后一位置赋值为x
	}
}
a[end+1]=x;

  • 插入排序初步思路形成,显然我们的目的不是真的插入一个数进入有序数组,而是在数组中找数进行插入排序,故end和x的值需要确定。
  • 观察单趟排序的代码,为了防止数组越界且保证每个值都被读取一次,end选择为数组每个元素的下标,当数组有n个元素,其最大为n-2,x选择为end后一位元素的值。这样做就是相当于将x插入[0,end]的区间中。显然使用for循环就能很好地完成这一切,至此,插入排序就完成了:
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end=i;
		int x=a[end+1];
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}
  • 显然,此排序的时间复杂度为O(N^2) ;
  • 希尔排序的思想在此基础上建立:是否能过通过一些操作,让序列更接近于有序然后再进行插入排序大幅度缩减其时间复杂度?
  • 希尔的思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

希尔排序实现

设定步长gap,可以将数组分为gap组,然后每个组内进行插入排序使其组内有序,最后整体使用一次插入排序,这时插入排序对一个接近有序的数组进行排序,效率就能很好地体现出
假设步长gap为5,随机给定一个数组,如图:
ljh
数组被分成5组,然后每个组内进行插入排序得到:
ljh
然后再此基础上再次使用一次插入排序,插入排序的时间复杂度会大大减小(前提是大量的数据,否则难以直接观察到):

思想上,我们是组内一组组进行插入排序,实际实现时只需要每遇见一个元素,然后与其关联为gap步的元素进行插入排序,而直接插入排序就可以理解为希尔排序中gap步长为1的特殊情况,代码应运而生:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end-=gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = x;
		}
	}
}

这里还有最后一个需要确定的元素就是gap的取值,经过大量测试,发现当数组越无序,gap越要取大,越接近有序gap越取小,故gap不是一个不变的值,这里最外层使用while循环控制,每次预排序都进行gap=gap/2;这使得gap等于1时最后对整个数组进行真正的插入排序就结束。

希尔排序的时间复杂度需要用数学知识来计算,这里附上《数据结构-用面相对象方法与C++描述》一书中殷人昆老师的计算和结论:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值