选择排序(二)——希尔排序

1、希尔排序基本思路:

之前的一篇博客中,介绍了直接插入排序的实现,希尔排序实际上是在直接插入排序的基础上优化

当一组数据,越接近有序,那么他需要调整(移动)的次数就越少

(往极端的方向想,如果一组数据是有序的,那么根本就无需排序)

                        ||

                      \ || /

                        V

希尔排序的思路:

(1)先通过分组预排序,让一组数据接近有序

(2)再通过直接插入排序,将这组数据重新排序

2、代码实现

 下面介绍的顺序是:

(a)先分组

(b)以红组为例,实现调整过程

(c)三个组预排序的整体实现

      (三组同时排序,红组排一次,绿组排一次,紫组排一次,然后又回到红组)

(d)直接插入排序

(1)分组

我们使用抽取的方法来将一组数据进行分组,抽取的间隔gap = 3

 这样就分成了 红组、绿组、紫组

(2)以红组为例进行排序

  调整的方式和直接插入排序的方式 几乎一样,但是需要注意的是,end每次移动的距离是 gap,

  而不是1

void ShellSort(int* a, int size, int gap)
{
	int end = 0;
	int val = a[end + gap];			//第一个注意点:end的下一个位置在 end+gap
	while (end>=0)
	{
		if (a[end] > val)
		{
			a[end + gap] = a[end];		//第二个注意点
			end -= gap;				//第三个注意点:end向前移动的时候,移动gap个单位
		}
		else
		{
			break;
		}
	}
	a[end + gap] = val;				//第四个注意点
}

(3)三组预排序

这里需要注意的两个地方是

(a)每个组的起始位置是连续的

(b)end最远能到达的位置只能是8所在的位置,即 size-1-gap 的位置 

void ShellSort(int* a, int size, int gap)
{
	for (size_t i = 0; i < size-1-gap; i++)
	{
		int end = i;						//end的起始位置是连续的
		int val = a[end + gap];			//第一个注意点:end的下一个位置在 end+gap
		while (end >= 0)
		{
			if (a[end] > val)
			{
				a[end + gap] = a[end];		//第二个注意点
				end -= gap;				//第三个注意点:end向前移动的时候,移动gap个单位
			}
			else
			{
				break;
			}
		}
		a[end + gap] = val;				//第四个注意点
	}
}

(4)直接插入排序

经过预排序后,这组数据更加接近有序,将这组数据送入直接插入排序即可

直接插入排序函数我们使用之前博客写好的选择排序——直接插入排序_challenglistic的博客-CSDN博客1、基本思想(1)想象 3的前面是一个空的有序数组(2)先将3 放入这个有序数组,然后从第2个元素开始,逐个和有序数组中的元素 排序横向:插入新的元素后,有序数组的自我调整纵向:有序数组调整完毕,有序数组多一位成员(3)直到end到达第 size-1 位size:数组元素的个数2、代码实现(1)单次有序数组的调整过程假设 以end = 2的调整为例int end = 2;int val = a[end + 1...https://blog.csdn.net/challenglistic/article/details/123396468?spm=1001.2014.3001.5501

void InsertSort(int* a,int size)
{
	for (size_t i = 0; i < size-1; i++)
	{
		int end = i;
		int val = a[end + 1];	//事先记录下每次要插入的数
		while (end >= 0)
		{
			if (a[end] > val)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = val;		//end = -1时,会跳出循环,说明有序数组中的所有元素都比val要小
	}
}

void ShellSort(int* a, int size, int gap)
{
    /*************************预排序********************************/
	for (size_t i = 0; i < size-1-gap; i++)
	{
		int end = i;						//end的起始位置是连续的
		int val = a[end + gap];			//第一个注意点:end的下一个位置在 end+gap
		while (end >= 0)
		{
			if (a[end] > val)
			{
				a[end + gap] = val;		//第二个注意点
				end -= gap;				//第三个注意点:end向前移动的时候,移动gap个单位
			}
			else
			{
				break;
			}
		}
		a[end + gap] = val;				//第四个注意点
	}

    /*************************直接插入排序****************************/
    InsertSort(a,size);
    
}

3、结果测试

int main() {
	
	int arr[] = { 3,5,2,6,1,7,4,8,12,10,11,9,15,14,13 };
	int gap = 3;
	ShellSort(arr, sizeof(arr) / sizeof(arr[0]), gap);

	for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}

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

​​​​​​​

4、预排序优化

预排序的时候,我们设置gap = 3,我们测试一下指针的移动次数

(1)gap与预排序的联系:

gap越大,预排序越快,但是 越不有序

gap越小,预排序越慢,但是 越有序

(2)思路

我们可以多次预排序,每次预排序结束,gap = gap/2,直到gap = 1

gap较大的时候,预排序很快

随着gap减小,虽然慢,但更接近有序

(3)代码实现

我们将gap的初始值设置为size,每经过一次预排序,gap除以2

直到gap = 1(直接插入排序)

void ShellSort(int* a, int size)
{
	int gap = size / 2;
	while (gap>1)
	{
		gap /= 2;
		for (size_t i = 0; i <= size - 1 - gap; i++)
		{
			int end = i;						//end的起始位置是连续的
			int val = a[end + gap];			//第一个注意点:end的下一个位置在 end+gap
			while (end >= 0)
			{
				if (a[end] > val)
				{
					a[end + gap] = a[end];		//第二个注意点
					end -= gap;				//第三个注意点:end向前移动的时候,移动gap个单位
					p++;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = val;				//第四个注意点
		}
	}	
	InsertSort(a, size);
}

(4)代码测试结果

 排序的个数比较少的时候,前后gap的设置方法没有特别明显的差异

 

5、时间复杂度

 希尔排序的时间复杂度计算十分复杂,这里就直接给出结果

希尔排序的移动次数和gap有关,平均移动次数是 N^1.3 

时间复杂度接近 O(N)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值