”插入排序“”选择排序“

news2025/4/14 22:27:34

文章目录

  • 插入排序
    • 1. 直接插入排序(O(n^2))
      • 举例1:
      • 举例2:
      • 直插排序的"代码"
      • 直插排序的“时间复杂度”
    • 2. 希尔排序(O(n^1.3))
        • 方法一
        • 方法二(时间复杂度更优)
  • 选择排序
    • 堆排序
    • 直接选择排序

我们学过冒泡排序,堆排序等等。(回顾一下:排升序,建大堆;排降序,建小堆。)

排序:所谓排序,就是使⼀串记录,按照其中的某个或某些关键字的⼤⼩,递增或递减的排列起来的
操作。

在生活中也会遇到很多排序:购物筛选排序(按照价格、综合、销量、距离等排序)、百度热搜(根据热搜程度)、院校排名等等。

在这里插入图片描述

插入排序

1. 直接插入排序(O(n^2))

基本思想:将(待排序的记录)按照(关键码值的大小)逐个插入到(已经排好序的有序队列)中。直到所有的记录插入完为止,得到一个新的有序序列。

先将arr[end]指向:(有序队列的最后一个数据),然后将(需要和有序数据进行比较的数据:即arr[end+1])暂时存储在tmp中,接下来将arr[end]和tmp里的数据进行比较。
在这里插入图片描述
注意在一次循环中,tmp的值是不变的(除非end++,才会使tmp的值改变(tmp=arr[end]))

举例1:

【假设想要排升序】
在这里插入图片描述

  • 现在arr[end]的值大于tmp,应该排在后面,所以现在将end的值放在end+1的位置,这个时候会发现end+1位置的值被覆盖了,所以我们需要提前将它的值存储在tmp中(这就是将值存储在tmp中的原因)。接下来需要将tmp的值放在end的位置(这个是arr[end]的前面没有其他值的情况,我们可以直接将tmp的值赋给arr[end])

在交换值之后,将end++,tmp的值为arr[end+1] (它不用修改,因为end已经修改了)
在这里插入图片描述

  • 如果arr[end]<tmp,并不需要交换呢?(比如5和9)那就直接end++,紧接着tmp的值也变啦。

  • 紧接着又是将9和6交换,按照之前的方法(将arr[end]的值赋给arr[end+1],然后将tmp的值赋给arr[end]
    在这里插入图片描述

  • 为什么我们还需要将交换之后的arr[end]的值和这个序列前面的值比较呢?------防止它比前面已经排好序的值小

接下来按照前面的方法,再将9和2进行比较

举例2:

在这里插入图片描述

直插排序的"代码"

我们需要一个循环来控制end++,往后走;
还需要一个循环来控制:当把arr[end]赋值给arr[end+1]之后,需要将end- -,将前面的数值和tmp继续比较。

在这里插入代码片`void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)  //为什么是i<n-1?
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}`

在这里插入图片描述

  • 为什么是i<n-1呢?
    因为tmp存储的是arr[end+1],我们要确保它是最后一个不会越界,即end+1<n,又因为end=i,所以i+1<n,所以i<n-1

直插排序的“时间复杂度”

直接插入排序的时间复杂度是O(n^2)

不过O(n^2)是最差的情况。
在这里插入图片描述

最差:想将降序----->升序 O(n^2)
最好:升序------->升序 O(n)

2. 希尔排序(O(n^1.3))

希尔排序就是对直接插入排序的一种优化。想要改善降序---->升序的时间复杂度。

希尔排序分为两步:1.预排序 2.直接插入排序

我们可以将很长的一段数组序列变为n段较短的序列,然后对每段“单独”进行直接插入排序(这个叫做预排序)。预排结束之后,小的数据大部分都在前半部分了(只是不按顺序),接着我们再对整体的数组(已经趋于有序的数组)序列进行一次直插排序,这样不仅完成了排序,还优化了时间复杂度。

方法一
  • 首先我们需要决定一个值:gap,然后将大段分为n个小段(每段的首和尾相隔gap个元素,即第一个值往后数gap个,那个数据是这段的最后一个元素)。

什么情况下预排:当gap>1的时候
什么情况下整段直插:当gap==1的时候

在这里插入图片描述

  1. 从上图可以知晓:要将一大段数据分为很多组数据,在寻找第二组数据时,需要将上次的首元素下标++。这一步由外层循环控制【遍历每个子序列的起始位置(从 0 到 gap-1)】for(i=0;i<gap;i++)(为什么i<gap?)

  2. 接下来是每组数据中,知道了首元素,然后找这组数据中的剩余数据,即end+gap,这个由第二层循环控制:for(int j=0;j<n-gap;j+=gap);(为什么内层的j<n-gap?)

  3. 再里面的元素之间比较就没有什么特别了,使用的是直接插入排序(它也有循环)。

【gap不能太大(若太大的话,会导致每组的数据比较少,但组数比较多),也不能太小(那样的话每组数据会有很多,那这样的话时间复杂度又变高了)】我们可以让gap由元素个数决定,gap=n/3+1,为什么最后还要+1呢?(如果遇到2/3的情况,那商就是0,也就是gap=0,那间距为0,谁和谁比呢?)而我们最后需要让gap=1,让已经预排之后的整段数据进行直插排序,所以需要+1.

【gap等于多少,这段数据最终就会被分为几组数据】如果n特别特别大,n/3之后也特大,先将+1给忽略掉

  • 之前我们说到为什么外层循环中i<gap而不是i<n呢?
    在这里插入图片描述
    我们第一组数据的第二个元素是arr[end+gap]也就是arr[0+gap],既然arr[gap]在第一组数据中已经进行比较过一次,那之后就没必要再将它比较一次了。大家观察上图会发现每个数据都进行比较过,如果外层用 i < n,会导致重复处理同一子序列,效率降低。

  • 还有一个问题是:为什么第二层循环需要j<n-gap,按照方法一个组中,每个数据它们都相邻gap个(n=8,gap=3第一组元素下标:0,3,6,下标为6的元素后面还有1个元素,但是剩余元素个数不够gap个,那就只能在这次的循环中将后面的元素舍弃。所以在找每组的数据时(即在第二层循环),循环结束的条件是最后一个元素后面的元素个数小于gap个(有剩余的元素或者剩余个数为0(也就是刚好后面没有元素了)),也就是它的下标小于n-gap

在这里插入图片描述

第一组的数据:7,4,1
第二组的数据:6,3
第三组的数据:5,2

  • 然后,先排第一组的数据,再排2,3组的数据

(1)初始情况:arr[end]指向第一个元素,tmp指向(与第一个相隔gap的元素).即tmp=arr[end+gap]

  • arr[end]>tmp,那么将arr[end]的值赋给tmp所指向的那个值,即arr[end+gap]=arr[end],赋值之后,将end=end-gap
  • arr[end]>tmp,不用修改值。直接将end+=gap
    对于每组数据的值比较的注释
    (2)排完第二组的数据之后,外层的i++,到第二个数据【下标为1】(它作为第二组的首元素),然后进入第二层循环,在进行比较。

到此为止,这个进行了一趟比较

void ShellSort(int* arr, int n)
{
	int gap = n / 3 + 1;
	for (int i = 0; i < gap; i++)
	{
		int j = i;
		for (j =i; j < n - gap; j += gap)
		{
			int end = j;
			int tmp = arr[end + gap];
			while (end>=0)  //将这组数据的每个数据比较,while结束的条件:end>=0;
			{

				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					//想继续和前面有序的数据比较,将end-gap
					end -= gap;
				}
				//到这里,arr[end]<tmp,那就不用赋值,直接将下标+gap,tmp的值随之改变,继续比较
				else {
					break;
				}
			}
			//在循环中,我们只是将大的数据往后赋值,那当初那个比较小的tmp赋给谁呢?
			//这组数据如何能比较结束(break)呢?肯定是此时的arr[end]并没有tmp大,不需要交换了
			//所以,我们把tmp值赋值给之前一个比较的值(由于是end-=gap,所以上一个是end+gap)
			arr[end + gap] = tmp;
		}
	}
}

在这里插入图片描述

在这里插入图片描述

之后我们将gap不断缩小,继续比较,直至最后gap==1时,进行最后的,对整段进行直插
在这里插入图片描述

void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap>1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < gap; i++)
		{
			int j = i;
			for (j = i; j < n - gap; j += gap)
			{
				int end = j;
				int tmp = arr[end + gap];
				while (end >= 0)  //将这组数据的每个数据比较,while结束的条件:end>=0;
				{
					printf("%d ", gap);
					if (arr[end] > tmp)
					{
						arr[end + gap] = arr[end];
						//想继续和前面有序的数据比较,将end-gap
						end -= gap;
					}
					//到这里,arr[end]<tmp,那就不用赋值,直接将下标+gap,tmp的值随之改变,继续比较
					else {
						break;
					}
				}
				//在循环中,我们只是将大的数据往后赋值,那当初那个比较小的tmp赋给谁呢?
				//这组数据如何能比较结束(break)呢?肯定是此时的arr[end]并没有tmp大,不需要交换了
				//所以,我们把tmp值赋值给之前一个比较的值(由于是end-=gap,所以上一个是end+gap)
				arr[end + gap] = tmp;
			}
		}
	}
}

注意:为什么gap>1还可以对整段进行直插排序呢?
大家先想一想gap==1的条件:肯定是上一个gap>1因而进入了循环,进行了gap=gap/3+1,而gap/3=0,gap/3+1=1,接下来进行了直插。不会有其他意外。

n=10:
第一次,gap=n=10,大于1,进入循环之后,gap=gap/3+1=4,之后进行排序。
第二次,gap=4,大于1,进入循环,gap=gap/3+1=4/3+1=2,之后进行排序
注意gap=2的时候它仍然大于1 ,进入循环了,然后才进行的gap=gap/3+1,得到了gap=1,进行最后的直插

方法二(时间复杂度更优)

在上面的基础上进行优化:
之前是将一组一组比,现在仍然是相隔gap个的两个元素进行比较,但是当它俩比较完之后,我们不用找arr[0+gap]再隔gap的值。我们直接找arr[0]的下一个:arr[1],再找arr[1+gap],将他俩比较。那到什么时候停止呢?当i<n-gap时。
在这里插入图片描述

void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap>1)
	{
		gap = gap / 3 + 1;
		for (int i=0; i < n - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)  //将这组数据的每个数据比较,while结束的条件:end>=0;
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					//想继续和前面有序的数据比较,将end-gap
					end -= gap;
				}
				//到这里,arr[end]<tmp,那就不用赋值,直接将下标+gap,tmp的值随之改变,继续比较
				else {
					break;
				}
			}
			//在循环中,我们只是将大的数据往后赋值,那当初那个比较小的tmp赋给谁呢?
			//这组数据如何能比较结束(break)呢?肯定是此时的arr[end]并没有tmp大,不需要交换了
			//所以,我们把tmp值赋值给之前一个比较的值(由于是end-=gap,所以上一个是end+gap)
			arr[end + gap] = tmp;
		}
	}
}

在这里插入图片描述
希尔排序的时间复杂度是:O(n^1.3)
在这里插入图片描述

选择排序

堆排序

这是堆排序的文章,点击链接即可跳转

直接选择排序

这个名字中的“选择”已经暴露了它的方法,直接选择排序是从整个数组中(arr[0]到arr[n-1])选出来min或者max的元素,然后放在整个数组的第一个位置。然后再从arr[1]到arr[n-1]选出来min或max,放在第二个位置。【将min/max放在第一个位置,那原本的数据怎么办,所以不是直接将这个值放在第一个位置,而是将两个值交换】

  • 思路:

    1. 先设定两个变量,begin和mini(它俩是下标),begin作为min或者max放置的位置的下标。mini作为遍历数组时此时位置的下标。[需要知道跳出循环的条件,即放的位置<n]
      在这里插入图片描述
  • 但是又一个bug是:每找一次最大/最小值,就需要将数组遍历一遍(和冒泡排序一样费时间O(n^2)),时间复杂度很大,我们进行优化。

在遍历一遍数组的时候,我们可以将min和max同时找出来,将它们放在数组的两端。这样就可以省略一半的时间。【但需要注意的是,这个上面那个的循环结束条件就不一样了,我们不用再遍历后半部分,就是已经放好特定值的那部分】

void SelectSort(int* arr, int n)
{
	int begin=0;
	for (begin = 0; begin < n/2; begin++) 
		//为什么begin<n/2呢?因为begin是前半部分的下标,只用占整个数组的一半
	{
		int maxi = begin;
		int mini = begin;

		//确定了这趟mini要放的位置,接下来这个循环用来遍历找最小值/最大值,用j来遍历
		int j = begin;
		for (j = begin+1; j < n-begin; j++)  //为什么j<n-begin
			//第一遍遍历的时候,需要遍历所有的
			//第二次遍历,就不用遍历最后一个,已经放了特定的值
			//第三次遍历,倒数两个都不用遍历了
		{
			if (arr[j] < arr[mini])
				mini=j;
			if (arr[j] > arr[maxi])
				maxi=j;
		}
		//这一步是什么作用?
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[begin], &arr[mini]);
		Swap(&arr[n - 1 - begin], &arr[maxi]);
	}
}

其中这一步的作用是什么呢?

if (maxi == begin)
{
	maxi = mini;
}

举个例子:
在这里插入图片描述

这个的问题就在于:maxi刚好和begin重合了,而arr[begin]先一步和arr[mini]交换了,所以我们需要在交换之前处理一下:如果它俩重合了,那就先让maxi走到mini的位置。
在这里插入图片描述

if(maxi==begin)
    maxi=mini;

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

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

相关文章

Python深度学习基础——卷积神经网络(CNN)(PyTorch)

CNN原理 从DNN到CNN 卷积层与汇聚 深度神经网络DNN中&#xff0c;相邻层的所有神经元之间都有连接&#xff0c;这叫全连接&#xff1b;卷积神经网络 CNN 中&#xff0c;新增了卷积层&#xff08;Convolution&#xff09;与汇聚&#xff08;Pooling&#xff09;。DNN 的全连接…

MTK7628基于原厂的mtk-openwrt-sdk-20160324-8f8e4f1e.tar.bz2 源代码包,配置成单网口模式的方法

一、配置. 在SDK工程下&#xff0c;运行make kernel_menuconfig&#xff0c;如下图所示&#xff1a; Ralink Module --->选上“One Port Only”&#xff0c;如下图所示&#xff1a; 如果P0网口实现WAN口&#xff0c;就配置成W/LLLL,否则就配置成LLLL/W. 二、修改网口的原代…

艾伦·图灵:计算机科学与人工智能之父

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 艾伦图灵&#xff1a;计算机科学与人工智能之父 一、天才的诞生与早期生涯 1912年6月…

策略模式实现 Bean 注入时怎么知道具体注入的是哪个 Bean?

Autowire Resource 的区别 1.来源不同&#xff1a;其中 Autowire 是 Spring2.5 定义的注解&#xff0c;而 Resource 是 Java 定义的注解 2.依赖查找的顺序不同&#xff1a; 依赖注入的功能&#xff0c;是通过先在 Spring IoC 容器中查找对象&#xff0c;再将对象注入引入到当…

React九案例中

代码下载 地图找房模块 顶部导航栏 封装NavHeader组件实现城市选择&#xff0c;地图找房页面的复用&#xff0c;在 components 目录中创建组件 NavHeader&#xff0c;把之前城市列表写过的样式复制到 NavHeader.scss 下&#xff0c;在该组件中封装 antd-mobile 组件库中的 N…

第一期:[特殊字符] 深入理解MyBatis[特殊字符]从JDBC到MyBatis——持久层开发的转折点[特殊字符]

前言 &#x1f31f; 在软件开发的过程中&#xff0c;持久层&#xff08;或数据访问层&#xff09;是与数据库进行交互的关键部分。早期&#xff0c;开发者通常使用 JDBC&#xff08;Java Database Connectivity&#xff09;来实现与数据库的连接与操作。虽然 JDBC 在一定程度上…

Adobe Photoshop 2025 Mac中文 Ps图像编辑

Adobe Photoshop 2025 Mac中文 Ps图像编辑 一、介绍 Adobe Photoshop 2025 Mac版集成了多种强大的图像编辑、处理和创作功能。①强化了Adobe Sensei AI的应用&#xff0c;通过智能抠图、自动修复、图像生成等功能&#xff0c;用户能够快速而精确地编辑图像。②3D编辑和动画功…

用纯Qt实现GB28181协议/实时视频/云台控制/预置位/录像回放和下载/事件订阅/语音对讲

一、前言 在技术的长河中探索&#xff0c;有些目标一旦确立&#xff0c;便如同璀璨星辰&#xff0c;指引着我们不断前行。早在2014年&#xff0c;我心中就种下了用纯Qt实现GB28181协议的种子&#xff0c;如今回首&#xff0c;一晃十年已逝&#xff0c;好在整体框架和逻辑终于打…

让你方便快捷实现主题色切换(useCssVar)

文章目录 前言一、useCssVar是什么&#xff1f;二、使用步骤1.安装依赖2.实现主题色切换 总结 前言 使用 CSS 变量&#xff08;CSS Custom Properties&#xff09;实现主题色切换是一种高效且易于维护的方法。通过将主题颜色定义为 CSS 变量&#xff0c;你可以轻松地在不同主题…

面试之《websocket》

配置环境 mkdir express cd express npm init npm install express ws// index.js var app require("express")(); var WebSocket require("ws");var wss new WebSocket.Server({ port: 8888 });wss.on(connection, function connection(ws) {ws.on(m…

L36.【LeetCode题解】查找总价格为目标值的两个商品(剑指offer:和为s的两个数字) (双指针思想,内含详细的优化过程)

目录 1.LeetCode题目 2.分析 方法1:暴力枚举(未优化的双指针) 方法2:双指针优化:利用有序数组的单调性 版本1代码 提问:版本1代码有可以优化的空间吗? 版本2代码 提问:版本2代码有可以优化的空间吗? 版本3代码(★推荐★) 3.牛客网题目:和为s的数字 1.LeetCode题目 …

英语学习4.9

cordial 形容词&#xff1a; 热情友好的&#xff0c;诚恳的 表示一个人态度温和、亲切&#xff0c;给人温暖和善的感觉。 令人愉快的&#xff0c;和睦的 形容关系融洽、氛围和谐。 例句​​&#xff1a; The two leaders had a ​​cordial​​ but formal discussion. &am…

MyBatis-Plus 核心功能

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、条件构造器1、核心 Wrapper 类型基础查询示例SQL 查询使用 QueryWrapper 实现查询 更新操作示例场景一&#xff1a;基础更新SQL 查询使用 QueryWrapper 实现更新…

Day22 -php开发01--留言板+知识点(超全局变量 文件包含 数据库操作 第三方插件)

环境要求&#xff1a;php7.0.9 小皮 navicat phpstorm24.1 知识点&#xff1a;会写&#xff08;留言板 留言板后台&#xff09; 超全局变量 三方插件的使用 文件包含 1、开启小皮并利用navicat新建一个数据库 注意&#xff1a;本地的服务mysql关闭后 才可打开小皮。属…

Java工具类-assert断言

我们可能经常在项目的单元测试或者一些源码中看到别人在使用assert关键字&#xff0c;当然也不只是Java语言&#xff0c;很多编程语言也都能看到&#xff0c;我们大概知道断言可以用于测试中条件的校验&#xff0c;但却不经常使用&#xff0c;本文总结了Java中该工具类的使用。…

人工智能、机器学习与深度学习-AI基础Day2

核心概念与技术全景解析 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术飞速发展&#xff0c;逐渐渗透到生活的方方面面。然而&#xff0c;对于许多人来说&#xff0c;AI、机器学习&#xff08;ML&#xff09;、深度学习&#xff08;DL&#xff09;以及生成式人工…

GGML源码逐行调试(上)

目录 前言1. 简述2. 环境配置3. ggml核心概念3.1 gguf3.2 ggml_tensor3.3 ggml_backend_buffer3.4 ggml_context3.5 backend3.6 ggml_cgraph3.7 ggml_gallocr 4. 推理流程整体梳理4.1 时间初始化与参数设置4.2 模型加载与词汇表构建4.3 计算图与内存分配4.4 文本预处理与推理过…

SpringCloud-OpenFeign

前言 1.存在问题 远程调用可以像Autowired一样吗 服务之间的通信⽅式,通常有两种:RPC和HTTP. 在SpringCloud中,默认是使⽤HTTP来进⾏微服务的通信,最常⽤的实现形式有两种&#xff1a; RestTemplate OpenFeign RPC&#xff08;RemoteProcedureCall&#xff09;远程过程调⽤&…

撰写学位论文Word图表目录的自动生成

第一步&#xff1a;为图片和表格添加题注 选中图片或表格 右键点击需要编号的图片或表格&#xff0c;选择 【插入题注】&#xff08;或通过菜单栏 引用 → 插入题注&#xff09;。 设置题注标签 在弹窗中选择 标签&#xff08;如默认有“图”“表”&#xff0c;若无需自定义标…

Web 项目实战:构建属于自己的博客系统

目录 项目效果演示 代码 Gitee 地址 1. 准备工作 1.1 建表 1.2 引入 MyBatis-plus 依赖 1.3 配置数据库连接 1.4 项目架构 2. 实体类准备 - pojo 包 2.1 dataobject 包 2.2 request 包 2.3 response 包 2.3.1 统一响应结果类 - Result 2.3.2 用户登录响应类 2.3.3…