115.【C语言】数据结构之排序(希尔排序)

目录

1.希尔排序(又称缩小增量排序)(插入排序的优化版本)

过程1:预排序

过程2:插入排序

2.代码

预排序代码

1.一次排一组(时间复杂度比第二种写法高)

运行结果

其他写法

2.一次排多组(多组并排)

运行结果

希尔排序代码

1.当预排序一次排一组时

运行结果

2.当预排序一次排多组时

运行结果

gap的其他变化方式


1.希尔排序(又称缩小增量排序)(插入排序的优化版本)

希尔排序(Shell's Sort):分为两个过程:预排序和直接插入排序

过程1:预排序

作用:让数组接近于有序状态

方法:分组插入排序(即对每组数字插入排序),例如间隔gap为一组

以升序为例,如数组int* arr\[\]={9,1,2,5,7,4,8,6,3,5}; ,设gap==3,即分三组插入排序

第一组:9,5,8,5(相互间隔gap步);第二组:1,7,6(相互间隔gap步);第三组:2,4,3(相互间隔gap步)

第一组插入排序后:5,5,8,9;第二组插入排序后:1,6,7;第三组插入排序后:2,3,4

排序后,小数尽量靠前,大数尽量靠后

过程2:插入排序

相对于只进行插入排序而言,由于预排序已经将数组接近于有序状态,因此预排序后的插入排序执行的会非常快

2.代码

gap取多少合适呢?

gap越大,跳的越快,越不接近有序(例如gap==n)

gap越小,跳的越慢,越接近有序(例如gap==1)

希尔没有给出具体的数字,gap为动态的值最合适,依循环次数而定,常见的有两种情况

gap = gap / 2;

//或

gap = gap / 3 + 1;//Knuth在《计算机程序设计技巧》给出的式子

和之前一样,先写单趟预排序,某次循环后例如gap=3

预排序代码

分组后,aend后一个元素应该间隔gap,因此int tmp = arrend + tmp

cpp 复制代码
	while (end>=0)
	{
		if (tmp < arr[end])
		{
			arr[end + gap] = arr[end];
			end -= gap;
		}
		else
		{
			break;
		}
	}
	arr[end + gap] = tmp;

再将while循环套到for循环中来控制每次循环的end的值

这样预排序就有两种写法:

1.一次排一组(时间复杂度比第二种写法高)

对于上图而言,把绿色组排有序了 后再去排蓝色组,把蓝色组排有序了后再去排紫色组,显然需要三重循环(每组排有序2重循环,控制组一重循环)

cpp 复制代码
int gap = 3;//将arr分为三组预排序
for (int j = 0; j < gap; j++)//j控制组
{
	for (int i = j; (j==0)?i<n:i<n-gap+j; i += gap)//i控制每组的排序
	{
		int end = i - gap;
		int tmp = arr[end + gap];//arr[end+gap]的值已被tmp保存,数组元素的值可以向右覆盖
		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			else
			{
				break;
			}
		}
		arr[end + gap] = tmp;
	}
}

注意到i循环条件的写法:(j == 0) ? i < n : i < n - gap + j; (原因在:int end = i - gap;,i的值跟随end+gap)

对于gap==3的情况,j==0,i最多到n-1;j==1,i最多到n-2;;j==1,i最多到n-1,找规律即可写出上述判断条件

运行结果

预排序后,按分组看每一组都是升序的:{1,4,7,9}、{0,6,7}、{-1,2,3}

其他写法
cpp 复制代码
		int gap = 3;
		for (int j = 0; j < gap; j++)//j控制组
		{
			for (int i = j; i < n - gap; i += gap)//i控制每组的排序
			{
				int end = i;//end的值跟随i
				int tmp = arr[end + gap];//arr[end+gap]的值已被tmp保存,数组元素的值可以向右覆盖
				while (end >= 0)
				{
					if (tmp < arr[end])
					{
						arr[end + gap] = arr[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				arr[end + gap] = tmp;
			}
		}

为什么i的循环条件变简单了呢?因为int end=i;中i的值跟随end(***推荐写法***),不同于之前的写法

2.一次排多组(多组并排)

对于上图而言,预排序可以交替进行:绿色组-->蓝色组-->紫色组-->绿色组-->蓝色组-->紫色组->绿色组-->蓝色组-->紫色组-->......只需要两重循环

cpp 复制代码
	int gap = 3;
	for (int i = gap; i < n; i++)//交替排序,每次i+1
	{
		int end = i - gap;
		int tmp = arr[end + gap];
		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			else
			{
				break;
			}
		}
		arr[end + gap] = tmp;
	}
运行结果

希尔排序代码

由于预排序有整体上有两种写法,因此希尔排序代码也有两种写法

希尔排序的框架(以gap /= 2为例)

cpp 复制代码
void ShellSort(int* arr, int n)
{
	int gap = n;
    while (gap > 1)
	{
		gap /= 2;
        //这里写预排序的代码
    }
}

1.当预排序一次排一组时

以gap /= 2为例,初始gap==n,无论n取何值,最后一次循环gap==1,没有必要调用InsertSort

原因:任何一个数gap/2/2/2/2/2/2/2/2/2/2...的最终结果都是1

即gap>1为预排序,gap==1为直接插入排序

cpp 复制代码
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
	    for (int j = 0; j < gap; j++)//j控制组
		{
			for (int i = j; i < n - gap; i += gap)//i控制每组的排序
			{
				int end = i;//end的值跟随i
				int tmp = arr[end + gap];//arr[end+gap]的值已被tmp保存,数组元素的值可以向右覆盖
				while (end >= 0)
				{
					if (tmp < arr[end])
					{
						arr[end + gap] = arr[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				arr[end + gap] = tmp;
			}
		}
	}
}
运行结果

但写四重循环很麻烦,不推荐这样写

2.当预排序一次排多组时

以gap = gap / 2为例,初始gap==n

cpp 复制代码
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
		for (int i = gap; i < n; i++)//交替排序,每次i+1
		{
			int end = i - gap;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}
运行结果

gap的其他变化方式

gap = gap/3 + 1,对于任何一个数n,其n/3/3/3/3/3/3/3/3/3.....结果不为0前,三种可能:1或2,若为gap = gap/3 + 1保证循环结束后gap==1(2/3+1==1,1/3+1==1,0/3+1==1)

相关推荐
地平线开发者6 小时前
profiler debug 工具用法与高一致性策略
算法·自动驾驶
编程大师哥6 小时前
匿名函数 lambda + 高阶函数
java·python·算法
isyangli_blog6 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
我叫袁小陌7 小时前
算法解题思路指南
算法
地平线开发者7 小时前
Conv+BN+Add+ReLU 融合机制简介
算法·自动驾驶
也曾看到过繁星7 小时前
数据结构---顺序表
数据结构