内部排序(二)希尔排序的两种实现

希尔排序是一种改进的插入排序算法,通过设定不同的增量来减少元素移动次数。其基本思想是将待排序列按增量分成多个子序列,对每个子序列进行直接插入排序,然后逐渐减小增量,重复排序过程,直至增量为1,完成排序。文章介绍了希尔排序的两种实现方法,包括标准实现和带哨兵的直接插入排序法,并提供了完整代码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇日志讲到,对于直接插入排序法,如果要做改良,可以从两个方面入手,减少元素之间的比较次数和减少元素之间的移动次数。从减少元素之间的比较次数方面,我们可以用二分查找的思想,因为在直接插入排序的“寻找插入位置”这一过程,是一个查找过程,所以可以嵌入二分查找的方法来寻找插入位置,把直接插入排序改良成二分插入排序,这是减少了序列中元素之间比较的次数。那么如果想要减少元素之间移动的次数,怎么做?

希尔排序是一种方法,又称“缩小增量排序”法。希尔排序可以减少直接插入排序中元素移动的次数,从而加快排序的进行。它是怎么做到的?看看希尔排序的思路:

  1. 先将待排序列按照某一“增量”,分割成若干个子序列。
  2. 对每一个子序列做直接插入排序。
  3. 缩小增量,继续把整个序列按照增量分割成若干个子序列。
  4. 继续对每一个子序列做直接插入排序。

…………………………………………

     5.重复上述步骤,直到最后增量为1后,也就是最后一次希尔排序,变成了一次直接插入排序。

上面就是希尔排序的过程。直接插入排序元素之间移动次数多的原因是每次只交换相邻两个元素之间的位置,这样做每次只能消除一对位置错误的元素,而希尔排序每次交换的是一定间隔的元素,通过每次消除一定间隔距离的元素,来减少排序过程中元素之间的移动次数。

      具体是怎么实现的,我们能通过代码来看看:

/*希尔排序方法1*/
void Shell_Sort(PtrlSqList P, int N, int Increment[]) 
{
	int increases, j, Tmp, k, Si;
	int count=0; /*排序次数的计数*/
	/*获得待排序列的长度,用来决定使用增量序列的长度*/
	int ListLength=GetListLength(P);
	
	/*遍历得出需要增量序列的长度*/
	for (Si=0; Increment[Si]>=ListLength; Si++);
	/*完成后得到的就是只需要用到Increment[Si]和之前的元素*/
	
	printf("\n需要用到的增量序列为:");
	for (j=Si; Increment[j]>=1; j++) {
		printf("%d ", Increment[j]);
	}
	printf("\n\n希尔插入排序过程:\n");
	/*选择增量,当增量不等与1时,到下一个增量*/
	for (increases=Increment[Si]; increases>=1; increases=Increment[++Si]) {
		/*直接插入排序*/
		for (k=increases; k<=ListLength; k++) {
			Tmp=P->arr[k]; /*从序列中逐个拿出元素*/
			for (j=k; j>=increases && P->arr[j-increases]>Tmp; j-=increases) {
				P->arr[j]=P->arr[j-increases]; /*往后挪一位*/
			}
			P->arr[j]=Tmp; /*最后找到比Tmp大的数都往后挪了,留下正确的位置就插入*/
		}
		count++;
		PrintList(P, count); /*此为输出序列函数,此处每次输出一次排序后的序列*/ 
		printf("\n");
		printf("\n");
	}
} 

表示增量的逐步缩小,我们用一个增量数组(第80行)来存放增量,并传入希尔排序的函数中。首先我们取得待排序列的长度,根据待排序列的长度来决定需要最大多大的增量。第90行从增量序列中第一个元素(增量最大的元素)开始,如果增量比待排序列的长度大,就不要,Si++;循环做完后,Si就是小于待排序列长度的最大增量。我们从这个增量开始,做缩小增量排序。

接着到排序过程,从增量序列中拿出第一个最大的增量赋值给increases,当increases增量不等于1也就是没到最后一次排序时,就从增量序列中继续拿出下一个缩小了的增量。

拿出一个增量后,从这个增量开始,做直接插入排序(第99行,k从increases开始到ListLength,即当作待排序列做直接插入排序),拿出序列中的第一个位置的元素赋给Tmp暂时保存,接着最内层for循环,j从k开始,即在“已排好序列”中,如果发现前一个增量位置的元素大于Tmp,就把该元素后移一个增量的位置,然后j后移一个增量。最后比Tmp大的数都后挪了一个增量位置后,找到最小的位置j,让Tmp赋值给P->arr[ j ]。

上面是第一种实现方法,还有第二种是改良“带哨兵的直接插入排序法”的希尔排序,我们来看具体怎么做:

/*希尔排序方法2*/
void Shell_Sort(PtrlSqList P, int length, int Increment[]) 
{
	int increases, j, k, ListLength, Si;
	int count=0;
	ListLength=GetLength(P);
	
	/*遍历获得需要的增量序列*/
	for (Si=0; Increment[Si]>=ListLength; Si++);
	
	printf("\n需要用到的增量序列为:");
	for (j=Si; Increment[j]>=1; j++) {
		printf("%d ", Increment[j]);
	}
	
	printf("\n\n希尔排序过程:\n");
	for (increases=Increment[Si]; increases>=1; increases=Increment[++Si]) {
		/*插入排序*/
		for (j=increases; j<=ListLength; j++) {
			/*如果后一个数小于前一个间隔增量的数*/
			if (P->arr[j]<P->arr[j-increases]) {
				P->arr[0]=P->arr[j]; /*把较小的数赋值给哨兵暂存*/
				for (k=j-increases; k>0 && P->arr[0]<P->arr[k]; k-=increases) {
					/*如果P->arr[0]<P->arr[k],即哨兵处元素比某一增量处的元素小的话*/
					P->arr[k+increases]=P->arr[k]; /*记录后移,找出插入位置*/
				} 
				P->arr[k+increases]=P->arr[0];
			}
		}
		count++;
		PrintList(P, count);
		printf("\n\n");
	}
}

前面同样是传进去增量数组Incerment,然后获取待排序列的长度并获得需要的增量序列。第108行同样从第一个最大增量开始,做插入排序,第110行i从increases开始,判断如果后一个数小于前一个间隔增量的数,就先把较小的数赋值给P->arr[ 0 ]哨兵处保存,然后最内层for循环,k从j-increases开始,即”已排好序列”中开始按增量逐一比较,如果有某一处元素比增量元素大的话,就把这个较大的元素后移一个增量的位置,最后for循环做完后,比哨兵处大的元素都后移了一个增量的位置,剩下的k+increases位置就是插入位置(因为循环每次k-=increases,当发现P->arr[0]不是小于P->arr[k]后,k要加increases才回到后一个,即正确的的插入位置)。

完整代码在个人代码云:

https://gitee.com/justinzeng/codes/cs9zbyxqhprlfukoji16e96

https://gitee.com/justinzeng/codes/50owqe176dzma3kjx9pul15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值