椋鸟数据结构笔记#10:排序·中

news2024/11/17 15:27:29

文章目录

      • 四、归并排序
          • 时间复杂度
          • 实现
            • 递归实现
            • 非递归实现
          • 测试
          • 稳定性
      • 五、非比较排序
        • 5.1 计数排序
          • 时间复杂度
          • 实现
          • 测试
          • 局限性
        • 5.2 桶排序
          • 时间复杂度
          • 实现
          • 测试
        • 5.3 基数排序
          • 时间复杂度
          • 实现
          • 测试
          • 局限性

萌新的学习笔记,写错了恳请斧正。

四、归并排序

归并排序是一种非常高效的排序算法。基本思想是将一个大数组分成两半,分别对这两半进行排序,然后将排序好的两部分合并在一起。这个过程递归进行,每次将数组分半,直到每个部分只有一个元素,自然是有序的,最终得到一个完整的有序数组。

归并排序的步骤如下:

  1. 分割:把当前序列平均分割成两半。
  2. 递归排序:递归地对这两半进行归并排序,直到分割的子序列只包含一个元素。
  3. 合并:将两个有序的子序列合并成一个有序序列。
时间复杂度

归并排序的时间复杂度为 O ( N log ⁡   N ) O(N\log\,N) O(NlogN)​,在最好最坏情况都是如此。是一种效率稳定的排序方法。

实现
递归实现
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}

	int mid = (left + right) / 2;
	//[left, mid] [mid + 1, right]
	
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);

	//合并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else
		{
			tmp[index++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	memcpy(arr + left, tmp + left, sizeof(int) * (right - left + 1));
}

void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc");
		return;
	}

	_MergeSort(arr, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}
非递归实现

同样的,归并排序的递归也可以整合为非递归的形式:

void MergeSortNonR(int* arr, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	for (int gap = 1; gap < n; gap *= 2)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
				{
					tmp[i++] = arr[begin1++];
				}
				else
				{
					tmp[i++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[i++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = arr[begin2++];
			}
			memcpy(arr + j, tmp + j, sizeof(int) * (end2 - j + 1));	//用j不用begin1,begin1已经改变
		}
	}
	free(tmp);
	tmp = NULL;
}
测试

下面是对一千万个随机数据的排序测试

稳定性

归并排序是稳定的。

五、非比较排序

非比较排序不通过直接比较元素之间的大小关系来排序,而是利用其他方法,如数字或者字符串的特性。

非比较排序往往能达到非常非常高的时间效率,但是也往往受到非常大的使用限制

5.1 计数排序

计数排序使用一个额外的数组来记录每个值的出现次数,然后根据这些计数来组织输出排序结果。

计数排序的步骤:

  1. 找出待排序数组中的最大值和最小值,确定计数数组的长度。
  2. 创建并初始化计数数组,索引代表原数组中的元素,值代表该元素出现的次数。
  3. 遍历原数组,更新计数数组:对于原数组中的每一个元素,将计数数组对应索引的值增加1。
  4. 根据计数数组,重构原数组:遍历计数数组,根据每个索引的计数,在原数组中按顺序填充相应的元素。
时间复杂度

计数排序的时间复杂度是 O ( N + K ) O(N+K) O(N+K),其中K是数组中数据跨度的范围大小(比方说一个数组中所有数据都是1,那这个跨度就是1,如果里面有一个1变成了100万,那K就直接变成了100万)。

所以说,如果数据跨度比较小,计数排序的时间复杂度就可以认为是 O ( N ) O(N) O(N),其效率非常离谱。

实现
void CountSort(int* arr, int n)
{
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < n; ++i)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc");
		return;
	}
	memset(count, 0, sizeof(int) * range);
	for (int i = 0; i < n; ++i)
	{
		count[arr[i] - min]++;
	}
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}
	free(count);
	count = NULL;
}
测试

下面是对一千万个随机数据的排序测试(数据在0到32767):

下面是对一千万个随机数据的排序测试(数据在0到十亿):

局限性

就像上面所说,计数排序只有在数据跨度较小时能够获得极高的时间效率。而且计数排序只能用于排序整型数据。另外,其空间复杂度较高。

5.2 桶排序

注意:桶排序效率高的离谱,局限性也高的离谱,如果还是随便生成大量数据测试可能导致程序崩溃甚至电脑卡死!

桶排序是基于基数排序和分布的一种排序算法。其基本思想是将一个区间内的数据分散到多个有序的桶中,然后分别对每个桶中的元素进行排序,最后将各个桶中的元素按顺序合并,从而得到一个完全有序的数组。

桶排序的步骤描述起来较难理解,下面在代码部分详细解释。

时间复杂度

桶排序时间复杂度最低可达 O ( N ) O(N) O(N),非常高。但是但凡数据跨度比较大、bucketsize(下面会解释是什么)选取的函数不那么合适,就会导致时间和空间复杂度剧烈变化,可能直接造成代码崩溃。

实现
void BucketSort(int* arr, int n)
{
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < n; ++i)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	int range = max - min + 1;
	int bucketSize = 5;	//这里对不同的情形需要设置不同的数字,效率差距非常大
	int bucketCount = range / bucketSize + 1;
	int** bucket = (int**)malloc(sizeof(int*) * bucketCount);
	for (int i = 0; i < bucketCount; ++i)
	{
		bucket[i] = (int*)malloc(sizeof(int) * n);
	}
	int* count = (int*)malloc(sizeof(int) * bucketCount);
	memset(count, 0, sizeof(int) * bucketCount);
	for (int i = 0; i < n; ++i)
	{
		int index = (arr[i] - min) / bucketSize;
		bucket[index][count[index]++] = arr[i];
	}
	int index = 0;
	for (int i = 0; i < bucketCount; ++i)
	{
		InsertSort(bucket[i], count[i]);	//采用插入排序只是一种方法,这里不唯一
		for (int j = 0; j < count[i]; ++j)
		{
			arr[index++] = bucket[i][j];
		}
	}
	for (int i = 0; i < bucketCount; ++i)
	{
		free(bucket[i]);
	}
	free(bucket);
	bucket = NULL;
	free(count);
	count = NULL;
}

在上方实现中,我们首先确定了数据范围range。

然后我们要根据 range 确定每一个桶内我们要存放范围大小为多少的数据,也就是bucketSize。注意,这不是说每个bucket只能放bucketSize个数据,而是可以放多少值不同的数据(相同值可以无限叠放)。

随后我们就计算出了桶的数量bucketCount,并且创建了这么多桶。同时每一个桶都配备了一个计数器(对应到count数组里)。

最后就是与计数排序类似的步骤,数据分桶再收集即可。

测试

100万0~99的数据,bucketSize = 2:

100万0~99的数据,bucketSize = 5:

100万0~99的数据,bucketSize = 1:

5.3 基数排序

基数排序是对计数排序的一个升级方法。只要我们==把数组中的数从低位到高位逐次进行只看某一位的计数排序,最终就能得到有序的数组。==这可能有些难以理解,但是我们可以看一个例子:

数组=[170,45,75,90,802,24,2,66]

我们将按照十进制的个位、十位、百位等进行排序。这里最大的数字是802,有三位数字,所以我们将进行三轮排序。

第一轮:按个位排序

  • 170的个位是0
  • 45的个位是5
  • 75的个位是5
  • 90的个位是0
  • 802的个位是2
  • 24的个位是4
  • 2的个位是2
  • 66的个位是6

按个位排序的结果为:170,90,802,2,24,45,75,66170,90,802,2,24,45,75,66

第二轮:按十位排序

  • 170的十位是7
  • 90的十位是9
  • 802的十位是0
  • 2的十位是0(没有十位,视为0)
  • 24的十位是2
  • 45的十位是4
  • 75的十位是7
  • 66的十位是6

按十位排序的结果为:802,2,24,45,66,170,75,90802,2,24,45,66,170,75,90

第三轮:按百位排序

  • 802的百位是8
  • 2的百位是0(没有百位,视为0)
  • 24的百位是0
  • 45的百位是0
  • 66的百位是0
  • 170的百位是1
  • 75的百位是0
  • 90的百位是0

按百位排序的结果为:2,24,45,66,75,90,170,8022,24,45,66,75,90,170,802

最终排序结果为:2,24,45,66,75,90,170,8022,24,45,66,75,90,170,802

时间复杂度

基数排序的时间复杂度仅为 O ( k ×   N ) O(k\times\,N) O(k×N),非常高效。

实现
void RadixSort(int* arr, int n)
{
	int max = arr[0];
	for (int i = 1; i < n; ++i)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	int maxDigit = 0;
	while (max)
	{
		max /= 10;
		++maxDigit;
	}
	int* count = (int*)malloc(sizeof(int) * 10);
	int* bucket = (int*)malloc(sizeof(int) * n);
	int radix = 1;
	for (int i = 0; i < maxDigit; ++i)	//@
	{
		memset(count, 0, sizeof(int) * 10);
		for (int j = 0; j < n; ++j)
		{
			count[(arr[j] / radix) % 10]++;
		}
		for (int j = 1; j < 10; ++j)
		{
			count[j] += count[j - 1];
		}
		for (int j = n - 1; j >= 0; --j)
		{
			bucket[--count[(arr[j] / radix) % 10]] = arr[j];
		}
		memcpy(arr, bucket, sizeof(int) * n);
		radix *= 10;
	}
	free(count);
	count = NULL;
	free(bucket);
	bucket = NULL;
}

对于上方@标记的循环体中的3个子循环,这里需要给出一些解锁:

  1. 第一个for循环

    循环遍历整个数组,计算当前位的数字(个位、十位、百位等),并对应的增加 count 数组中对应索引的值。这里 a r r [ j ] / r a d i x %   10 arr[j]/radix\%\,10 arr[j]/radix%10 计算出当前位的值(如个位、十位等),count 数组用来记录每个数字(0-9)在当前位出现的次数。

  2. 第二个for循环

    通过累加前一个索引的 count 值,将 count 数组转化为前缀和数组。这一步是为了在下一个循环中能够直接定位每个元素在 bucket 中的存放位置。每个元素的存放位置取决于它当前位的值,并使用前缀和确定其在 bucket 中的结束位置。

  3. 第三个for循环

    从数组的最后一个元素开始向前遍历,这样可以保持排序的稳定性(即相同值的元素保持原有顺序)。通过查找当前位的数字对应的 count 数组值,确定元素在 bucket 中的位置(使用--count是为了下次遇到同样的数时位置向前移动一个单位),然后将元素放在 bucket 中相应的位置。

测试

下面是对一千万个随机数据的排序测试(数据在0到32767):

下面是对一千万个随机数据的排序测试(数据在0到十亿):

局限性

可以看到,基数排序一定程度上减除了计数排序对大范围数据处理的劣势,但是也增加了空间复杂度。与此同时,基数排序依旧保留了计数排序只能处理整数的缺点。

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

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

相关文章

pta L1-063 吃鱼还是吃肉

L1-063 吃鱼还是吃肉 分数 10 全屏浏览 切换布局 作者 陈越 单位 浙江大学 国家给出了 8 岁男宝宝的标准身高为 130 厘米、标准体重为 27 公斤&#xff1b;8 岁女宝宝的标准身高为 129 厘米、标准体重为 25 公斤。 现在你要根据小宝宝的身高体重&#xff0c;给出补充营养的…

java实现识别图片上的文字(OCR识别身份证等证件信息)

利用第三方jar包&#xff0c;实现识别图片上的文字。第三方支持地址&#xff1a;Spire.OCR for Java | 专业的图文识别组件&#xff0c;用以读取图片格式中的文本Spire.OCR for Java 是专为 Java 开发者设计的强大OCR库&#xff0c;提供高效的文字识别功能&#xff0c;能够从图…

Proxy 代理

意图 为其它对象提供一种代理以控制这个对象的访问。 结构 Proxy保存一个引用使得代理可以访问实体&#xff1b;提供一个与Subject的接口相同的接口&#xff0c;使代理可以用来替代实体&#xff1b;控制实体的存取&#xff0c;并可能负责创建和删除它&#xff1b;其他功能依赖…

企业文档知识库建设,数据安全如何保障?

随着现代市场经济的高速发展&#xff0c;企业的竞争优势越来越多体现在人才和科技的优势。而随着员工流动率的提升&#xff0c;随之流失的则是员工积累多年的宝贵工作经验&#xff0c;如果缺乏有效的内部知识库的建设和管理&#xff0c;企业的竞争优势将难以维系。「企业网盘」…

网络管理实验三、SNMP协议工作原理验证与分析

1 实验概括 实验目的&#xff1a; 学习捕获SNMP报文&#xff0c;通过报文分析理解SNMP协议的工作过程。 实验内容&#xff1a; 1&#xff09; 使用snmputilg发送SNMP数据包; 使用wireshark抓包&#xff1b;使用netstat –an查看代理站TCP/UDP连接表&#xff1b; 2&#xff09;…

单机调度问题(第i工件的完工时间=加工时间+等待时间)

第08章 制造系统的调度控制 - 百度文库 (baidu.com) 模拟退火单机极小化总流水时间的排序问题_哔哩哔哩_bilibili 在单机调度问题中&#xff0c;工件的完工时间是一个至关重要的指标&#xff0c;因为它直接反映了生产效率的高低。而完工时间的计算&#xff0c;必须同时考虑工件…

cocos creator 3.6 发布web手机端 加载进度条添加

cocos creator 升级到3.x之后加载进度条取消了&#xff0c;测试了多个3.x版本最终以creator 3.6.3版本&#xff0c;构建了简单的进度加载 参考链接&#xff1a; https://forum.cocos.org/t/topic/137113 打包web-mobile后&#xff0c;没有进度条。加载的时候只显示一个黑屏。…

贪吃蛇设计详解

在去年12月中&#xff0c;初次接触c语言&#xff0c;我靠着为数不多的知识&#xff0c;使用数组仿照写了一份贪吃蛇&#xff0c;现在时隔5个月&#xff0c;我已经有能力独立写出真正的贪吃蛇而不是简单的仿照&#xff08;虽然写的是挺简单的&#xff09;。 那么我们现在就正式…

在ComfyUI中使用Deforum简单步骤, 以及报错处理

⛳背景 deforum这个插件其实去年就在webui流行的时候火过一阵子&#xff0c;效果的话&#xff0c;因为并没有引入太多“时间”的概念&#xff0c;所以画面基本上每一帧都不一样&#xff0c;但也恰恰因为这个&#xff0c;所以可以产生很多宛若吃了毒蘑菇的视频&#xff0c;后来…

Rokid AR Lite空间计算套装发布,软硬件全面升级推动居家、出行、户外场景大规模应用

4月20日&#xff0c;以“好玩、好看、好上头”为主题的Rokid Open Day 2024发布会在杭州举行&#xff0c;Rokid对外正式发布新一代AR Lite空间计算套装&#xff0c;分享了近期Rokid在AR开发者生态和数字文化领域的进展和成果&#xff0c;并宣布了多项跨行业重磅合作。作为中国代…

OerOerlikonTCO1200欧瑞康LPCVD system操作使用说明

OerOerlikonTCO1200欧瑞康LPCVD system操作使用说明

javaWeb项目-智能仓储系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、JSP技术 JSP(Jav…

MySQL 列数据跨表拷贝,一句SQL快速将表A每条记录的某些字段拷贝到表B每条记录的某些字段(A、B表通过ID等字段对应)

文章目录 MySQL 列数据跨表拷贝&#xff0c;一句SQL快速将表A每条记录的某些字段拷贝到表B每条记录的某些字段&#xff08;A、B表通过ID等字段对应&#xff09;背景定义表填充测试数据跨表一 一对应拷贝列数据SQL参考资料 MySQL 列数据跨表拷贝&#xff0c;一句SQL快速将表A每条…

OSPF动态路由实验(思科)

华为设备参考&#xff1a;OSPF动态路由实验&#xff08;华为&#xff09; 一&#xff0c;技术简介 OSPF&#xff08;Open Shortest Path First&#xff09;是一种内部网关协议&#xff0c;主要用于在单一自治系统内决策路由。它是一种基于链路状态的路由协议&#xff0c;通过…

漆包线行业你了解多少?专业漆包线行业MES生产管理系统

今天就说说漆包线行业&#xff0c;漆包线是工业电机&#xff08;包括电动机和发电机&#xff09;、变压器、电工仪表、电力及电子元器件、电动工具、家用电器、汽车电器等用来绕制电磁线圈的主要材料。 漆包线上游是铜杆行业&#xff0c;下游是各种消费终端&#xff0c;主要是电…

详解数据在内存中的存储

系列文章目录 第一章 C语言基础知识 第二章 C语言控制语句 第三章 C语言函数详解 第四章 C语言数组详解 第五章 C语言操作符详解 第六章 C语言指针详解 第七章 C语言结构体详解 文章目录 1. 数据类型 1.1 基本数据类型 1.2 派生数据类型 2. 整形在内存中的存储 2.1 …

力扣练习题(2024/4/18)

1不相交的线 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非水平线…

kaggle 房价预测 得分0.53492

流程 导入需要的包引入文件,查看内容数据处理调用模型准备训练输出结果 导入需要的包 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.linear_model i…

Pandas介绍与Series创建

1.Pandas介绍 Pandas 是基于 NumPy 的一种工具&#xff0c;该工具是为解决数据分析任务而创建的&#xff0c;Pandas 提供了大量能使我们快速便捷地处理数据的功能 Pandas 与出色的 Jupyter 工具包和其他库相结合&#xff0c;Python 中用于进行数据分析的环境在性能、生产率和协…

【介绍下WebStorm开发插件】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…