ShellSort(希尔排序)——C语言实现

前言:这个需要先理解插入排序

 

然后这个希尔排序呢,其实是一种分段分段的插入排序,是把原来的数列按一定的尺度分成几个小的序列进行插入排序,然后缩小尺度继续进行插入排序,直到尺度缩小到1进行最后一次正常的插入排序,就可以得到最后完整的序列。

所以,希尔排序也叫缩小增量排序。(这个增量是针对下表而言的,不是针对数字大小)

思路:

首先我们需要确认尺度,最简单的尺度就是length不断整除二,我们先这么写:

这边有张图非常清晰明了,我就直接摘过来了;

原文链接是:排序:希尔排序(算法) - 简书 (jianshu.com)

1.主要的思路就是按照一定的间隔,我们把原数组分成若干个小的新数组,然后在小的数组中进行插入排序。说到这,有的算法中写的是 for(for(for(if 的结构来展现希尔排序,然而我认为既然你是继承插入排序,最关键那层没必要不和插入排序一样,所以我用的是 for(for(while 的结构来展现希尔排序,也就是最关键的排序部分用的是插入排序。

2.如何选择间隔(gap)?最简单的自然是对length不断进行二分,从而实现,但是会存在弊端,我们放在文章的后半部分。如何进行二分?最直接的自然是 /=2 这个操作,但是用位运算会快一些,我们可以认为 >>=1 运算与 /=2 一模一样。

既然要选择gap,而且gap在每一轮中是不变的,因此gap的变化要放在最外层的for中实现。

3.剩下的一层 for(while 其实我们就是用来实现插入排序的,格式都一样。但是这边的插入排序有些不一样,他并不是一次可以把一个分出来的小数组全部排列完,他是先排前两个,然后自增,排另一个分出来的小数组···直到自增到最先那个小数组的第三个元素,这时才相当于把第一个小数组的前三个元素进行了插入排序。至于为什么要这么实现,自然是不想开辟新的空间,浪费内存。

思路说到这就差不多了,一些细枝末节写在注释中。 下面上代码:

void ShellSort(int *a,int length)
{
	int i, j;
	int gap,temp;
	for (gap = length >> 1; gap >= 1 ; gap >>= 1)
	{
		for (i = gap; i < length; i++)//这边为什么是自增,看我上头写的3
		{
            //下面就是一个完整的插入排序
            //其余部分为什么都是加减gap,看我上面写的去/doge
			temp = a[i];
			j = i - gap;
			while (j >= 0 && a[j] > temp)
			{
				a[j + gap] = a[j];
				j-=gap;
			}
			a[j + gap] = temp;
		}
	}
}

拓展一下:

我们gap序列的选择时length/2,但如果尺度的选取是类似1 2 4 8 这样的话,我们前面的几次插入排序都是徒劳的,效率反而比直接的插入排序要低上许多,所以我们选取尺度序列需要进行一定的考量。这边参考了

排序算法之希尔排序及其增量序列 - ccneko - 博客园

其中提到的Hibbard增量序列:

这个增量序列的递推公式是Dk=2^k−1:{1, 3, 7, 15, 31, 63, 127, 255, 511, 1023...}

然后我们想要借助新的gap序列实现希尔排序,就需要稍微改动一下原来的代码了,首先是我们需要设置个函数开辟个新的数组来存增量(其实也不一定,不过这样比较通用)。

1.在这个创建GAP序列的函数中,我们需要创建一个可以在别的函数调用的数组,因此我们开辟的时候要声明全局变量(静态变量)static。

2.根据所需的长度来生成一些gap值

3.需要返回所创建的gap序列的首地址。‘

因此,函数如下:(这里我依旧采用位运算来代替乘法,可以认为 <<=1 与 *2 作用相仿)

int* Get_Hibbard_Gap(int length)//传入你需要排序的个数,生成gap序列。
{
	static int arr[N] = { 0 };
	int num = 2;
	for (int i = 0; num < length + 1; i++)
	{
		arr[i] = num - 1;
		num <<= 1;
	}
	return arr;
}

然后只需要在原来的ShellSort稍作更改,就可以应用新序列进行排序,一些细节写在注释里,下面是代码:

void Hibbard_ShellSort(int *a,int length)
{
	int i, j;
	int temp = 0;
	int* gap = Get_Hibbard_Gap(length);
	//由于从后往前使用gap序列,我们要先找到gap序列中最后一个数对应的下标
	while (gap[temp])//这边就没必要新建个变量了,麻烦一下暂时用不上的temp  /doge
		temp++;
	temp -= 1;

	//下面的代码整体上几乎只需要把之前的gap改成gap数组中的值就行,没什么大变化
	for (int k = temp; k >= 0; k--)
	{
		printf("gap = %d\n", gap[k]);//用来测试的
	
		for (i = gap[k]; i < length; i++)
		{
			temp = a[i];
			j = i - gap[k];
			while (j >= 0 && a[j] > temp)
			{
				a[j + gap[k]] = a[j];
				j -= gap[k];
			}
			a[j + gap[k]] = temp;
		}
		//这个打印用来测试的
		for (int i = 0; i < N; i++)
			printf("%d ", a[i]);
		printf("\n");
	}
}

然后这个希尔排序就完成了,下面展示一下我的测试代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define N 30

//之前写过了,就不再全写一遍了
void generate_random_number(int*, int, int);
void swap(int*, int*);

int* Get_Hibbard_Gap(int length)//传入你需要排序的个数,生成gap序列。
{
	static int arr[N] = { 0 };
	int num = 2;
	for (int i = 0; num < length + 1; i++)
	{
		arr[i] = num - 1;
		num <<= 1;
	}
	return arr;
}

void Hibbard_ShellSort(int *a,int length)
{
	int i, j;
	int temp = 0;
	int* gap = Get_Hibbard_Gap(length);
	//由于从后往前使用gap序列,我们要先找到gap序列中最后一个数对应的下标
	while (gap[temp])//这边就没必要新建个变量了,麻烦一下暂时用不上的temp  /doge
		temp++;
	temp -= 1;

	//下面的代码整体上几乎只需要把之前的gap改成gap数组中的值就行,没什么大变化
	for (int k = temp; k >= 0; k--)
	{
		printf("gap = %d\n", gap[k]);//用来测试的
	
		for (i = gap[k]; i < length; i++)
		{
			temp = a[i];
			j = i - gap[k];
			while (j >= 0 && a[j] > temp)
			{
				a[j + gap[k]] = a[j];
				j -= gap[k];
			}
			a[j + gap[k]] = temp;
		}
		//这个打印用来测试的
		for (int i = 0; i < N; i++)
			printf("%d ", a[i]);
		printf("\n");
	}
}

int main()
{
	int arr[N + 10] = { 0 };
	generate_random_number(arr, 0, 1024);

	Hibbard_ShellSort(arr,N);

	printf("排序后数列:\n");
	for (int i = 0; i < N; i++)
		printf("%d ", arr[i]);

	printf("\n");
	return 0;
}

测试结果:

 至此,ShellSort(希尔排序)完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值