数据结构之排序(上)

news2024/11/27 4:30:00

片头

嗨,小伙伴们,大家好!我们今天来学习数据结构之排序(上),今天我们先讲一讲3个排序,分别是直接插入排序、冒泡排序以及希尔排序。

1. 排序的概念及其应用

1.1 排序的概念

排序:什么是排序呢?排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。


一、插入排序

2.1.1 基本思想:

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。(简便记忆:将待排序的元素一个个插入到一个已经排好序的有序序列中,直到整个数列都有序为止)

emmm,听上去有点迷迷糊糊,我们来举个例子~

直接插入排序,大家平时都玩过,我们玩扑克牌时,最好让我们的牌按照从小到大的顺序排列,排好序之后,我们就方便出牌。

摸了一张牌后,怎么保证手里的牌是有序的?手里的牌本身是有序的,再摸一张牌,摸完后,插入到相应位置,继续保持手里的牌有序。

插入排序的思想:已经有一个有序序列,再摸一张牌起来,然后把它插入到合适的位置,保持它们继续有序。

数组的本质:最开始把第一个数当作是有序的,把后一个数(第二个数)往前插入;前2个数有序,第3个数插入;前3个数有序,第4个插入,依次类推......

往前怎么插入呢?如果插入的元素比前一个元素小,就将前一个元素往后挪动;如果插入的元素比前一个大,就放到前一个元素的后面。

当 前n-1个数有序,(第n个元素)最后一个元素插入,数组排序完成。

2.1.2 直接插入排序

当插入第 i (i>=1)个元素时,前面的array[0],array[1],....,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],...的排序吗顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

动图演示:

那我们要怎么实现直接插入排序呢?

 排序这个部分,按2个步骤去完成:

①单趟 ②整体

单趟插入排序:

思想:把一个数往前面的有序区间插入,必须确保[0,end]区间是有序的。

比如,我在[0,end]这个区间是有序的,我要把end+1这个位置的值往[0,end]这个区间进行插入。

我们先假设arr数组里面已经存放了  "1" ,  "3" ,  "5" ,现在我们想要存放"7"

如果我们想要插入"2",该怎么做呢?

完整过程如下:

此时,"2"比"1"大,直接将"2"放到"1"的后面即可,换句话来说,将temp里面的值放入arr数组的end+1位置即可。

如果我们想要插入"0",该怎么做呢?

很简单,思路和刚才基本一致,我们一起来画一画图~

当执行到最后一次的时候,"0"比"1"小,因此将"1"往后挪动一位,同时end--,然后将"0"这个元素,也就是temp保存的值插入到 end+1 的位置,数组排序完成。

单趟插入排序的代码如下:

//直接插入排序
void InsertSort(int* a, int n) {
	//单趟
	int end;
	int temp = a[end + 1];        //将end+1位置的值保存到temp变量里面
	while (end >= 0) {    
		if (temp < a[end]) {      //如果temp的值比end位置的值小
			a[end + 1] = a[end];  //将end位置的值往后挪动
			end--;                //end继续找前一个元素
		}
		else {
			break;                //如果temp的值比end位置的值大,或者两者相等,跳出循环
		}
	}
	//跳出循环有2种情况:
	//①temp的值比end位置的值大或者两者相同,直接将temp的值放到end位置的后面(end+1位置)
	//②temp的值比所有的值都小,循环结束,此时end为-1
	//那我还是要将temp放到end后面, -1 + 1 = 0, 放到下标为0的位置

	a[end + 1] = temp;
}

单趟的插入排序我们已经知道了,那么整体的插入排序怎么写呢? 

我们可以发现,无论是上面2种情况的哪一种,插入的元素都是放到end的后面,也就是end+1的位置。基于这样一个原因,我们可以:

① 定义一个变量temp,把end+1位置的值保存起来

② 最坏的情况下,end<0即循环结束(end == -1,已经超出了数组的范围),就是插入的值比所有的数都小

③跳出循环有2种情况:

  • temp的值比end位置的值大或者两者相等,直接将temp的值放到end位置后面(end+1位置)
  • temp的值比所有的值都小,循环结束,此时end为-1。那我还是要将temp的值放到end位置的后面(end+1位置), -1 + 1 = 0,将temp的值放到下标为0的位置。

 综上,  ①end起始位置是0,默认[0,0]区间的元素是有序的。此时,end == 0,下标为0的元素当作是有序的,后一个元素向前插入;end == 1,前2个元素有序了,再把第3个元素往前插入;end == 2,前3个元素有序了,再把第4个元素往前插入,以此类推....... 当 end == n-2, 前n-1个数已经有序,第n个数往前插入,整个数组有序。

因此,我们可以定义一个变量j,  j 的范围在[0,n-2] , j的最后一个位置的值为 n-2 ,也就是说 j < n-1

2.1.3 直接插入排序的代码:
//直接插入排序
void InsertSort(int* a, int n) {
//end起始位置是0,初始时,[0,0]区间的元素是有序的
//end = 0,下标为0的元素当做是有序的,后一个元素往前插入
//end = 1,前2个元素有序了,再把第3个元素往前插入
//end = 2,前3个元素有序了,再把第4个元素往前插入
//.....
//end = n-2,当前n-1个元素已经有序了,将第n个元素往前插入,整个数组有序
	//j的范围[0,n-2], j == n-2 --> j < n-1
	for (int j = 0; j < n - 1; j++) {
		int end = j;
	//定义一个变量temp,把temp+1位置的值保存起来
		int temp = a[end + 1];	
	//最坏的情况: end < 0即循环结束(end == -1,已经超出了数组的范围)
	//插入的值比数组中所有的值都小
		while (end >= 0) {
	//如果temp的值比end位置的值小,将end位置的值向后挪动
			if (temp < a[end]) {
				a[end + 1] = a[end];
				end--;
			}
	//如果temp的值比end位置的值大或者两者相同,跳出循环
			else {
				break;
			}
		}
	//跳出循环有2种情况:
	//①temp的值比end位置的值大或者两者相同,直接将temp的值放到end位置的后面(end+1位置)
	//②temp的值比所有的值都小,循环结束,此时end为-1
	//那我还是要将temp放到end后面, -1 + 1 = 0, 放到下标为0的位置
	
		a[end + 1] = temp;
	}
}

二、冒泡排序

  2.2.1 基本思想

 冒泡排序是一种基本的排序算法,通过重复地比较相邻的两个元素,并且交换位置,将最大的元素逐渐"冒泡"到最后面。冒泡排序的思想是重复地遍历待排序的元素,每次遍历比较相邻的两个元素,如果他们的顺序错误就交换位置,直到没有需要交换的元素为止。

   具体实现时,首先从数组的第一个元素开始,与相邻的元素进行比较,如果顺序错误就交换位置,然后继续比较相邻的下一对元素,一直到数组的最后一个元素。这样一次遍历后,最大的元素就会"冒泡"到最后面。然后再从第一个元素开始,重复上述操作,直到整个数组都排好序。

动图展示:

单趟冒泡排序的思想: 把最大的数换到最后

如果i从1开始,i的最后一个位置为 n-1, 将前一个元素和当前元素进行比较,也就是将 a[i-1] 和 a[i] 进行比较,如果前一个元素比当前元素大,则交换。当 i == n,循环结束

如果i从0开始,i的最后一个位置为 n-2, 将当前元素和后一个元素进行比较,也就是将 a[i] 和 a[i+1] 进行比较, 如果当前元素比后一个元素大,则交换。当 i == n-1,循环结束

单趟冒泡排序的代码:

//2个数进行交换
void Swap(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//冒泡排序
void BubbleSort(int* a, int n) {
	//单趟
	//如果i从1开始,i的结束位置在n-1
	//将前一个元素a[i-1]和当前元素a[i]进行比较,如果前一个比当前元素大,进行交换
	//如果i从0开始,i的结束位置在n-2
	//将当前元素a[i]和后一个元素a[i+1]进行比较,如果当前元素比后一个元素大,进行交换
	for (int i = 1; i < n; i++) {
		if (a[i - 1] > a[i]) {
			Swap(&a[i - 1], &a[i]);
		}
	}
}

我们已经知道了单趟的冒泡排序是如何实现的,那么整体的冒泡排序怎么做呢? 

第一次冒泡,冒泡完毕后,结束位置在下标为 n-1 的位置(i < n);第二次冒泡,结束位置在下标为 n-2 的位置(i < n-1);第三次冒泡,结束位置在下标为 n-3 的位置(i < n-2);第四次冒泡,结束位置在下标为 n-4 的位置 (i < n-3)....... 当 i == 1 ( i < n - (n-2) )时,冒泡结束。

 2.2.2 冒泡排序的代码:
//2个数进行交换
void Swap(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//冒泡排序
void BubbleSort(int* a, int n) {
	//第一次冒泡,冒泡完毕后,结束位置在 n-1 --> i<n
	//第二次冒泡,结束位置在 n-2 --> i<n-1
	//第三次冒泡,结束位置在 n-3 --> i<n-2
	//第四次冒泡,结束位置在 n-4 --> i<n-3
	//....
	//当 i==1 时,冒泡结束。
	// i == 1 --> i == n-(n-1) --> i<2 --> i< n-(n-2)
	// j的范围:[0,n-2], j == n-2 --> j<n-1
	for (int j = 0; j < n - 1; j++) {
		for (int i = 1; i < n-j; i++) {
			if (a[i - 1] > a[i]) {
				Swap(&a[i - 1], &a[i]);
			}
		}
	}
}
算法优化:

如果遍历一遍数组后没有发生任何元素交换,说明每一个数,前一个都小于后一个,此时数组已经有序,排序就可以结束了。

优化过的代码如下:

//2个数进行交换
void Swap(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//升级版冒泡排序
void BubbleSort1(int* a, int n) {
	//第一次冒泡,冒泡完毕后,结束位置在 n-1 --> i<n
	//第二次冒泡,结束位置在 n-2 --> i<n-1
	//第三次冒泡,结束位置在 n-3 --> i<n-2
	//第四次冒泡,结束位置在 n-4 --> i<n-3
	//....
	//当 i==1 时,冒泡结束。
	// i == 1 --> i == n-(n-1) --> i<2 --> i< n-(n-2)
	// j的范围:[0,n-2], j == n-2 --> j<n-1
	for (int j = 0; j < n - 1; j++) {
	//定义变量flag,假设此时数组是有序的
		int flag = 1;	
		for (int i = 1; i < n - j; i++) {
			if (a[i - 1] > a[i]) {
				Swap(&a[i - 1], &a[i]);
	//如果发生了交换,说明数组此时是无序的,flag为0
				flag = 0;
			}
		}
	//如果没有发生交换,说明数组已经有序
	//不需要进行比较了直接跳出循环
		if (flag == 1)
			break;
	}
}

三、希尔排序(最小增量排序)

希尔排序又称为缩小增量排序。它也是插入排序的一种,由希尔于1959年提出。

2.3.1 基本思想

希尔排序的基本思想是将待排序的元素分成几个子序列进行排序,通过逐步缩小子序列的间隔,最终使整个序列变为有序,具体步骤如下:

(1)首先确定一个增量gap,通常为数组的一半,然后将数组分成gap个子序列。

(2)分别对这些子序列进行插入排序,即对每个子序列进行直接插入排序,这样每个子序列都是部分有序的。

(3)再次选择一个较小的增量gap,重复步骤(2),直到gap为1。

(4)最后进行一次增量gap为1的插入排序,完成排序。

动图演示:

 希尔排序的思想:改革直接插入排序,有什么方法能让数组接近有序呢?

我直接再来一趟插入排序。把排序分成2个部分,第1个部分: 预排序。预排序的目标是:让数组接近有序;第2个部分:插入排序,目标是:让整个数组有序。

什么是预排序呢?

预排序是指:分组插入排序。目标:大的数更快换到后面的位置,小的数更快换到前面的位置

 gap给多少的问题:

①gap越大,数据跳得越快,大的数更快换到后面位置,小的数更快换到前面位置,但是越不接近有序

②gap越小,数据跳得越慢,但是越接近有序,当gap == 1时,插入元素后就是有序。

2.3.2 希尔排序的代码:
//希尔排序
void ShellSort(int* a, int n) {
	int gap = 3;
	for(int j = 0; j < gap; j++) {
		for (int i = j; i < n - gap; i += gap) {
			int end = i;
			int temp = a[end + gap];
			while (end >= 0) {
				if (temp < a[end]) {
					a[end + gap] = a[end];
					end = end - gap;
				}
				else {
					break;
				}
			}
			a[end + gap] = temp;
		}
	}
}

算法优化:

//希尔排序
void ShellSort(int* a, int n) {
	int gap = n;
    while(gap>1){
        gap = gap / 3 + 1;
    for (int i = 0; i < n - gap; i++ ) {
			int end = i;
			int temp = a[end + gap];
			while (end >= 0) {
				if (temp < a[end]) {
					a[end + gap] = a[end];
					end = end - gap;
				}
				else {
					break;
				}
			}
			a[end + gap] = temp;
		}
	}
}
	

希尔排序特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap>1时都是预排序,目的是让数组更接近有序。当gap==1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

希尔排序的时间复杂度不好计算,因为gap取值的方法很多,导致很难去计算,因此在很多书中给出的希尔排序的时间复杂度都不固定。


片尾

今天我们学习了3个排序,分别是直接插入排序,冒泡排序以及希尔排序,希望能对看完文章的友友们有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!

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

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

相关文章

R语言数据分析案例-股票可视化分析

一、数据整合的对象 # Loading necessary libraries library(readxl) library(dplyr)# Reading the data from Excel files data_1 <- read_excel("云南白药.xlsx") data_2 <- read_excel("冰山.xlsx")二、数据整合的代码 # Reading the data from…

Docker:docker在项目中常用的一些命令

简介   Docker 是一个开源的容器化平台&#xff0c;它允许开发者将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;并发布到任何安装了 Docker 引擎的机器上。这些容器是轻量级的&#xff0c;包含了应用程序运行所需的所有东西&#xff0c;如代码、系统库、系统工具…

“数字化叙事的革命:人工智能驱动的创意工具的崛起”

近年来&#xff0c;人工智能 (AI) 改变了我们生活的许多方面&#xff0c;数字故事讲述的世界也不例外。随着人工智能驱动的创意工具的出现&#xff0c;广告商、内容创作者和专业人士现在配备了创新的解决方案来简化他们的工作流程&#xff0c;增强他们的创意输出&#xff0c;并…

08.4.grafana自定义图形并直接数据库取值

grafana自定义图形并直接数据库取值 自定义添加油表图形 选择gauge图形&#xff0c;并且配置对应设定值&#xff0c;点击应用 如图所示&#xff0c;可以看到仪表盘上的值是zabbix上取得值 配置grafana直接数据库取值 添加mysql数据源 添加后进行配置&#xff0c;我这…

二分判定+选插冒排序+归并快速堆希尔+计数排序

二分力扣题 一&#xff1a;搜索二维矩阵 74. 搜索二维矩阵 按照题意&#xff1a;直接利用二维数组转换成一维数组进行求解 方法一&#xff1a;普通等于的二分查找 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {t…

websevere服务器从零搭建到上线(三)|IO多路复用小总结和服务器的基础框架

文章目录 epollselect和poll的优缺点epoll的原理以及优势epoll 好的网络服务器设计Reactor模型图解Reactor muduo库的Multiple Reactors模型 epoll select和poll的优缺点 1、单个进程能够监视的文件描述符的数量存在最大限制&#xff0c;通常是1024&#xff0c;当然可以更改数…

STM32快速入门(定时器之输入捕获)

STM32快速入门&#xff08;定时器之输入捕获&#xff09; 前言 本节主要讲解STM32利用通用定时器&#xff0c;在输入引脚出现指定电平跳变时&#xff0c;将CNT的值锁存到CCR寄存器当中&#xff0c;从而计算PWM波形的频率、占空比、脉冲间隔、电平持续时间等。其功能的应用有&…

免费思维13招之七:空间型思维

免费思维13招之七:空间型思维 本篇给你带来的是空间型思维。 空间型思维,具体分为内部空间型思维和外部空间型思维。 什么叫内部空间型思维呢? 内部空间型就是充分利用现有空间或资源为社会提供免费服务,积累人气,增加流量,从而带动消费。 为什么你生意不好?为什么你…

ubuntu中的docker记录(5)——如何使用阿里云的镜像加速配置docker镜像加速器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、镜像加速器介绍1. 什么是docker镜像加速器&#xff1f;2. 为什么要配置镜像加速器&#xff1f; 二、配置镜像加速器1. 注册阿里云账号2. 注册镜像容器服务3…

C++ int 学习

在C语言中 & 是取地址符号&#xff1b; 在C中有 int& 这样的&#xff0c;这里的&不是取地址符号&#xff0c;而是引用符号&#xff1b; 引用是C对C的一个补充&#xff1b; 变量的引用就是变量的别名&#xff0c;讲的通俗一点就是另外一个名字&#xff1b; a的值…

代码随想录算法训练营第二十七天| LeetCode39. 组合总和、LeetCode40.组合总和II、LeetCode131.分割回文串

#LeetCode 39. Combination Sum #LeetCode 39. 视频讲解&#xff1a;带你学透回溯算法-组合总和&#xff08;对应「leetcode」力扣题目&#xff1a;39.组合总和&#xff09;| 回溯法精讲&#xff01;_哔哩哔哩_bilibili 当建立树的结构的时候&#xff0c;target 可以限制树的深…

Spring Boot 调用外部接口的几种方式

Spring Boot 调用外部接口的几种方式 在微服务架构中&#xff0c;服务间的调用是不可或缺的环节。Spring Boot 为开发者提供了多种方式来实现这一任务&#xff0c;这个文章将为你详细介绍这些方式。 一、使用RestTemplate RestTemplate是 Spring Boot 早期版本中常用的 REST 客…

基于 Spring Boot 博客系统开发(八)

基于 Spring Boot 博客系统开发&#xff08;八&#xff09; 本系统是简易的个人博客系统开发&#xff0c;为了更加熟练地掌握 SprIng Boot 框架及相关技术的使用。&#x1f33f;&#x1f33f;&#x1f33f; 基于 Spring Boot 博客系统开发&#xff08;七&#xff09;&#x1f…

HCIP-Datacom-ARST自选题库_06_排障【28道题】

一、单选题 1.如果面对复杂的网络故障&#xff0c;并经过评估认为短时间内无法完成排障&#xff0c;而此时用户又急需恢复网络的可用性&#xff0c;那么正确的做法是? 告诉用户这是不可能实现的 不通知客户的情况下&#xff0c;直接搭建替代的网络环境 始终尝试排除故障&a…

【Spring】验证 @ServerEndpoint 的类成员变量线程安全

文章目录 前言猜想来源验证方法Controller 的情况ServerEndpoint 的情况 后记 前言 最近有 websocket 的需求。探索 ServerEndpoint 的类成员变量特点。 这里类比 Controller 讨论 ServerEndpoint 类成员变量是否线程安全。 猜想来源 网上的教程大多数都这么展示程序&#…

5.10.6 用于乳腺癌超声图像分类的Vision Transformer

医学超声&#xff08;US&#xff09;成像由于其易用性、低成本和安全性已成为乳腺癌成像的主要方式。卷积神经网络&#xff08;CNN&#xff09;有限的局部感受野限制了他们学习全局上下文信息的能力。利用 ViT 对使用不同增强策略的乳房 US 图像进行分类。 卷积神经网络&#…

LeetCode题练习与总结:二叉树的中序遍历--94

一、题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;roo…

C++八股(面试题、手撕题)自用版

目录 面试题&#xff1a; 1. define inline 在编译的哪个阶段 2. const static 3. 子函数返回结构体有什么问题&#xff0c;返回对象调用了哪些函数 4. volatile关键字 5. 编译器基本原理 6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的 7. 数组和指针&a…

【HCIP学习】BGP对等体组、聚合、路由反射器、联盟、团体属性

一、大规模BGP网络所遇到的问题 BGP对等体众多&#xff0c;配置繁琐&#xff0c;维护管理难度大 BGP路由表庞大&#xff0c;对设备性能提出挑战 IBGP全连接&#xff0c;应用和管理BGP难度增加&#xff0c;邻居数量过多 路由变化频繁&#xff0c;导致路由更新频繁 二、解决大…

【python量化交易】qteasy使用教程07——创建更加复杂的自定义交易策略

创建更加复杂的自定义交易策略 使用交易策略类&#xff0c;创建更复杂的自定义策略开始前的准备工作本节的目标继承Strategy类&#xff0c;创建一个复杂的多因子选股策略策略和回测参数配置&#xff0c;并开始回测 本节回顾 使用交易策略类&#xff0c;创建更复杂的自定义策略 …