【数据结构】图解八大排序(上)

news2025/1/19 10:53:15

文章目录

  • 一、排序简介
  • 二、直接插入排序
  • 三、希尔排序
  • 四、直接选择排序
  • 五、堆排序
  • 六、冒泡排序
  • 七、冒泡排序与直接插入排序效率对比

一、排序简介

生活中,我们经常能看到排序的应用。例如,我们在网购商品的时候,经常按销量从高到低排序。
在这里插入图片描述那么这些排序是如何实现的呢?
我们来看看常见的排序算法有哪些:
在这里插入图片描述
先来介绍一下关于排序算法的几个概念。
稳定性:相等的元素排序之后相对次序不变
内部排序:数据全在内存中的排序
外部排序:数据太多不能同时在内存中

关于排序算法的代码实现,建议先写单趟,这样较为简单。

下面所有排序算法都以排升序为例

二、直接插入排序

直接插入排序类似我们平时玩扑克牌的洗牌过程
在这里插入图片描述
基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

简单来说,就是从拿到第二张牌开始,开始逐个往前比对,按大小关系插到比对过程中的某张牌的前面或者后面,插完第二张牌后再按相同的比对过程插第三张,以此类推。

如图中,我们手上已经洗好了2、4、5、10四张牌。
再拿到一张7,先跟10比,比10小,所以再往前比对。
跟5比,比5大,所以刚好就把7插到5和10之间。
这样我们手上的牌就从四张有序的牌变成五张有序的牌了。

我们来看一个动图:
在这里插入图片描述
该动图完整展现了直接插入排序的过程。
在代码实现中,我们把牌插到哪个位置,这个位置后面的所有元素就要后挪一位。而后挪会覆盖我们拿出来的那张牌原本位置的元素,所以应该先保存待插元素的值。
完整代码如下:

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		//单趟
		int end = i - 1;
		//tmp保存要插入排序的值
		int tmp = a[i];//a[0]是最小有序区间,从a[1]开始排序
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				//后挪
				a[end + 1] = a[end];
				//往前走
				end--;
			}
			else
			{
				break;
			}
		}
		//出循环两种可能,break或者end=-1
		a[end + 1] = tmp;//这两种无一例外都要执行这一句
	}
	//时间复杂度
	//最坏情况  O(N^2)
	//最好情况  O(N)
	//空间复杂度O(1)
}

因此,我们可以发现:

  1. 一开始越接近有序,直接插入排序的效率就越高。
  2. 直接插入排序后,相等元素的相对次序是不变的,故直接插入排序具有稳定性。

但是从时间复杂度O(N^2)可以看出来,直接插入排序的效率其实并不高。下面介绍直接插入排序的优化版本,也就是希尔排序。

三、希尔排序

希尔排序,又称缩小增量排序。
上文提到,对于直接插入排序,一开始越接近有序,排序的效率越高。
所以,希尔排序的优化方式就是先让数组接近有序。

希尔排序包括两个过程:预排序直接插入排序

  1. 预排序:让数组先接近有序。
    按照间隔gap分组,进行直接插入排序,可以一组排完再排另一组,也可以多组一起排。
  2. 直接插入排序
    在这里插入图片描述
    在代码实现中,预排序其实就是gap>1时进行排序的过程,直接插入排序也就是gap==1时的排序过程。
    完整代码如下:
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//也可以gap /= 2;
		gap = gap / 3 + 1;//除2可以保证最后一次是1,除3不行所以要加1

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[i + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

希尔排序是对直接插入排序的优化,但是其具体效率很难计算。
我们暂且认为时间复杂度大约在O(N^1.3),慢于O(NlogN)

另外很容易发现,在预排序分组插排的过程中,相等元素的相对次序可能会发生变化,所以希尔排序是不稳定的。

四、直接选择排序

直接选择排序的思想其实非常简单,以升序为例,就是每次选出最小的换到左边或者最大的换到右边
在这里插入图片描述
很容易发现,直接选择排序的效率较低,所以我们在代码实现的时候采用一次遍历同时找到最大、最小换到右边、左边的方案。
完整代码如下:

void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;

	while (left < right)
	{
		//选出最小的和最大的,得到下标
		int mini = left, maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}

		//最小放左边,最大放右边
		swap(&a[left], &a[mini]);
		
		//如果left和maxi重叠,交换后修正一下
		if (left == maxi)
		{
			maxi = mini;
		}
		swap(&a[right], &a[maxi]);

		left++;
		right--;
	}
}

不难看出,直接选择排序的时间复杂度最好最坏都是O(N^2),效率较低,所以实际中很少使用直接选择排序。
另外也很容易看出,直接选择排序的空间复杂度是O(1)

而且,由于每次遍历存在交换到最左边或者最右边的过程,所以排序之后相等元素的相对次序可能会发生变化。
因此,直接选择排序是不稳定的。

上文提到,直接选择排序的效率不高,所以很少使用。
事实上,如果改用堆的结构来选择数据,情况就会不一样。

五、堆排序

堆排序是选择排序的一种,通过堆来进行选择数据。
需要注意的是:排升序要建大堆,排降序建小堆
具体过程如下:
在这里插入图片描述
完整代码如下:

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 选出左右孩子中大的那一个
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	// 建堆 -- 向下调整建堆 -- O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);

		--end;
	}
}

堆排序的时间复杂度是O(NlogN),相比直接选择排序就快了很多。
空间复杂度依然是O(1)
观察堆排序的过程,显然,堆排序不具有稳定性。

六、冒泡排序

冒泡排序我们都很熟悉了,就是每趟都比较相邻两个元素大小,如果不满足升序或降序则交换位置,总共比较n-1趟。
这里直接上动图和代码,相信大家都很容易能理解。
在这里插入图片描述

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)// n-1 趟
	{
		bool exchange = false;//检查是否交换
		for (int j = 0; j < n - 1 - i; j++)// 每趟比较 n-1-i 次
		{
			if (a[j] > a[j + 1])
			{
				swap(&a[j], &a[j + 1]);
				exchange = true;
			}
		}

		if (exchange == false)//如果没交换,说明已经有序
		{
			break;
		}
	}
	//时间复杂度
	//最坏:O(N^2)
	//最好:O(N)
	//空间复杂度:O(1)
}

由于两两元素比较的时候,相等的话不会交换位置,所以相等元素的相对次序不变,因而冒泡排序具有稳定性。

我们可以发现,冒泡排序的时间复杂度无论最坏情况还是最好情况,都和直接插入排序相同。
因此我们可以比较一下二者的效率。

七、冒泡排序与直接插入排序效率对比

数组有序时:一样
接近有序时:冒泡稍慢一些
数组局部有序:冒泡慢很多

所以,虽然冒泡排序可能是我们接触最早的一种排序方法,但是它的效率真的很一般,甚至不如直接插入排序。

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

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

相关文章

Linux服务器怎么分区

Linux服务器怎么分区 我是艾西&#xff0c;linux系统除了从业某个行业经常要用到的程序员比较熟悉&#xff0c;对于小白或只会用Windows系统的小伙伴还是会比较难上手的。今天艾西简单的跟大家聊聊linux系统怎么分区&#xff0c;让身为小白的你也能一眼看懂直接上手操作感受程序…

【数据结构】用Java实现七大排序算法

目录 &#x1f337;1. 排序的概念及引用 1.1 排序的概念 1.2 衡量指标 1.2 十个排序算法 1.3 十个排序性能对比 &#x1f337;2. 冒泡排序 2.1 算法描述 2.2 动图 ⭐️代码优化 &#x1f337;3. 选择排序 3.1 算法描述 3.2 动图 3.3 代码 &#x1f337;4. 插入排序 4.1 算法描述…

(大数据开发随笔9)Hadoop 3.3.x分布式环境部署——全分布式模式

索引完全分布式模式守护进程布局集群搭建准备总纲配置文件格式化集群启动集群集群控制命令集群启停进程查看启动日志查看集群常见问题案例演示&#xff1a;WordCount完全分布式模式 分布式文件系统中&#xff0c;HDFS相关的守护进程也分布在不同的机器上&#xff0c;如&#x…

cgroups是linux内核中限制、记录、隔离进程组(process groups)所使用的物理资源的机制

容器虚拟化 可以实现应用程序的隔离 直接使用物理机的操作系统可以快速响应用户请求 不占用部署时间 占用少量磁盘空间 缺点∶学习成本增加、操作控制麻烦、网络控制与主机虚拟化有所区别、服务治理难。 微服务架构师需要会多门编程语言&#xff0c;才能治理各种服务 三种…

web路径专题+会话技术

目录自定义快捷键1. 工程路径问题及解决方案1.1 相对路径1.2 相对路径缺点1.3 base标签1.4 作业11.5 作业21.6注意细节1.7 重定向作业1.8 web工程路径优化2. Cookie技术2.1 Cookie简单示意图2.2 Cookie常用方法2.2 Cookie创建2.3 Cookie读取2.3.1 JSESSIONID2.3.2 读取指定Cook…

Linux文件目录操作命令

目录 Linux常用的基础命令 使用技巧 1. ls命令&#xff1a;查看当前目录所有内容 ls 命令的多种使用方法&#xff1a; 注&#xff1a;假如执行乱码&#xff0c;则执行以下两步的代码&#xff1a; 2. cd命令&#xff1a;切换当前工作目录&#xff0c;即进入指定目录 3. …

网络-IP地址(嵌入式学习)

IP地址基本概念IPv4 五类&#xff1a;A B C D E特殊地址子网掩码子网号概念IPv6优势举个栗子基本概念 IP地址是Internet中主机的标识 IP地址&#xff08;Internet Protocol Address 互联网国际地址&#xff09;是一种在Internet上的给主机编址的方式&#xff0c;它主要是为互…

Java Web 开发技术的演进:从 Servlet、Spring MVC 到 WebFlux 及其竞品分析

前言 随着互联网技术的快速发展&#xff0c;Web 应用程序在处理海量用户访问和大数据时面临着巨大的挑战。在这个过程中&#xff0c;Java Web 开发技术经历了从 Servlet 到 Spring MVC 再到 WebFlux 的演变。在这篇文章中&#xff0c;我们将探讨这三个技术的发展历程、痛点及解…

Go的IO -- Go语言设计与实现

Go合IO的不解之缘 协程是Go的很大的一个优势。Go天然支持高并发&#xff0c;那么我们来研究一下这个高并发的秘诀在哪里&#xff1f; 执行体调度得当。CPU 不停的在不同的执行体&#xff08; Goroutine &#xff09;之间反复横跳&#xff01;CPU 一直在装填和运行不同执行体的…

数字化坚鹏:金融数据治理、数据安全政策解读及银行数字化转型

金融数据治理、数据安全政策解读及银行数字化转型课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不知道如何准确理解金融数据治理及数据安全相关政策 不清楚金融数据治理及数据安全相关政策对银行有什么影响&#xff1f; 不清楚如何进行银行数字化转型&#xff1f…

Azure DevOps Pipelines

Azure DevOps主要通过管理代码、管理服务器、管理发布的管道来实现一体化解决方案 发布流程&#xff1a; 1、代码上传Repos仓储 略 2、DevOps连接并管理发布服务器 2.1、Deployment Groups配置 2.2、服务器执行连接指令 2.3、服务器状态查看 3、创建 Pipline(构建代码) 3.1…

前端中font的使用

知识点&#xff1a; 运行截图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name&…

【RabbitMQ】SpringBoot整合RabbitMQ、实现RabbitMQ五大工作模式(万字长文)

目录 一、准备 1、创建SpringBoot项目 2、添加配置信息 3、创建配置类 二、RabbitMQ的配置类里创建队列 三、RabbitMQ的配置类里创建交换机及绑定队列 四、SpringBoot整合RabbitMQ入门案例 1、生产者 2、消费者 四、SpringBoot里实现RabbitMQ五大工作模式 1、简单模式…

Linux--进程多线程(上)

前言 精神内耗一方面可能是消极的&#xff0c;人好像一直在跟自己过不去&#xff0c;但其实它也是一种积极的情绪。精神内耗在某种程度上&#xff0c;是在寻找一种出口&#xff0c;寻找他自己人生的出口&#xff0c;寻找我今天的出口&#xff0c;或者寻找我一觉醒来明天的出口。…

【k8s完整实战教程5】网络服务配置(nodeport/loadbalancer/ingress)

系列文章&#xff1a;这个系列已完结&#xff0c;如对您有帮助&#xff0c;求点赞收藏评论。 读者寄语&#xff1a;再小的帆&#xff0c;也能远航&#xff01; 【k8s完整实战教程0】前言【k8s完整实战教程1】源码管理-Coding【k8s完整实战教程2】腾讯云搭建k8s托管集群【k8s完…

恐怖的ChatGPT!

大家好&#xff0c;我是飞哥&#xff01;不知道大家那边咋样。反正我最近感觉是快被ChatGPT包围了。打开手机也全是ChatGPT相关的信息&#xff0c;我的好几个老同学都在问我ChatGPT怎么用&#xff0c;部门内也在尝试用ChatGPT做一点新业务出来。那就干脆我就趁清明假期这一天宝…

AB测试基本原理

AB测试基本原理AB测试AB测试的基本步骤1、AB测试的基本步骤①选取指标指标的分类②建立假设③选取实验单位④计算样本量⑤流量分割⑥实验周期计算⑦线上验证⑧数据检验AB测试 所谓的AB测试就是使用实验组和对照组&#xff0c;通过控制变量法保证实验组和对照组基本条件一致&am…

NumPy 数组学习手册:6~7

原文&#xff1a;Learning NumPy Array 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 六、性能分析&#xff0c;调试和测试 分析&#xff0c;调试和测试是开发过程的组成部分。 您可能熟悉单元测试的概念。 单元测试是程序员编写的用于测试其代码的自动测试。 例如&…

AI —— 一看就懂的代码助手Copilot获取教程

背景 随着chatgpt的发布&#xff0c;人工智能领域近期站上了风口浪尖。GitHub Copilot由github与 OpenAI 合作创建&#xff0c;是世界上第一个使用 OpenAI 的 Codex 模型&#xff08;GPT-3 的后代&#xff09;制作的大规模生成式 AI 开发工具。GitHub Copilot 作为 AI 结对程序…

【条件判断】

目录知识框架No.0 筑基No.1 条件判断题目来源&#xff1a;PTA-L1-031 到底是不是太胖了题目来源&#xff1a;PTA-L1-063 吃鱼还是吃肉题目来源&#xff1a;PTA-L1-069 胎压监测题目来源&#xff1a;PTA-L1-077 大笨钟的心情题目来源&#xff1a;PTA-L1-083 谁能进图书馆知识框架…