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

news2025/1/16 20:06:02

一. 排序的概念及分类

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,可见希尔排序的效率远高于直接插入排序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGL相关库及其关系概述

目录 1、OpenGL 2. .GLUT 3、Freeglut 4、glew 5、glfw 6、glad 1、OpenGL OpenGL只有框架没有实现&#xff0c;换句话说就是OpenGL只有函数声明没有源文件实现&#xff0c;类似于接口和虚函数。所有的实现是显卡生产商提供。比如NVIDIA或者AMD就要自己实现OpenGL函数内容…

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

文章目录一、项目环境二、生成公钥私钥1.生成私钥2.查看私钥3.生成公钥4.查看公钥三、安装依赖包四、自测加解密1.纯前端自测2.前后端联调①前端登录代码改为&#xff1a;②后端登录接口代码&#xff1a;③验证五、与数据库密码进行对比&#xff08;数据库是进行了bcryptjs加密…

关于非授权访问的逻辑漏洞挖掘

简介 挖洞的时候日常笔记&#xff0c;很多细节都写得不好&#xff0c;师傅们不要介意 开始 获取目标&#xff1a;https://fofa.info/ 在fofa上输入body“后台登录”&#xff0c;可以看到一大堆目标&#xff0c;今天我打算去测试逻辑漏洞 进入目标地址 随意输入用户名和密码&…

Python LC Loan贷款数据集 统计分析 数据挖掘 研究报告

实验代码&#xff1a;https://download.csdn.net/download/Amzmks/87396462 首先读表 将有空值的列和完全相同的列删除 将数值型数据单独挑出来 将数值型数据从string转为float 用方差阈值法筛选特征较为明显的部分数值型数据 将文本型数据单独挑出来 去除所有的可能的头…

计算机存储系统

前言何为存储系统&#xff1f;存储系统是指计算机中由存放程序和数据的各种存储设备、控制部件及管理信息调度的设备&#xff08;硬件&#xff09;和算法&#xff08;软件&#xff09;所组成的系统。为何需要存储系统&#xff1f;信息是人类认知外界的方式&#xff0c;最初的信…

对CAS的理解

CAS的概念&#xff08;compare and swap&#xff09;:比较并交换我们首先要明白&#xff0c;自旋是一个纯用户态的操作&#xff0c;本身并没有进行加锁&#xff0c;所执行的代码&#xff0c;是在并发环境下执行的&#xff0c;没有锁。我们举一个例子来说明自旋加CAS的作用&…

Day05 C++STL入门基础知识三——String容器(上)概念-构造-赋值-拼接-查找-替换【全面深度剖析+例题代码展示】

文章目录1. 基本概念1.1 本质1.2 string与char*的区别1.3 特点2. 构造函数2.1 四种构造2.2 举例展示3. 赋值操作3.1 七种赋值函数原型&#xff08;operator等号赋值/assign成员函数赋值&#xff09;3.2 举例展示4. 拼接操作4.1 七种拼接函数原型4.2 举例展示5. 查找操作5.1 八种…

分布式概念

目录一、什么是分布式二、分布式与集群的关系三、软件架构演变四、RPC是什么1.RPC思想原理2.服务之间的交互可以用两种方式五、分布式思想与基本概念1.高并发2.高可用3.注册中心4.负载均衡5.服务雪崩6.熔断7.限流8.API网关9.服务跟踪10.弹性云一、什么是分布式 《分布式系统原…

Python流程控制语句之循环语句

上一篇&#xff1a;Python流程控制语句之选择语句 文章目录前言一、while 循环二、for 循环1. 进行数值循环2. 遍历字符串三、循环嵌套总结前言 生活中很多问题都无法一次解决&#xff0c;就像谚语所说&#xff1a;罗马不是一天建成的。一些事物必须周而复始地运转才能保证其存…

linux并发控制详解

目录 1.并发控制 1.1.并发概念 1.2.并发问题 2.多CPU核心 3.解决 4.中断屏蔽 5.原子操作 6.自旋锁 7.自旋锁衍生读写自旋锁 7.1.自旋锁与读写自旋锁的对比&#xff1a; 8.读写自旋锁衍生顺序锁 9.RCU 10.信号量 11.互斥体&#xff08;互斥锁&#xff09; 11.1.互…

2023起点上,一段迷茫的自我倾诉

大家新年快乐。 回顾记忆中渐渐远去的2022。 我曾想象随着一年过去我就能取得很大的进步&#xff0c;“彻底”改变自己的生活状态。其实不过幻想罢了&#xff0c;人才不会无缘无故进步呢。 我曾一度沉溺于网络世界中的关注&#xff0c;想象着自己将可以取得一些成就&#xff…

【每日一道智力题】三个火枪手(快来看人生哲理)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大的激励…

Qt 之 QSystemTrayIcon

文章目录一、QSystemTrayIcon是什么二、属性三、公共类型四、信号提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、QSystemTrayIcon是什么 QSystemTrayIcon类为应用程序在系统托盘中提供一个图标。 如下图&#xff1a; 现代操作系统通常在桌面上提…

“深度学习”学习日记。与学习有关的技巧--Bacth Normalization

2023.1.25 现在已经学习过了&#xff0c;如果我们设置了合适的权重初始值&#xff0c;则各层的激活值分布会呈现适当的广度&#xff0c;从而可以时神经网络模型顺利的进行学习。 而 batch normalization算法 的思想就是为了使得各层有适当的广度&#xff0c;“强制性”地调整…

No package ‘vips‘ found系列问题解决方案

目录 系列报错集合 错误1 错误2 错误3 解决方案清单 系列报错集合 错误1 No package vips found Package vips was not found in the pkg-config search path. Perhaps you should add the directory containing vips.pc to the PKG_CONFIG_PATH environment variable N…

医疗实体及关系识别挑战赛

赛题概要 请本赛题排行榜前10的队友通过作品提交源代码&#xff0c;模型以及说明文档&#xff0c;截止时间为09/27/23:59:59.若文件过大&#xff0c;可发送至官网邮箱&#xff1a;AICompetitioniflytek.com。若截止时间内未提交&#xff0c;官方回通过电话联系相关选手&#x…

JavaEE8-Bean的生命周期

目录 1.Bean执行原理分析 2.Bean生命周期 2.1.实例化Bean&#xff1a;为Bean分配内存空间。&#xff08;相当于买房&#xff0c;从无到有&#xff09; 2.2.设置属性&#xff1a;Bean注入和装配。&#xff08;执行依赖类的注入&#xff1a;A需要使用B的方法&#xff0c;先初…

win32com操作word API精讲 第六集 Range(四)对齐和缩进

本课程《win32com操作word API精讲&项目实战》同步在B站、今日头条、视频号及本公众号发布。其中本平台以发布文字教程为主&#xff0c;所有平台ID均为&#xff1a;一灯编程 今天是大年初二&#xff0c;一灯在此祝愿各位朋友兔年吉祥&#xff0c;达成所想。 本节课主要讲解…

机器学习(六):模型评估

文章目录 模型评估 一、分类模型评估 二、 回归模型评估 三、拟合 1、欠拟合 2、过拟合 模型评估 模型评估是模型开发过程不可或缺的一部分。它有助于发现表达数据的最佳模型和所选模型将来工作的性能如何。 按照数据集的目标值不同&#xff0c;可以把模型评估分为分类…

Python信用卡欺诈检测 [TensorFlow]

Python信用卡欺诈检测 [TensorFlow] 提示&#xff1a;前言 Python 信用卡欺诈检测 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录Python信用卡欺诈检测 [TensorFlow]前言一、导入包二、加载数据三、加载数据四、 …