排序(五)——非比较排序+排序总结

news2024/12/29 10:23:58

1.非比较排序

我们前面讲的排序算法都是通过比较大小来进行排序的,他们都是比较排序。

像基数排序、计数排序和桶排序等都不是通过比较大小来排序的,是非比较排序,在这里我们讲一下其中的计数排序和基数排序,而桶排序实现起来太挫,是一种类似哈希的结构,我们之后会学到哈希结构,就不学习桶排序了。

1.1计数排序

计数排序是怎么不通过比较两个元素的大小进行排序的呢?假设我们有下面这样一组数据:

对于这样的数据,我们前面学的排序当然都可以解决,但是这里我们要用一种很巧妙的方法,相信大家在做题的过程中肯定遇见过这样的一类题 : 某个数组包含 0~n 的数据,每一数组都只出现了一次,而有一个数据缺失了,只遍历这个原数组一遍找出这个缺少的数。在做这个题的时候,我们用到了一种方法,就是创建容纳一个 n+1 个整形的数组,用calloc,开辟的时候顺便初始化为0,然后遍历原数组一遍,以原数组的元素作为下标访问 我们开辟的新数组,把该下标位置的0加一变成1。最后遍历一遍用来计数的数组,看哪个下标的元素为0,就代表着这个下标在原数组中没有出现过。而我们计数排序就是通过统计数组中每一个元素出现的个数来进行排序的。

首先我们要创建一个数组来统计个数,我们看到前面的数组的最大值就是9,所以我们就开创一个十个整形大小的数组。

	//遍历一遍找最大数
	int max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
	}
	//创建一个 max+1的数组
	int* count = (int*)calloc(max + 1, sizeof(int));
	assert(count);

第二步就是遍历数组统计次数

	//遍历一遍统计个数
	for (int i = 0; i < n; i++)
	{
		count[a[i]]++;
	}

当把每个数出现的次数统计出来之后,我们就看艳照出现的次数将其依次覆盖回原数组。覆盖完之后就已经把元数据排好序了,最后释放掉count数组

	int j = 0;
	//覆盖回原数组
	for (int i = 0; i < max + 1; i++)
	{
		while (count[i]--)
		{
			a[j++] = i;
		}
	}

	free(count);

这个算法的思路很简单,同时,我们在统计数组元素个数时,以计数数组的下标来代表元素,这种就是映射,下标的之就代表元素的值,就是绝对映射。

但是对于这样一组数据呢?

他们之中的最大值是120,那是不是意味着我们就要开辟一块容纳 121 个整形的空间呢?如果我们开辟这么大一块空间,那么前面的一百个空间是不是浪费了?而统计的时候我们只会用到后面的二十一块空间。或者说如果要排序下面一个数组:

这里的数据有负数,而下标是不可能出现负数,这种情况该怎么处理呢?

这就引申出了第二种思路,相对映射

相对映射具体做法是这样的,我们用 0 下标来代表数组中最小的数据,下标 1 代表最小的数据+1,以此类推。这样一来,我们的计数的数组只要算出数组中的最大值和最小值,他们的差值+1就是我们要开辟的数组要统计的出现次数的数据的个数。

比如第一个数组,我们用0下标来代表100,用1下标来代表101... ...用20下标来代表20,这样一来我们就只要开辟21个整形大小的空间就能完成计数了,大大减小了空间浪费

对于存在负数的数组也是一样的,我们用下标 0 来代表最小值 -5,用下标 1 来代表 -4 ... ...用下标 10代表 5 。

这时候我们就可以先遍历一边数组来找出最小值和最大值,然后求出数据范围。其他的操作还是跟之前一样

void CountSort(int* a, int n)
{
	assert(a);

	int min = a[0];
	int max = a[0];
	//遍历一遍找最小值和最大值
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	//求数据个数(范围)
	int range = max - min + 1;
	//计数数组
	int* count = (int*)calloc(range, sizeof(int));
	assert(count);
	//统计个数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	int j = 0;
	//覆盖
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(count);
}

相对映射比绝对映射更适合大多数场景。

对于计数排序,适合对数据范围相对集中的数据进行排序,效率很高,只需要遍历两遍数组,O(N+range)的时间复杂度和O(range)的空间复杂度。

他的缺点就是:1.不适合数值分散的数据

                         2.只适用于整形,不适合浮点数和字符串的排序

字符和负数的排序只要范围相对集中就都能用计数排序进行处理。

1.2基数排序

基数排序其实我们在生活中经常用到,比如打扑克的时候,一张牌的大小取决于它的数值和他的花色,我们对其排序的时候可以先根据他的花色分分组,然后在按其数值进行分组,这样一来政府派的大小就排好序了。

基数排序的思想就是两部:分发数据和回收数据。具体的概念很复杂,但是实现起来其实不是很难。对于基数排序方式有两种:MSD(最高位优先)LSD(最低位优先)

我们直接举例来实现一次基数排序,对于下面的这一组数据

这样的一组数据我们可以根据他的个位十位和百位分别进行分组和回收,这样一来,我们就只有十个分组的基,因为每一位的数值都在0~9之间。

首先我们以个位进行分组

以个位分完组之后进行回收,相同的组里先进来的先出去。

然后再以十位进行分组。

分发完数据之后再进行回收

最后再以百位分发数据

再次回收

这次回收数据之后我们就发现他已经排成有序了,分发和回收数据的次数取决于它的最大的数据有几位。这就是一种基数排序的应用。我们前面说了先进入分组的先出去,这时候我们就可以用队列来实现了。

//分发和回收数据
void Radix(int* a, int n, int k,Queue*q)
{
	//先取出每一个数,找到它的那一位,放进相应的队列中
	for (int i = 0; i < n; i++)
	{
		int num = a[i];
		int key = 0;
		int c = k;
		while (c--)
		{
			key = num % 10;
			num = num / 10;
		}
		//求出他的指定位的数字
		//放进指定的队列中
		QueuePush(&q[key], a[i]);
	}
	//回收数据
	int j = 0;
	for (int i = 0; i < 10; i++)
	{
		while (!QueueEmpty(&q[i]))
		{
			a[j++] = QueueFront(&q[i]);
			QueuePop(&q[i]);
		}
	}
}

//主逻辑
void RadixSort(int* a, int n, int k)
{
	assert(n);
	//k趟分发和回收
	Queue q[10];
	for (int i = 0; i < 10; i++)
	{
		QueueInit(&q[i]);
	}
	for (int i = 1; i <= k; i++)
	{
		Radix(a, n, i,q);
	}


	for (int i = 0; i < 10; i++)
	{
		QueueDestroy(&q[i]);
	}

}

排序总结

我们前面学了几种比较排序,这里我们对他们进行一个总结

时间复杂度

直接插入排序:O(N^2)

希尔排序:       O(N^1.3)

选择排序:       O(N^2)

堆排序:           O(NlogN)

冒泡排序:       O(N^2)

快排:              最好O(NlogN),最坏O(N^2),但是三数取中优化之后一般不会出现最坏

归并排序:       O(NlogN)

空间复杂度

直接插入排序:O(1)

希尔排序:       O(1)

选择排序:       O(1)

堆排序:           O(1)

冒泡排序:       O(1)

快排:              最好O(logN),最坏O(N),但是三数取中优化之后一般不会出现最坏

归并排序:       O(logN)

除了时间复杂度和空间复杂度,有时候也会关注算法的稳定性。排序算法的稳定性指的是数据中相同的值在经过排序之后,他们的相对顺序不变,如果可以做到相对顺序不变,那就是稳定的排序算法,如果做不到,那就是不稳定的。

稳定性:

直接插入排序:稳定      我们可以控制相同的数不交换位置,这样相对顺序是不会变的

希尔排序:不稳定         我们不知道相同的值被分到了哪个组,分组我们是不可控的,所以希尔排序是不稳定的

选择排序:不稳定           选择排序其实是不稳定的,因为他选数然后交换的时候是无法保证相对顺序一定不变的,就拿下面的数组来举例 

对于这样的数据选择排序是无法保证相对顺序的

堆排序:不稳定         堆排序是经典的不稳定的,他的建堆过程相同的数在不同的子树上就有可能会改变相对顺序,同时,将堆顶元素和最后一个元素交换的时候也是无法保证相对顺序的。比如我们举一个极端例子:

冒泡排序:稳定         冒泡排序我们可以控制他在相等时不交换,这样就能保证不改变相对顺序

快排:不稳定        快排的不稳定体现在 如果key就是那个相等的值,他一趟排序下来的相遇点的位置也就是最终位置是不一定能保证与与之相等的数相对顺序不变的

归并:稳定        归并是分区间排序的,我们可以控相等的时候左边区间的数排在右边区间的前面,这样就能保证相对顺序不变了

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

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

相关文章

在k8s 中部署有状态服务MongoDB高可用集群详解(附带镜像)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、k8s简介 2、MongoDB介绍 3、为什么要…

网络爬虫软件学习

1 什么是爬虫软件 爬虫软件&#xff0c;也称为网络爬虫或网络蜘蛛&#xff0c;是一种自动抓取万维网信息的程序或脚本。它基于一定的规则&#xff0c;自动地访问网页并抓取需要的信息。爬虫软件可以应用于大规模数据采集和分析&#xff0c;广泛应用于舆情监测、品牌竞争分析、…

【 书生·浦语大模型实战营】作业(五):LMDeploy 量化部署

【 书生浦语大模型实战营】作业&#xff08;五&#xff09;&#xff1a;LMDeploy 量化部署 &#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系…

Vue2之组件通信(爆肝)

大家有什么想看的可以在评论区留言&#xff0c;我尽量满足&#xff0c;感谢大家&#xff01; 组件通信是vue中一个非常重要的内容&#xff0c;我们需要掌握好组件通信&#xff0c;那么让我为大家介绍几种组件通信的方式吧&#xff01; 一、props 这是父传子的方式&#xff0…

FFmpeg合并音视频文件操作备忘(mac版)

利用NDM嗅探插件从B站下载下来的文件是音视频分开的&#xff0c;用剪辑软件合并时发现导出时文件都特别大&#xff0c;于是使用FFmpeg处理 环境&#xff1a; MBP M1芯片版 系统 macOS Sonama 14.4.1 操作步骤&#xff1a; 一、官方下载链接&#xff1a;https://evermeet.cx/…

MySQL 锁机制全面解析

目录 1. MySQL的锁类型1.1 全局锁1.2 表锁1.3 行锁1.4 共享锁&#xff08;读锁&#xff09;1.5 排它锁&#xff08;写锁&#xff09;1.6 死锁 2 乐观锁和悲观锁2.1 乐观锁2.2 悲观锁 3 意向锁4 间隙锁5 临键锁6. 事务隔离级别对锁的影响6.1 读未提交&#xff08;Read Uncommitt…

npm内部机制与核心原理

npm 的核心目标&#xff1a; Bring the best of open source to you, your team and your company. npm 最重要的任务是安装和维护开源库。 npm 安装机制与背后思想 npm 的安装机制非常值得探究。Ruby 的 Gem&#xff0c;Python的pip都是全局安装机制&#xff0c;但是npm的安装…

️️️Vue3+Element-Plus二次封装一个可定制化的table组件

前言 为什么需要二次封装 开发后台管理系统,会接触到很多表格和表单,一但表格表单多起来,仅仅只需要一小部分改变&#xff0c;都需要在中重写一大堆代码,许多重复逻辑,我们可以把重复逻辑抽离出来二次封装一个组件 使用,减少在开发中需要编写的代码。 为什么需要定制化 每个…

【AI工具之Prezo如何自动生成PPT操作步骤】

先说优缺点&#xff1a; 最大的优点就是免费&#xff08;但说实话功能和体验方面很弱&#xff09;支持中文提问&#xff08;最好用英文&#xff09;&#xff0c;智能生成图文&#xff08;但是只能生成英文内容&#xff09;可以AI生成图片&#xff0c;图片很精美酷炫&#xff0…

数据可视化(四):Pandas技术的高级操作案例,豆瓣电影数据也能轻松分析!

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

(八)Pandas窗口数据与数据读写 学习简要笔记 #Python #CDA学习打卡

一. 窗口数据(Window Functions) Pandas提供了窗口函数(Window Functions)用于在数据上执行滑动窗口操作&#xff0c;可以对数据进行滚动计算、滑动统计等操作。需要注意的是&#xff0c;在使用窗口函数时&#xff0c;需要根据实际需求选择合适的窗口大小和窗口函数&#xff0…

硬件设备杂记——12G SDI及 AES67/EBU

常见的 SDI线缆规格&#xff0c;HD-SDI又被称为1.5G-SDI&#xff0c;具体参数以秋叶原的参数为例 AES67/EBU 目前音频网络标准主要集中在OSI网络体系的第二层和第三层。 第二层音频标准的弊端在于构建音频网络时需要专用的交换机&#xff0c;无法利用现有的以太网络&#xff0c…

布局香港之零售中小企篇 | 传承之味,迈向数字化经营的时代

随着内地与香港两地经贸合作日渐紧密&#xff0c;越来越多内地消费品牌将目光投向香港这片充满机遇的热土&#xff0c;纷纷入驻香港市场。「北店南下」蔚然成风&#xff0c;其中不乏已在内地市场深耕多年的传统老字号。数字化经营时代&#xff0c;老字号焕新刻不容缓&#xff0…

QoS流量整形

流量整形是一种带宽技术形式&#xff0c;它延迟某些类型的网络数据包的流动&#xff0c;以确保更高优先级应用程序的网络性能&#xff0c;它主要涉及调整数据传输速率&#xff0c;以确保网络资源以最佳容量得到利用。流量整形的目的是防止网络拥塞并提高网络的整体性能&#xf…

【Leetcode每日一题】 分治 - 排序数组(难度⭐⭐)(60)

1. 题目解析 题目链接&#xff1a;912. 排序数组 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路&#xff1a; 快速排序作为一种经典的排序算法&#xff0c;其核心思想在于通过“分而治之”的策略&#xff…

C++:深入理解operator new/operator delete

动态内存管理 1.语法层面1.基本语法注意点 2.new/delete和malloc/free的区别3.operator new和operator delete函数&#xff08;底层重点&#xff09;1.operator new/delete原理2.图解1.new/new[]2.delete/delete[] 3.new[n]和delete[] 4.定位new1.定义2.使用格式 1.语法层面 1…

EPSON晶振应用到汽车电子产品上的型号有哪些?

EPSON品牌应用在汽车电子产品上的晶振.&#xff0c;当然也少不了晶振可能最熟悉的就是32.768K系列和26MHZGPS晶振用的多。 在汽车里每一个部件都应有的不一样,甚至多次使用到同一尺寸,不同频率的晶振.爱普生品牌晶振型号就有几百种,很容易混淆,要想记住汽车里所应用到的不是件…

python爬虫(Selenium案列)第二十四

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

windows docker desktop==spark环境搭建

编写文件docker-compose.yml version: 3services:spark-master:image: bde2020/spark-master:3.1.1-hadoop3.2container_name: spark-masterports:- "8080:8080"- "7077:7077"- "2220:22"volumes:- F:\spark-data\m1:/dataenvironment:- INIT_D…

HiveSql中的函数家族(二)

一、窗口函数 1、什么是窗口函数 在 SQL 中&#xff0c;窗口函数&#xff08;Window Functions&#xff09;是一种特殊的函数&#xff0c;它允许在查询结果集的特定窗口&#xff08;通常是一组行&#xff09;上执行聚合、分析和计算操作&#xff0c;而无需聚合整个结果集。窗口…