排序之插入排序

news2024/11/20 15:16:07

文章目录

  • 前言
  • 一、直接插入排序
    • 1、基本思想
    • 2、直接插入排序的代码实现
    • 3、直接插入排序总结
  • 二、希尔排序
    • 1、希尔排序基本思想
    • 2、希尔排序的代码实现
    • 3、希尔排序时间复杂度


前言

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。


一、直接插入排序

1、基本思想

直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
例如当有如下一组有序的数组。当分别将10、5、0按照插入排序的思想插入到数组中时,
在这里插入图片描述
此时插入的数据要与数组最后一位进行比较,如果比最后一位大,就插入到数组最后一位的后面;
在这里插入图片描述
如果比数组中最后一位小,就将数组中最后一位元素向后移一位,然后插入的数据与倒数第二个数据进行比较,就这样依次循环下去 。
在这里插入图片描述
如果插入的数据比数组中所有数据都小,此时插入的数据会被插入到数组下标为0的位置,即作为数组的第一个元素。
在这里插入图片描述
上述的操作就是插入排序的单趟排序,即在有序区间,插入一个数使这个区间继续有序。

2、直接插入排序的代码实现

直接写出来插入排序来将数组排序比较困难,我们可以先写出来单趟排序,即当有一个数组的[0,end]有序,把end+1位置的值插入数组中,然后保持数组的[0,end+1]有序。
例如有如下数组。
在这里插入图片描述
此时数组中end+1的值为5,该值小于数组中end位置的值,此时就用tmp记录end+1位置的值,然后将end的值向后移一位,移到end+1位置。然后将end–,此时再将end位置的值与tmp比较。
在这里插入图片描述
此时end位置的值比tmp小,则说明tmp的值就应该插入到end位置之后,此时直接将end+1位置赋值为tmp即可。然后数组中[0,end+1]有序。
在这里插入图片描述
上述就是单趟排序。

void InsertSort(int* arr, int n)
{
	//[0,end]有序,把end+1位置的值插入,保持有序
	int end;
	//将tmp赋值为数组中下标为end+1位置的值
	int tmp = arr[end + 1];
	//如果end没有到数组头部,就继续比较
	while (end >= 0)
	{
		//如果tmp小于arr[end],就将此时end的数据向后移,然后将end--,使tmp与新的end位置的值比较
		if (tmp < arr[end])
		{
			arr[end + 1] = arr[end];
			end--;
		}
		else
		{
		//如果满足条件就跳出循环
			break;
		}
	}
	//此时end为数组中第一个小于tmp的位置,此时将end+1位置赋值为tmp。
	//如果当end为-1时,即插入的数据tmp比数组头元素还小,此时end+1刚好为0,即让插入的数据tmp作为新的数组头部。
	arr[end + 1] = tmp;
}

通过上述的操作我们明白了插入排序排序数组时的单趟操作,当我们有一个长度为n的数组arr时,我们先将end置为0,然后依次将数组首元素之后的元素依次进行插入,当end为n-2时,此时end+1为n-1,即为数组最后一个元素的插入。我们就可以再写一个循环将上面的代码套入其中,并且控制end的值从0到n-2。
在这里插入图片描述

void InsertSort(int* arr, int n)
{
	int i = 0;
	//控制end的值,将数组中的元素从首元素后面开始依次进行插入。
	for (i = 0; i < n-1; i++)
	{
		//[0,end]有序,把end+1位置的值插入,保持有序
		int end=i;
		//将tmp赋值为数组中下标为end+1位置的值
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			//如果tmp小于arr[end],就将此时end的数据向后移,然后将end--,使tmp与新的end位置的值比较
			if (tmp < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		//此时end为数组中第一个小于tmp的位置,此时将end+1位置赋值为tmp。
		//如果当end为-1时,即插入的数据tmp比数组头元素还小,此时end+1刚好为0,即让插入的数据tmp作为新的数组头部。
		arr[end + 1] = tmp;
	}
	
}

3、直接插入排序总结

直接插入排序的特性总结:
1.元素集合越接近有序,直接插入排序算法的时间效率越高
2.时间复杂度:O(N^2)
3.空间复杂度:O(1),它是一种稳定的排序算法
4.稳定性:稳定
直接插入排序的最坏时间复杂度为O(N^2),即当数组为逆序时。
最优时间复杂度为O(N),即当数组为顺序有序/接近顺序有序。

二、希尔排序

由上面总结的直接插入排序的最优时间复杂度可知,当数组的顺序接近有序时,直接插入排序的性能可以得到提高,并且能达到O(N)的级别,而希尔排序就是利用了这一点。

1、希尔排序基本思想

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后gap=gap/gap,取gap,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。
希尔排序分为两步。
1、预排序(接近顺序有序)
2、直接插入排序(有序)
例如有如下的一组逆序的数据。
在这里插入图片描述
假设gap为3,即将该组数据分为如图所示的三组数据,
在这里插入图片描述
然后将这三组数据分别进行直接插入排序,此时该组数据的排序虽然没有达到顺序有序,但是相比于原来的逆序,此时该组数据已经接近顺序有序。
在这里插入图片描述
此时将该组数据总体进行直接插入排序,这样相比于原来逆序时就直接进行直接插入排序,效率提高了很多。并且我们可以看出当gap=1时,此时就是进行直接插入排序。

2、希尔排序的代码实现

我们可以像实现直接插入排序一样,先了解希尔排序的一组数据中的单趟排序。
即将分成的3组数据的红色组数据先进行直接插入排序。
在这里插入图片描述

此时将end+gap位置的值赋值为end位置的值。
在这里插入图片描述
然后end = end - gap,此时end已经为负数,但是end+gap为0,刚好为数组的首位,所以将arr[end+gap]的位置存入tmp,即将tmp插入到数组的首位。
在这里插入图片描述
下列代码就实现了上述的操作。

void ShellSort(int* arr, int n)
{
	int gap = 3;
	int end = 0;
	//将tmp赋值为数组中end位置后gap位置的值
	int tmp = arr[end + gap];
	while (end >= 0)
	{
		//如果tmp的值小于此时end位置的值
		if (tmp < arr[end])
		{
			//就将end+gap位置的值赋值为end位置的值,
			arr[end + gap] = arr[end];
			//然后将end向后移gap个距离,继续和tmp比较
			end -= gap;
		}
		else
		{
			break;
		}
	}
	//此时end位置的值是小于tmp的,将end+gap位置的值赋值为tmp
	arr[end + gap] = tmp;
}

但是上述代码只是将红色数组中的一个数据完成了直接插入排序,下面我们要将红色数组的数据都进行直接插入排序,则需要加一个循环,然后使end的值每次都向后改变。

void ShellSort(int* arr, int n)
{
	int gap = 3;
	int i = 0;
	for (i = 0; i < n - gap; i += gap)
	{
		int end = i;
		//将tmp赋值为数组中end位置后gap位置的值
		int tmp = arr[end + gap];
		while (end >= 0)
		{
			//如果tmp的值小于此时end位置的值
			if (tmp < arr[end])
			{
				//就将end+gap位置的值赋值为end位置的值,
				arr[end + gap] = arr[end];
				//然后将end向后移gap个距离,继续和tmp比较
				end -= gap;
			}
			else
			{
				break;
			}
		}
		//此时end位置的值是小于tmp的,将end+gap位置的值赋值为tmp
		arr[end + gap] = tmp;
	}
	
}

上述代码只是将红色数组完成了直接插入排序,但是蓝色数组和紫色数组还没有完成直接插入排序,所以需要再次套入一个循环,将蓝色数组和紫色数组也完成直接插入排序操作。此时才算将gap组数都分别在各自组内进行了直接插入排序,此时数组arr中的数据接近顺序有序。

void ShellSort(int* arr, int n)
{
	int gap = 3;
	int i = 0;
	for (i = 0; i < gap; i++)
	{
		int j = 0;
		for (j = i; j < n - gap; j += gap)
		{
			int end = j;
			//将tmp赋值为数组中end位置后gap位置的值
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				//如果tmp的值小于此时end位置的值
				if (tmp < arr[end])
				{
					//就将end+gap位置的值赋值为end位置的值,
					arr[end + gap] = arr[end];
					//然后将end向后移gap个距离,继续和tmp比较
					end -= gap;
				}
				else
				{
					break;
				}
			}
			//此时end位置的值是小于tmp的,将end+gap位置的值赋值为tmp
			arr[end + gap] = tmp;
		}
	}
}

上述代码嵌套了三层循环,会让人觉得循环太多,其实可以将外面的循环去掉,然后将第二层循环的i += gap变为i++。达到的效果和上面的代码一致,上面代码是将gap组数依次进行直接插入排序,而这个代码是将第一组数的一个进行直接插入排序后,然后将第二组数进行直接插入排序,然后将第三组数进行直接插入排序。这样两个代码达到的效果是一样的。

void ShellSort(int* arr, int n)
{
	int gap = 3;

	int j = 0;
	for (j = 0; j < n - gap; j ++)
	{
		int end = j;
		//将tmp赋值为数组中end位置后gap位置的值
		int tmp = arr[end + gap];
		while (end >= 0)
		{
			//如果tmp的值小于此时end位置的值
			if (tmp < arr[end])
			{
				//就将end+gap位置的值赋值为end位置的值,
				arr[end + gap] = arr[end];
				//然后将end向后移gap个距离,继续和tmp比较
				end -= gap;
			}
			else
			{
				break;
			}
		}
		//此时end位置的值是小于tmp的,将end+gap位置的值赋值为tmp
		arr[end + gap] = tmp;
	}
}

到这里只完成了希尔排序的第一个步骤,即预排序,而想要将数组中的数据都有顺序,还需要将数组进行直接插入排序。预排序的目的是让数组中的数据达到基本顺序有序,这样使用直接插入排序对数组排序时效率就会提高。
希尔排序的gap该怎么选择呢?
由上面我们可以直到当排升序时,gap越大,大的数更快到后面,小的数可以更快到前面,但是越不接近有序。
排升序时,gap越小,越接近有序,当gap==1时,就是直接插入排序。
当数组中数据为10000个时,此时如果选择gap=3,则预排序和进行直接排序插入的复杂度差不多。当数组中数据为100个时,此时将gap=30,则经过预排序后数组中的数据还是没有达到基本顺序有序。
此时我们可以将gap设置为一个变化的值。

void ShellSort(int* arr, int n)
{
	int gap = n;
	int j = 0;
	//gap>1时都为预排序,并且随着gap越来越小,数组越来越接近有序
	while (gap > 1)
	{
		//随着gap越来越小,数组中的数据越接近有序,当gap==1时,就是直接插入排序
		gap = gap / 3 + 1;  // gap / 3 + 1保证了最后一次gap=1,即最后一次为直接插入排序
		for (j = 0; j < n - gap; j++)
		{
			int end = j;
			//将tmp赋值为数组中end位置后gap位置的值
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				//如果tmp的值小于此时end位置的值
				if (tmp < arr[end])
				{
					//就将end+gap位置的值赋值为end位置的值,
					arr[end + gap] = arr[end];
					//然后将end向后移gap个距离,继续和tmp比较
					end -= gap;
				}
				else
				{
					break;
				}
			}
			//此时end位置的值是小于tmp的,将end+gap位置的值赋值为tmp
			arr[end + gap] = tmp;
		}
	}
	
}

3、希尔排序时间复杂度

希尔排序的时间复杂度很难求,我们只需记住它的时间复杂度接近于O(N^1.3)。
《数据结构(C语言版)》— 严蔚敏
在这里插入图片描述

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

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

相关文章

【STM32单片机】FATS文件系统,写入字符串到文件,读取文件内容

基于正点原子的库函数版本的FATS文件系统&#xff0c;实现向文件写入字符串&#xff0c;读取文件内容&#xff0c;这里是指txt文件&#xff0c;其余文件其实也一样&#xff0c;读取成字节。 #include "led.h" #include "delay.h" #include "sys.h&qu…

IDEA 性能优化

前言 IDEA 基于JVM&#xff0c;是内存紧张型的应用&#xff0c;即使是16GB内存也很一般。 机器配置&#xff1a; win10 proi7-4720hq 3.2G 4c8tddr3-1600IDEA 2023.2.1 本文优化在不升级硬件的前提下使用 优化 调整JVM堆内存及GC IDEA 自身的JVM运行时配置&#xff0c;启动…

【日积月累】后端刷题日志

刷题日志 说说对Java的理解JAVA中抽象类和接口之间的区别Java中的泛型 和equals()的区别八种基本数据类型与他们的包装类在一个静态方法内调用一个非静态成员为什么是非法的静态方法与实例方法有何不同重载与重写深拷贝浅拷贝面向过程与面向对象成员变量与局部变量Spring框架Sp…

C++-list实现相关细节和问题

前言&#xff1a;C中的最后一个容器就是list&#xff0c;list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指…

机器学习和数据挖掘02-Gaussian Naive Bayes

概念 贝叶斯定理&#xff1a; 贝叶斯定理是概率中的基本定理&#xff0c;描述了如何根据更多证据或信息更新假设的概率。在分类的上下文中&#xff0c;它用于计算给定特征集的类别的后验概率。 特征独立性假设&#xff1a; 高斯朴素贝叶斯中的“朴素”假设是&#xff0c;给定…

库仑定律和场强

1、库伦定律 两个电荷相互作用的力。 力是矢量&#xff0c;有大小和方向。 1.1、力的大小 1.2、力的方向 在两个电荷的连线上&#xff0c;同种电荷相互排斥&#xff0c;异种电荷相互吸引。 真空&#xff0c;不是必要条件&#xff0c;修改公式中介电常数的值仍然满足库伦定律。…

环境安装:rpm安装jdk上线项目

Tomcat安装 解析域名 购买域名并配置 安装Docker yum 卸载以前装过的docker

动力节点Spring (18-19)

⼗⼋、Spring6集成MyBatis3.5 18.1 实现步骤 ● 第⼀步&#xff1a;准备数据库表 ○ 使⽤t_act表&#xff08;账户表&#xff09; ● 第⼆步&#xff1a;IDEA中创建⼀个模块&#xff0c;并引⼊依赖 ○ spring-context ○ spring-jdbc ○ mysql驱动 ○ mybatis ○ myb…

MySQL从入门到精通【进阶篇】之 主从复制详解

文章目录 0.前言1. 主从复制简介2. 主从复制的工作流程主从复制过程中的日志文件作用&#xff08;Binary Log&#xff09;和中继日志&#xff08;Relay Log&#xff09; 3. MySQL主从复制的配置4. 参考资料 0.前言 MySQL的主从复制和读写分离是数据库领域的基本概念&#xff0…

越南“李嘉诚”造车狂飙 吊打许家印

作者&#xff1a;积溪 快评&#xff1a;造车6年&#xff0c;车没卖多少&#xff0c;市值却仅次特斯拉、丰田&#xff0c;成为第三大汽车厂商&#xff0c;造神背后的越南首富潘日旺&#xff0c;究竟有多神奇&#xff1f; 真是 贾跃亭看了会窒息 许家印看了会流泪 同样是造车…

C语言:二级指针简介

二级指针简介 二级指针即为二级指针变量&#xff0c;用于存放一级指针变量的地址。 一级指针变量是用来存放普通变量的地址&#xff08;地址其实就是一些数字&#xff09;&#xff0c;一级指针变量也是一个变量&#xff0c;存放普通变量地址的同时自身也是有地址的。那么一级指…

java八股文面试[多线程]——一个线程两次调用start()方法会出现什么情况

典型回答&#xff1a; Java 的线程是不允许启动两次的&#xff0c;第二次调用必然会抛出 IllegalThreadStateException&#xff0c;这是一种运行时异常&#xff0c;多次调用 start 被认为是编程错误。 通过线程的状态图&#xff0c;在第二次调用 start() 方法的时候&#xff…

docker-compose安装opengauss数据库

文章目录 1. docker-compose.yaml2. 部署3. 卸载4. 连接 1. docker-compose.yaml mkdir -p /root/i/docker-compose/opengauss && cd /root/i/docker-compose/opengausscat <<EOF> /root/i/docker-compose/opengauss/docker-compose.yaml version: 3 service…

在Visual Studio 2017上配置Glut

上篇 已经介绍了如何配置OpenGL&#xff0c;但缺点是每次新建一个项目时&#xff0c;都应重新安装 “nupengl.core.redist” 与 “nupengl.core” 这两个文件&#xff0c;这在有网的情况下还是可以实现的&#xff0c;但不是一个长久之计。现在介绍另一种方法&#xff0c;用Glut…

最新AI系统ChatGPT镜像源码+详细图文搭建教程/支持GPT4.0/AI绘画+MJ绘画/Dall-E2绘画/H5端/Prompt知识库/思维导图生成

一、AI系统 如何搭建部署AI创作ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01;SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、Midjourney绘画&#xf…

数据集格式处理:xml转txt(亲测有用)

1.文件准备及文件夹目录展示 如上图所示: 1.images文件夹里面装的是jpg图片&#xff0c;这个不用管&#xff0c;在xml转换txt格式的时候用不到 2.主要看labels文件夹&#xff0c;这个文件夹里面装的是标签文件 train_annotations、test_annotations文件夹里面装的是xml格式的…

qt第二天

#include "widget.h" #include "ui_widget.h" #include "QDebug" Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(QSize(800,600)); //使用匿名对象&#xff0c;调用重…

LabVIEW对EAST长脉冲等离子体运行的陀螺稳态运行控制

LabVIEW对EAST长脉冲等离子体运行的陀螺稳态运行控制 托卡马克是实现磁约束核聚变最有希望的解决方案之一。电子回旋共振加热&#xff08;ECRH是一种对托卡马克有吸引力的等离子体加热方法&#xff0c;具有耦合效率高&#xff0c;功率沉积定位好等优点。陀螺加速器是ECRH系统中…

leetcode18. 四数之和(java)

四数之和 题目描述nSum 双指针代码演示 上期经典 题目描述 难度 - 中等 原题链接 - 四数之和 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两…

自定义域名访问任意网站

最开始动手是按照这篇博客来的&#xff1a;http://t.csdn.cn/M3wui 但这篇博客只适用于Ubuntu等通过apt命令安装应用的linux系统&#xff0c;如果是用yum方式安装的nginx和apache2&#xff0c;配置文件的位置和名字会不一样。 现在这篇博客的门槛会比上面的链接指向的更简单一些…