[数据结构基础]排序算法第一弹 -- 直接插入排序和希尔排序

目录

一. 排序的概念及分类 

1.1 排序的概念

1.2 常见的排序算法 

二. 直接插入排序

2.1 直接插入排序的实现逻辑

2.2 直接插入排序的实现代码

2.3 直接插入排序的时间复杂度分析

三. 希尔排序

3.1 希尔排序的实现逻辑

3.2 希尔排序实现代码

3.3 希尔排序的效率测试


一. 排序的概念及分类 

1.1 排序的概念

排序,就是使一串数据,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

1.2 常见的排序算法 

图1.1按照排序算法的思想,将排序分为四大类:插入排序、选择排序、交换排序、归并排序。本文对插入排序的实现思想和代码进行了详细解读,插入排序包括:直接插入排序和希尔排序。

图1.3 常见的排序算法及分类

二. 直接插入排序

前置说明:本文中的排序算法均以排升序为例进行讲解实现。

2.1 直接插入排序的实现逻辑

学习直接插入排序的实现逻辑,首先要学习单趟直接插入排序的实现。即:给定一组数据集arr(已经按要求被排序)和一个待插入的数据x,要将x插入到数据集中,x插入到数据中后,要求新的数组集中的数据按照升序(降序)排列。如:

  • 数据集arr = {1, 3, 5, 6, 6, 8, 10, 12},待插入的数据x = 7。
  • x插入后的数据集后:arr = {1, 3, 5, 6, 6, 7, 8, 10, 12}

直接插入排序的单趟实现逻辑:

  1. 找到数据集中最后一个小于或等于x的数据:从数据集中最后一个数据开始与x进行比较,如果大于x,则将数据集中的这个数据后移一位,当找到比x小的数据或者发现数据集中的所有数据都大于x时停止查找。
  2. 在数据集中插入x:在第一个小于或等于x的数据后面插入x,如果数据集中所有数据都大于x则是在首元素位置插入x。至此,直接插入排序单趟实现完成。
图2.1 单趟直接插入排序的实现逻辑图

明确直接插入排序的单趟实现逻辑后,再将其延伸到对一组数据进行排序。假设给定一个有n个数据的数据arr[n],首先将第一个数据单独视为有序序列,将第二个数据插入有序序列,然后将数组的前两个数据视为有序序列,将第三个数据插入,以此类推,最后将数组中前n-1个数据视为有序序列,将第n个数据插入。如:

  • 对arr[5] = {5, 4, 3, 2, 1}进行升序排列
  • STEP1:将5单独视为有序序列,插入4,此时arr[5] = {4, 5, 3, 2, 1}。
  • STEP2:将4、5视为有序序列,插入3,此时arr[5] = {3, 4, 5, 2, 1}。
  • STEP3:将3、4、5视为有序序列,插入2,此时arr[5] = {2, 3, 4, 5, 1}。
  • STEP4:将2、3、4、5视为有序序列,插入1,此时arr[5] = {1, 2, 3, 4, 5},排序完成。

2.2 直接插入排序的实现代码

typedef int DataType;  //待排序数据的类型

#include<stdio.h>

//直接插入排序函数
//a为指向存储待排序数组首元素的指针,n为待排序数据的个数
void InsertSort(DataType* a, int n)
{
	int i = 0; //循环参数
	for (i = 0; i < n - 1; ++i)
	{
		int end = i;  //已经排好序的数据的最大元素的下标
		DataType x = a[end + 1];  //要插入的数据

		while (end >= 0)
		{
			//如果已排序的数据大于待插入数据x,则将已排序的数据后移
			//找到最靠后的小于或等于x的数据
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}

		//此时end为-1或第一个小于等于x的数据的下标
		a[end + 1] = x;
	}
}

2.3 直接插入排序的时间复杂度分析

  • 当待排序数据逆序时,直接插入排序所需要进行的操作次数最多
  • 对n个数据采用直接插入法进行排序,所需要进行的单趟插入次数为n-1次,在每次单趟插入中,如果有序序列中的数据大于待插入数据,有序序列中的对应数据向后移动,假设有序序列中有m个数据,每次单趟插入最多进行移动操作m次(有序序列中所有数据都大于待插入数据)。
  • 综上,完成一次直接插入排序最多进行的操作次数为:F(N)=\frac{1}{2}N^2-\frac{1}{2}N,根据大O渐进法规则,直接插入排序的时间复杂度为O(N)

三. 希尔排序

3.1 希尔排序的实现逻辑

希尔排序是在直接插入排序的的基础上进行优化得来的,其基本实现逻辑为

  • 将待排序数据分为gap组,先将每组数据进行预排序,使每组数据在预排序后都是升序或降序序列。
  • 不断变化gap的值,一般来说,第一次分组取gap=n/2,n为待排序数据的个数,每次分组排序完成之后,采用gap /= 2对gap进行更新。
  • gap最终会变为1,此时数组中的数据已经非常接近有序,这是再对数据采用直接插入排序的方法进行排序,消耗大大减少。gap=1时,分组排序等价于直接插入排序。

综上:希尔排序就是先对数据进行分组预排序,使数据接近于有序,然后采用直接插入排序的方法对这组接近于有序的数据进行排序,以此来达到减少时间消耗的目的。

图3.1  希尔排序实现逻辑

3.2 希尔排序实现代码

void ShellSort(int* a, int n)
{
	assert(a);

	int i = 0;  //循环参数,表示每组首元素下标
	int j = 0;  //循环参数,表示每组元素中数据的下标

	int gap = n;  //分组间距

	while (gap > 1)  //不断缩小排序间距,最终gap = 1相当于直接插入排序
	{
		gap /= 2;

		for (i = 0; i < gap; ++i)
		{
			//分组排序
			//每组中的相邻数据在数组中的下标相差gap
			for (j = i; j < n - gap; j += gap)
			{
				int end = j;  //已排序的最后一个数据下标
				int x = a[end + gap];  //待插入排序的数据

				while (end >= 0)
				{
					if (a[end] > x)  //数据大于x就后移
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}

				a[end + gap] = x;  //插入x
			}
		}
	}
}

3.3 希尔排序的效率测试

演示代码3.3通过rand函数随机生成100000个整型数据,分别采用直接插入排序和希尔排序对这100000个数据进行排序,通过clock函数,分别记程序运行到100000个随机数生成结束、直接插入排序结束和希尔排序结束的运行时间为end1、end2、end3。这样,end2 - end1的结果就是直接插入排序的运行时间(ms),end3 - end2的结果就是希尔排序的运行时间(ms)。

演示代码3.3:(测试希尔排序效率)

int main()
{
	int n = 100000;
	int* a1 = (int*)malloc(n * sizeof(int));   //数据集1(用于直接插入排序)
	if (a1 == NULL)
	{
		return 1;
	}

	int* a2 = (int*)malloc(n * sizeof(int));   //数据集2(用于希尔排序)
	if (a2 == NULL)
	{
		return 2;
	}

	srand((unsigned int)time(NULL));

	for (int i = 0; i < n; ++i)
	{
		a1[i] = rand() / 1000;
		a2[i] = a1[i];
	}

	int end1 = clock();  //程序运行到生成100000个随机数消耗的时间(ms)

	InsertSort(a1, n);   //直接插入排序
	int end2 = clock();  //程序运行到直接插入排序结束消耗的时间(ms)

	ShellSort(a2, n);  //希尔排序
	int end3 = clock();  //程序运行到希尔排序结束消耗的时间(ms)

	printf("直接插入排序消耗时间:%d\n", end2 - end1);
	printf("希尔排序消耗的时间:%d\n", end3 - end2);

	return 0;
}

  

测试结果表明:

  • 对100000个随机生成的数据进行排序,采用直接插入排序消耗时间3300ms左右,而采用希尔排序仅需9ms,可见希尔排序的效率远高于直接插入排序。
  • 6
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【Shine】光芒

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值