探索数据结构:堆,计数,桶,基数排序的分析与模拟实现

news2024/11/8 6:28:25

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 堆排序

1.1. 算法思想

堆排序(Heap Sort)是一种基于堆数据结构的排序算法。其核心思想是将待排序的元素构建成一个最大堆或最小堆,然后依次将堆顶元素与堆中最后一个元素交换,并重新调整堆,使得剩余元素重新满足堆的性质。重复这个过程直到所有元素都被取出,就得到了一个有序的序列。

1.2. 算法步骤

  1. 建立一个大根堆(升序)。
  2. 将堆顶元素与堆底末尾元素交换,这时待排序中最大元素成功放到正确的位置,并且将堆中待排序的元素个数size--
  3. 然后对堆顶元素进行向下调整,使剩余待排序元素重新形成一个大根堆。
  4. 重复步骤2,3直至待排序元素个数size = 1,排序完成。

img

img

为什么升序要建大堆,降序要建小堆?

因为如果升序一旦建小堆的话,每一个取堆顶的元素之后都可能会破坏原本的堆的结构,都需要重新建堆,而建堆的时间复杂度为O(N),这样N个元素的排序,时间复杂度就会劣化为O(N2 )。

1.3. 动图演示

img

1.4. 代码实现

void AdjustDown(int* arr, int n, int parent)
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}
		if (arr[child] > arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* arr, int n)
{
	//向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

1.5. 复杂度分析

  • 时间复杂度:向下调整建堆的时间复杂度为O(N),向下调整的时间复杂度为O(logN),一共N次。所以总时间为O(N+NlogN),复杂度为O(NlogN)。
  • 空间复杂度:没有开辟额外的空间,所以空间复杂度为O(1)。

2. 计数排序

2.1. 算法思想

**计数排序(Counting Sort)**是一种非比较性的排序算法,适用于一定范围内的整数排序。其核心思想是统计每个元素出现的次数,然后根据这个统计信息,将元素放置到正确的位置上。

2.2. 算法步骤

  1. 找出待排序数组中的最大值max和最小值min
  2. 创建一个长度为max - min + 1的计数数组count,用于存储每个元素出现的次数。
  3. 遍历待排序数组,统计每个元素出现的次数,将其存储在计数数组中相应的位置上。
  4. 根据计数数组中的统计信息,将待排序数组重新排列。
  5. 将排好序的元素从计数数组中放回待排序数组中。

img

2.3. 动图演示

img

2.4. 代码实现

void CountSort(int* arr, int n)
{
	//找出最大与最小元素
	int max = arr[0];
	int min = arr[0];
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	int range = max - min + 1;
	int* countArray = (int*)malloc(sizeof(int) * range);
	if (countArray == NULL)
	{
		perror("malloc fail:");
		return;
	}
	//初始化
	memset(countArray, 0, sizeof(int)*range);
	//统计各元素出现次数
	for (int i = 0; i < n; i++)
	{
		countArray[arr[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (countArray[i]--)
		{
			arr[j++] = i + min;
		}
	}
}

2.5. 复杂度分析

  • 时间复杂度:遍历了原数组与range数组,所以时间复杂度为O(N+range)。
  • 空间复杂度:开辟了大小为range的数组,所以空间复杂度为O(range)。

3. 桶排序

3.1. 算法思想

桶排序(Bucket Sort) 是一种适用于一定范围内的元素排序的算法。其核心思想是将待排序的元素分配到有限数量的桶中,然后分别对每个桶中的元素进行排序,最后按照顺序将各个桶中的元素依次取出,得到有序序列。

3.2. 算法步骤

  1. 确定桶的每个桶的元素个数和桶的数量,将待排序数组中的元素分配到对应的桶中。
  • 每个桶的元素个数:bucketsize=(max-min)/n+1maxminn分别为数组最大元素,最小元素,以及元素个数。每个桶的范围就是:[min,bucketsize)[min+bucketsize,min+2*bucketsize)
  • 桶的数量:bucketnum=(max-min)/bucketsize+1bucketsize为每个桶的元素个数。
  1. 对每个桶中的元素进行排序,可以选择其他排序算法。
  2. 将各个桶中的元素按照顺序取出,组成最终的有序序列。

img

为什么bucketnumbucketsize 的计算最后要加1

  1. 首先是因为除法运算的结果是可以等于0的,而桶的数量与桶最大容纳个数是不可能为0,所以需要加1。
  2. 其次我们默认每个桶的范围是左闭右开区间,如果不加1最大的元素可能无法进入桶内。

3.3. 动图演示

img

3.4. 代码实现

void BucketSort(int* arr, int n)
{
	//找出最大与最小元素
	int max = arr[0];
	int min = arr[0];
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	//每个桶的元素最大个数
	int bucketsize = (max - min) / n + 1;
	//桶的个数
	int bucketnum = (max - min) / bucketsize + 1;
	int bucket[bucketnum][bucketsize];
	int bucketcount[bucketnum];//每个桶当前元素个数计数器
	memset(bucket, 0, sizeof(bucket));
	memset(bucketcount, 0, sizeof(bucketcount));
	//将元素放入桶中
	for (int i = 0; i < n; i++)
	{
		int index = (arr[i] - min) / bucketsize;//第几个桶
		bucket[index][bucketcount[index]] = arr[i];
		bucketcount[index]++;//第几个桶的个数++
	}
	for (int i = 0; i < bucketnum; i++)
	{
		QuickSort(bucket[i], 0, bucketcount[i] - 1);
	}
	for (int i = 0; i < bucketnum; i++)
	{
		int t = 0;
		for (int j = 0; j < bucketcount[i]; j++)
		{
			arr[t++] = bucket[i][j];
		}
	}
}

3.5. 复杂度分析

  • 时间复杂度:假设有N个元素,K个桶。假设元素在各个桶内平均分布,那么每个桶内的元素数量为N/K 。假设排序单个桶使用(N/K)log(N/K)时间,则排序所有桶使用Nlog(N/K)时间。 当桶数量K比较大时,时间复杂度则趋向于O(N) 。合并结果时需要遍历所有桶和元素,时间复杂度为O(N+K)。
  • 空间复杂度:需要借助N个元素以及K个桶的辅助空间,所以空间复杂度为O(N+K)。

4. 基数排序

4.1. 算法思想

基数排序(Radix Sort)是一种非比较性的排序算法,适用于对整数或字符串等元素进行排序。其核心思想是将待排序的元素按照位数进行分组,然后依次对每个位数进行稳定的排序,最终得到有序序列。

4.2. 算法步骤

  1. 确定待排序元素的最大位数,通常通过计算最大元素的位数或者最高位数来确定。
  2. 从最低位开始,依次对元素按照当前位上的数值进行分组,并且统计每个数组出现次数记录在counter数组中。(十进制的位范围为 0~9 ,因此需要长度为 10 的统计数组)
  3. 利用前缀和counter[i] = counter[i - 1] + counter[i]求出每个对应数值的最后一个元素的下标索引。
  4. 倒序遍历,通过每个元素arr[i]的当前位上的值求出下标索引j=counter[i]-1,并将该元素存入新的数组ret[j]=arr[i]中,最后以ret数组覆盖原数组达到排序该位数的目的。
  5. 重复步骤2,3,4直至达到最大元素的位数,排序完毕。
  1. 按个位排序
    img

  2. 按十位排序
    img

为什么一定要从从最低位开始排序?

在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果a<b ,而第二轮排序结果 a>b,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,我们应该先排序低位再排序高位。

4.3. 动图演示

img

4.4. 代码实现

//获取当前位数的值
int digit(int num, int exp) 
{
	return (num / exp) % 10;
}
//对当前位数进行排序
void CountSortDigit(int arr[], int n, int exp) {
	// 十进制的位范围为 0~9 ,因此需要长度为 10 的统计数组
	int* counter = (int*)malloc((sizeof(int) * 10)); 
	if (counter == NULL)
	{
		perror("malloc fail:");
		return;
	}
	//初始化
	memset(counter, 0, sizeof(int)*n);
	// 统计 0~9 各数字的出现次数
	for (int i = 0; i < n; i++) 
	{
		int d = digit(arr[i], exp);
		counter[d]++;
	}
	// 求前缀和,将“出现个数”转换为“数组索引”
	for (int i = 1; i < 10; i++) 
	{
		counter[i] += counter[i - 1];
	}
	// 倒序遍历,根据统计数组内统计结果,将各元素填入 ret
	int* ret = (int)malloc(sizeof(int) * n);
	if (ret == NULL)
	{
		perror("malloc fail:");
		return;
	}
	memset(ret, 0, sizeof(int) * n);
	for (int i = n - 1; i >= 0; i--) 
	{
		int d = digit(arr[i], exp);
		int j = counter[d] - 1; // 获取 d 在数组中的索引 j
		ret[j] = arr[i]; // 将当前元素填入索引 j
		counter[d]--; 
	}
	// 覆盖原数组
	for (int i = 0; i < n; i++) 
	{
		arr[i] = ret[i];
	}
}
void RadixSort(int*arr, int n) 
{
	// 获取数组的最大元素,用于判断最大位数
	int max = arr[0];
	for (int  i = 0; i < n ; i++) 
	{
		if (arr[i] > max) 
		{
			max = arr[i];
		}
	}
	// 按照从低位到高位的顺序遍历
	for (int exp = 1; max >= exp; exp *= 10)
	{
		CountSortDigit(arr, n, exp);
	}
}

4.5. 复杂度分析

  • 时间复杂度:设数据量为N、数据为D进制、最大位数为K ,则对某一位执行计数排序使用O(N+D) 时间,排序所有K 位使用O((N + D)K) 时间,时间复杂度为O(N*K)。通常情况下,D和K都相对较小,时间复杂度趋向O(N) 。
  • 空间复杂度:基数排序需要借助长度为N和D的统计数组,所以基数排序空间复杂度为O(N+D)。

5. 排序算法的稳定性

5.1. 稳定性的定义

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

5.2. 各种排序算法的稳定性

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度稳定性
冒泡算法O(N2 )O(N )O(N2 )O(1 )稳定
选择算法O(N2 )O(N2 )O(N2 )O(1 )不稳定
插入排序O(N2 )O(N )O(N2 )O(1 )稳定
希尔排序O(N1.3 )O(N )O(N2 )O(1 )不稳定
快速排序O(NlogN)O(NlogN)O(N2 )O(logN )不稳定
归并排序O(NlogN)O(NlogN)O(NlogN)O(N )稳定
堆排序O(NlogN)O(NlogN)O(NlogN)O(1)不稳定
计数排序O(N+K)O(N+K)O(N+K)O(K)稳定
桶排序O(N+K)O(N+K)O(N2 )O(N+K)稳定
N)O(N )稳定
堆排序O(NlogN)O(NlogN)O(NlogN)O(1)不稳定
计数排序O(N+K)O(N+K)O(N+K)O(K)稳定
桶排序O(N+K)O(N+K)O(N2 )O(N+K)稳定
基数排序O(N*K)O(N*K)O(N*K)O(N+K)稳定

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

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

相关文章

在IDEA中使用Git在将多次commit合并为一次commit

案例&#xff1a; 我想要将master分支中的 测试一、测试二、测试三三次commit合并为一次commit 1. 点击Git 2. 双击点击commit所在的分支 3. 右键要合并的多个commit中的第一次提交的commit 4. 点击右键后弹出的菜单中的Interactively Rebase From Here选项 5. 点击测试二…

家政服务小程序,提高企业在市场中的竞争力

近几年&#xff0c;人们对家政的需求持续增加&#xff0c;面对小程序的快速发展&#xff0c;互联网家政的模式成为了市场新的发展方向&#xff0c;越来越多的居民也开始在线上预约家政服务。随着当下人们对家政的需求日益提升&#xff0c;线上家政小程序利用各种信息技术&#…

2024年华为OD机试真题-多段线数据压缩-C++-OD统一考试(C卷D卷)

2024年OD统一考试(D卷)完整题库:华为OD机试2024年最新题库(Python、JAVA、C++合集)​ 题目描述: 下图中,每个方块代表一个像素,每个像素用其行号和列号表示。 为简化处理,多段线的走向只能是水平、竖直、斜向45度。 上图中的多段线可以用下面的坐标串表示:(2, 8), (3…

webgl_effects_stereo

ThreeJS 官方案例学习&#xff08;webgl_effects_stereo&#xff09; 1.效果图 2.源码 <template><div><div id"container"></div></div> </template> <script> import * as THREE from three; // 导入控制器 import { …

锐捷校园网自助服务系统 login_judge.jsf 任意文件读取漏洞复现(XVE-2024-2116)

0x01 产品简介 锐捷校园网自助服务系统是锐捷网络推出的一款面向学校和校园网络管理的解决方案。该系统旨在提供便捷的网络自助服务,使学生、教职员工和网络管理员能够更好地管理和利用校园网络资源。 0x02 漏洞概述 校园网自助服务系统/selfservice/selfservice/module/sc…

Linux Kernel nf_tables 本地权限提升漏洞(CVE-2024-1086)

文章目录 前言声明一、netfilter介绍二、漏洞成因三、漏洞危害四、影响范围五、漏洞复现六、修复方案临时解决方案升级修复方案 前言 2024年1月&#xff0c;各Linux发行版官方发布漏洞公告&#xff0c;修复了一个 netfilter:nf_tables 模块中的释放后重用漏洞&#xff08;CVE-…

企业数据挖掘建模平台极简建模流程

泰迪智能科技企业数据挖掘建模平台是企业自主研发&#xff0c;面向企业级用户的快速数据处理构建模型工具。平台底层算法基于R语言、Python、Spark等引擎&#xff0c;使用JAVA语言开发&#xff0c;采用 B/S 结构&#xff0c;用户无需下载客户端&#xff0c;可直接通过浏览器进…

Makefile:2:*** missing separator. Stop.

中文意思是说缺少分隔符。 解决办法如下 出现这种错误的原因: 在编辑makefile 时有些行没有前面没有按下tab键。举例&#xff1a;另外需要注意的是&#xff0c;如果你是使用vscode编辑&#xff0c;注意在vscode里面编辑的tab有可能也出现问题。建议使用vim编辑一下Makefile &a…

Word2021中的The Mathtype DLL cannot be found问题解决(office 16+mathtype7+非初次安装)

问题描述&#xff0c;我的问题发生在word中无法使用自定义功能区中的mathtype 我的环境是&#xff1a;W11Word2021mathtype7 因为我是第二次安装mathtype7&#xff0c;所以我怀疑是因为没有卸载干净&#xff0c;于是我参考了下面这篇文章的做法 参考文章 1.首先重新卸载当前的…

QT:QML中使用Loader加载界面

目录 一.介绍 二.实现 三.效果展示 四.代码 一.介绍 在QML中使用Loader加载界面&#xff0c;可以带来诸多好处&#xff0c;如提高应用程序的启动速度、动态地改变界面内容、根据条件加载不同的组件、更有效地使用内存以及帮助分割应用逻辑等。 1.延迟加载&#xff1a;QML…

【WRF理论第四期】namelist.wps文件详述

WRF理论第四期&#xff1a;namelist.wps文件详述 1 namelist.wps 的主要部分1 &share2 &geogrid3 &ungrib4 &metgrid示例 namelist.wps 文件参考 namelist.wps 文件是 WRF Preprocessing System (WPS) 的关键配置文件&#xff0c;用于设置地理数据和气象数据预…

c语言练习:POJ 1003 宿醉(HangOver)

为什么写这篇文章 作为一名计算机相关方向的学生&#xff0c;本人的代码能力却十分差劲&#xff0c;这不能不让人万分羞愧。于是&#xff0c;决定从此好好学代码&#xff0c;每天坚持刷题。而C语言是计算机程序语言的基础&#xff0c;遂决定从c语言开始&#xff0c;提高自身编…

以hive metastore报错举例,远程调试hadoop服务

项目场景&#xff1a; CDH集群CM切换hive元数据库报错&#xff1a; com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at…

UML静态图-类图

概述 静态图包含类图、对象图和包图的主要目的是在系统详细设计阶段&#xff0c;帮助系统设计人员以一种可视化的方式来理解系统的内部结构和代码结构&#xff0c;包括类的细节、类的属性和操作、类的依赖关系和调用关系、类的包和包的依赖关系。 一、类图的表示法 类图(Cla…

2024年计算机、信息工程与大数据应用国际会议(CIEBDA 2024)

2024 International Conference on Computer, Information Engineering, and Big Data Applications 【1】大会信息 会议简称&#xff1a;CIEBDA 2024 大会地点&#xff1a;中国青岛 审稿通知&#xff1a;投稿后2-3日内通知 投稿邮箱&#xff1a;ciebdasub-paper.com 【2】会…

eNSP学习——配置RIP的版本兼容、定时器和协议优先级

目录 主要命令 原理概述 实验内容 实验拓扑 实验目的 实验编址 实验步骤 1、基本配置 2、配置RIP协议的版本兼容 3、配置RIP的定时器 4&#xff0e;配置RIP协议优先级 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NSP各种设备配置命令大全PDF版…

【YOLOv8改进[CONV]】SPDConv助力YOLOv8目标检测效果 + 含全部代码和详细修改方式 + 手撕结构图

本文将使用SPDConv助力YOLOv8目标检测效果的实践,文中含全部代码、详细修改方式以及手撕结构图。助您轻松理解改进的方法。 改进前和改进后的参数对比: 目录 一 SPDConv 二 SPDConv助力YOLOv8目标检测效果 1 整体修改 ① 添加SPDConv.py文件 ② 修改ultralytics/nn/tas…

【图像处理与机器视觉】灰度变化与空间滤波

基础 空间域与变换域 空间域&#xff1a;认为是图像本身&#xff0c;对于空间域的操作就是对图像中的像素直接进行修改 变换域&#xff1a;变换系数处理&#xff0c;不直接对于图像的像素进行处理 邻域 图像中某点的邻域被认为是包含该点的小区域&#xff0c;也被称为窗口 …

ai智能写作app有分享吗?4个专业的软件!

在数字化浪潮席卷全球的今天&#xff0c;内容创作的需求日益旺盛&#xff0c;而AI智能写作app的出现&#xff0c;无疑为内容创作者们带来了一股清新的风。它们凭借强大的自然语言处理能力和海量的数据资源&#xff0c;帮助创作者们快速生成高质量、有吸引力的文章&#xff0c;让…

目标检测-AnyLabeling标注格式转换成YOLO格式

Anylabel可以极大的增加数据的标注效率&#xff0c;但是其标注格式如何能转换成YOLO标注格式&#xff0c;具体内容如下所示。 关于AnyLabeling的其它详细介绍如下链接所示 https://blog.csdn.net/u011775793/article/details/134918861 Github链接 https://github.com/vietanhd…