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

预排序代码

分组后,a[end]后一个元素应该间隔gap,因此int tmp = arr[end + 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)

相关推荐
hnjzsyjyj15 分钟前
“高精度算法”思想 → 大数阶乘
数据结构·高精度算法·大数阶乘
白宇横流学长22 分钟前
基于java出租车计价器设计与实现【源码+文档+部署讲解】
java·开发语言
佳心饼干-1 小时前
C语言-09内存管理
c语言·算法
物联网牛七七1 小时前
4、数据结构与算法解析(C语言版)--栈
c语言·栈操作
dbln1 小时前
贪心算法(三)
算法·贪心算法
数据小爬虫@2 小时前
Java爬虫实战:深度解析Lazada商品详情
java·开发语言
songroom2 小时前
Rust: offset祼指针操作
开发语言·算法·rust
code04号2 小时前
C++练习:图论的两种遍历方式
开发语言·c++·图论
煤泥做不到的!4 小时前
挑战一个月基本掌握C++(第十一天)进阶文件,异常处理,动态内存
开发语言·c++
F-2H4 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++