【数据结构】选择排序(详细)

news2024/9/23 17:03:08

选择排序

  • 1. 直接选择排序
  • 2. 堆排序
    • 2.1 堆
    • 2.2 堆的实现(以大根堆为例)
    • 2.3 堆排序
  • 3. 堆排序(topK问题)


1. 直接选择排序

  1. 思想
    以排升序为例。以a[i]为最大值(或最小值),从a[i+1]到a[n-1-i]比较选出最大值放在a[n-1-i](或最小值放在a[i])。简单讲就是将以每轮排序的第一个数作为最大值或者最小值,比较剩余的元素,得到最大值或者最小值,将最大值放在剩余数据的最后一位,最小值放在剩余数据的第一位。
    升级版
    以排升序为例。每轮排序中选出最大值和最小值,分别放在数据的左右两端。

  2. 例子(排升序)
    在这里插入图片描述

  3. 代码实现

//选择排序(以排升序为例)
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int maxi = left, mini = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		Swap(&a[maxi], &a[right]);
		//这里要考虑特殊情况:如果最小值mini==right,那么在交换maxi和right后,
		//最小值就发生改变,所以要最小值的下标要改变。
		if (mini == right)
		{
			mini == maxi;
		}
		Swap(&a[mini], &a[left]);
		right--;
		left++;
	}
}
  1. 算法分析
    时间复杂度
    假设有n个元素,第一次排序要遍历n-2个元素,第二次排序要遍历n-4个元素,往后每次排序的元素个数都减2,总的遍历次数是等差数列的前n项和,所以时间复杂度是O(N^2)。
    空间复杂度
    空间复杂度是O(1)。
    稳定性
    是不稳定的排序。如上面的例子中的12,在选出最大值和最小值时,前后顺序发生改变。
    注意
    直接选择排序的最好情况和最坏情况的时间复杂度都是O(N^2)。

2. 堆排序

2.1 堆

  1. 概念
    堆其实是一种树形结构,分为大根堆和小根堆。大根堆是树中所有父节点大于子节点,小根堆是树中所有父节点小于子节点。但父节点的左右孩子谁大谁小没有要求。

  2. 预备知识
    (1)堆的逻辑结构是树,物理结构是数组(即在内存中以数组的形式存储)。
    在这里插入图片描述

(2)父节点和子节点的关系
已知子节点下标,它的父节点下标parent = (child - 1)/2。
已知父节点下标,它的子节点下标leftchild = parent * 2 + 1,rightchild = parent * 2 + 2。
(3)种存储结构只适用于满二叉树和完全二叉树,否则会浪费很多空间。

2.2 堆的实现(以大根堆为例)

  1. 堆的定义
//堆的实现
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;//用来申请空间
	int size;//记录当前堆的元素个数
	int capacity;//记录当前堆的最大容量
};
  1. 堆的初始化
//堆的初始化
void HeapInit(HP* ph)
{
	assert(ph);
	ph->a = (HPDataType*)malloc(sizeof(HPDataType)*10);//初始化容量设置为10
	if (ph->a == NULL)
	{
		perror("malloc failed");
		return;
	}
	ph->capacity = 10;
	ph->size = 0;
}
  1. 入堆
//入堆
void HeapPush(HP* ph, HPDataType x)
{
	assert(ph);
	//首先要检查堆是否已满
	if (ph->size == ph->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(ph->a, sizeof(HPDataType) * ph->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc failed");
			return;
		}
		ph->a = tmp;
		ph->capacity *= 2;//每次容量不够时扩容两倍
	}
	ph->a[ph->size++] = x;
	//因为要实现大根堆,如果入堆元素大于前面的元素,就要把它向上调整。
	AdjustUp(ph->a, ph->size - 1);//第一次参数是要调整的数据元素,第二个参数是入堆元素的下标。
}
  1. 向上调整
//向上调整
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
	//得到父节点的下标
	int parent = (child - 1) / 2;
	while (child > 0)//当子节点爬到下标为0的位置,就达到顶部了,此时可以停止循环
	{
		//比较父节点和子节点,谁大谁上去
		//让大的节点不断往上爬
		if (a[chil] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

在这里插入图片描述

  1. 出堆
    疑惑
    (1)
    出堆其实就是弹出根节点。但我们要考虑弹出根节点后,后面的数据要如何处理?将数据往前移动?这样数据间的关系就全乱了,且所有数据往前移效率低(O(N^2))。
    正确的做法是交换堆顶元素和最后一个元素,然后删除最后一个元素,然后将堆顶元素向下调整
    (2)
    为什么要弹出根节点?弹出最后一个元素岂不是更简单?这就涉及到堆的应用。我们所写的是大根堆,根结点是这组数据的最大值,弹出根节点后,向下调整可以得到次大值,然后再弹出根节点。就这样,我们就得到这组数据的前n个最大值,比如得到年纪前50名,得到产品销量前100名。由此可见,弹出根结点是非常有用的。
    代码实现
//堆空
bool HeapEmpty(HP* ph)
{
	assert(ph);
	return ph->size == 0;
}

//出堆
void HeapPop(HP* ph)
{
	assert(ph);
	//首先判断堆是否为空
	assert(!HeapEmpty(ph));
	//交换堆顶元素和最后一个元素
	Swap(&ph->a[0], &ph->a[ph->size - 1]);
	//删除最后一个元素
	ph->size--;
	//向下调整
	AdjustDown(ph->a, ph->size, 0);
	//第一个参数是要调整的数据,第二个参数是数据的个数,第三个参数是要调整的位置
}
  1. 向下调整
//向下调整(条件:左右子树都是大根堆或者小根堆)
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = 2 * parent + 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 = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

在这里插入图片描述

  1. 堆顶
//堆顶
HP* HeapTop(HP* ph)
{
	assert(ph);
	//首先判断堆是否为空
	assert(!HeapEmpty(ph));
	return ph->a[ph->size - 1];
}
  1. 打印前K个元素
//打印前K个元素
void PrintK(HP* ph, int k)
{
	while (!HeapEmpty(ph) && k--)
	{
		printf("%d ", HeapTop(ph));
		HeapPop(ph);
	}
	printf("\n");
}
  1. 销毁
//销毁
void HeapDestroy(HP* ph)
{
	assert(ph);
	free(ph->a);
	ph->a = 0;
	ph->capacity = 0;
	ph->size = 0;
}

2.3 堆排序

了解堆的基本操作后,就可以实现堆排序。

//法一
//堆排序(以排升序为例)
void HeapSort(int* a, int n)
{
	//建堆(大根堆或者小根堆)
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);//从下标为1的元素不断插入,从而向上调整
	}
	//建大根堆后,就交换根结点和最后一个元素,每次交换都将最大值放在数据的最后面
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);//交换最大值和最后一个元素
		AdjustDown(a, end, 0);//交换后最后一个元素不参与调整,所有元素个数只有end个
		//每次调整后都会将最大值放在根节点的位置
		--end;
	}
}
//法二
void HeapSort(int* a, int 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;
	}
}

时间复杂度
这两种方法唯一的不同就是法一前面是向上调整,法二前面是向下调整。要比较两种方法的时间复杂度就得比较向下调整和向上调整的时间复杂度。

  1. 向下调整的时间复杂度是O(N)
    在这里插入图片描述

  2. 向上调整的时间复杂度是O(NlogN)
    在这里插入图片描述
    咋一看好像第二种方法时间复杂度更牛,但实际上这两种方法的时间复杂度都是O(NlogN)。我们讨论的只是向下调整和向上调整。
    在这里插入图片描述
    总的来说,堆排序的时间复杂度是O(NLogN)。两种方法都在这个量级,但第二种方法稍微高一点点。


3. 堆排序(topK问题)

找出N个数中最大的(或最小的)前K个数。
有两种方法:
法一:建N个大根堆,然后PopK次就可以。
法二:建有K个数的小根堆,遍历剩下的数据,如果数据比堆顶元素大,就替换它,然后向下调整,最后这个小根堆的数据就是最大的前K个。
这里主要用法二

void PrintTopK(const char* file, int k)
{
	//首先要建立小根堆
	int* topk = (int*)malloc(sizeof(int) * k);
	if (topk == NULL)
	{
		perror("malloc failed");
		return;
	}
	//从文件中读取K个数据
	FILE* f = fopen(file, "r");
	if (f == NULL)
	{
		perror("fopen failed");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(f, "%d", &topk[i]);
	}
	//建小堆(之前写的向下调整是调整大根堆,只需修改其中一些>或者<即可)
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(topk, k, i);
	}
	//将剩下的n-k个元素依次与根节点比较,比它大就入堆且向下调整
	//这样将大的元素往下沉,小的元素不断换掉,最终只剩下最大的K个元素
	int val = 0;
	int ret = fscanf(f, "%d", &val);
	while (ret != EOF)
	{
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}
		ret = fscanf(f, "%d", &val);
	}
	for (int i = 0; i <k; i++)
	{
		printf("%d ", topk[i]);
	}
	fclose(f);
}
void CreateData()
{
	int n = 1000;
	srand(time(0));
	const char* file = "TestData.txt";
	FILE* f = fopen("TestData.txt", "w");//以写的形式打开
	if (f == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 1000;//获得0~999的数字
		fprintf(f, "%d ", x);
	}
	fclose(f);
}

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

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

相关文章

【5G RRC】NR 小区接入控制

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

Wind10安装cuDNN,几分钟搞定

cuDNN的安装过程 1、下载cuDNN 因为之前的博文“目标检测第3步”下载的CUDA版本是11.1&#xff0c;那么我们就要找到与CUDA11.1版本对应的cuDNN版本。地址为&#xff1a;cuDNN Archive | NVIDIA Developer 2、安装cuDNN 下载到的cuDNN文件是一个压缩包&#xff0c;解压缩之后…

并发编程03:Java锁

文章目录 3.1 乐观锁和悲观锁3.2 通过8种情况演示锁运行案例&#xff0c;看看锁到底是什么3.2.1 锁相关的8种案例演示code3.2.2 synchronized有三种应用方式3.2.3 从字节码角度分析synchronized实现3.2.4 反编译synchronized锁的是什么3.2.5 对于Synchronized关键字 3.3 公平锁…

创维高安版-E900-Hi3798MV100-强刷卡刷固件包-当贝纯净桌面

创维高安版-E900-Hi3798MV100-强刷卡刷固件包-当贝纯净桌面-内有主板图短接点和教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置…

关于百度地图开放平台api覆盖物“自定义Marker图标”不能正常显示的解决方案

“自定义Marker图标”不能正常显示&#xff08;用网上图片能正常显示&#xff0c;用本地图片就不能显示&#xff09;&#xff0c; 分两种情况&#xff1a; 1.网上图片&#xff0c;往往是图片的url地址有问题&#xff0c;也可能是url地址的图片失效了。 2.本地图片&#xff0c;这…

linux软件包管理和进程管理

目录标题 RPM管理工具rpm安装rpm查询功能rpm软件包升级、卸载 YUM管理工具建立本地光盘源配置互联网源yum工具管理软件包 ps指令动态查看进程top RPM管理工具 软件包&#xff08;本地–网络&#xff09;—安装&#xff08;软件包&#xff09;—卸载&#xff08;软件&#xff0…

【Python】【进阶篇】24、Django if标签详解

目录 24、Django if标签详解1. 模板标签1) 判断逻辑的 if 标签 24、Django if标签详解 本节继续讲解 Django 的模板语言&#xff0c;Django 内置了许多标签用于简化模板的开发过程&#xff0c;同时 Django 也支持自定义标签&#xff0c;这极大的方便了 Web开发者&#xff0c;下…

拓展系统变量

文章目录 拓展系统变量 使用方式拓展系统变量获取服务端IP - $ZSERVERIP获取客户端IP - $ZCLIENTIP获取最大许可数量 - $ZMAXLICENSE获取当前系统名称 - $ZOSNAME获取字符串最大长度 - $ZMAXSTRINGLEN获取当前登录用户ID - $ZUSERID获取当前登录用户名 - $ZUSERNAME系统最近错误…

【Linux从入门到精通】C语言模拟实现进度条小程序

在Linux下&#xff0c;我们安装软件时会经常看到进度条&#xff0c;来告知我们安装的进度。我们不妨自己模拟实现一个进度条&#xff0c;看看其中的细节。模拟实现进度条并不困难&#xff0c;但其中的细节我们又不可忽视。本篇文章会对模拟实现进度条进行详解。 文章目录 一、进…

顺序表(数据结构)---排队啦!

目录 前言&#xff1a; 1.线性表的性质 2.静态数组or动态数组 2.1静态数组 2.2动态数组 3.结构体的创建 4*接口函数的详细讲解 4.1初始化结构体 4.2尾插 4.3打印数据 4.4用完后销毁创建的堆空间 4.5 尾删 4.6头插 4.7头删 4.8查找 4.9任意位置插入 4.10任意位…

springboot+jsp商务安全邮箱(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot商务安全邮箱。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&…

谈谈Edge浏览器新出的分屏功能

谈谈Edge浏览器新出的分屏功能 前言 在 2023 年三月份微软为 Microsoft Edge 浏览器的稳定版本带来了一个新功能 —— 分屏浏览 (Split Screen)&#xff0c;此功能允许用户在当前页面以左右视图的形式并排打开两个标签页面&#xff0c;作用上类似于应用的分屏可以让浏览器同时处…

Kali Linux部署qemu虚拟化启动img镜像文件

一、先下载最新版本的Kali环境 Kali Linux官网下载网址&#xff1a;Get Kali | Kali Linux 安装到VMware里面后&#xff0c;调整内存大小为4G&#xff08;如果自己电脑内存32G的话&#xff0c;可以调整为8G&#xff09; 更新一下Kali Linux源 然后安装如下软件 apt install qe…

二十九、交换机堆叠与集群

文章目录 堆叠技术概述一、可靠组网二、堆叠技术名称三、华为堆叠原理1、基本概念2、堆叠端口&#xff1a;&#xff08;逻辑端口&#xff09;3、堆叠拓扑类型4、堆叠硬件要求 四、堆叠配置示例&#xff08;华三模拟器&#xff09;1、sw1&#xff1a;2、sw2&#xff1a;3、激活i…

弹射起步——pythonweb开发Flask框架,前端原生+Flask后端框架+mysql数据库实战(附带小案例)

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

HCT:深度是我们没有的奢侈品

文章目录 Deep is a Luxury We Don’t Have摘要本文方法Efficient AttentionThe HCT Architecture Deep is a Luxury We Don’t Have 摘要 医学图像具有高分辨率。高分辨率对于早期发现恶性组织至关重要。然而&#xff0c;这一解决方案在建模长期依赖性方面提出了挑战。浅层t…

接口自动化测试的神器:使用Python编写高效的自动化测试工具

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 摘要&#xff1a; 安装工具&#xff1a; 测试脚本 编写python脚本 1.使用requests发送HTTP请求 2.使用py…

生产环境出现CPU占用过高,请谈谈你的分析思路和定位

假如生产环境出现CPU占用过高&#xff0c;请谈谈你的分析思路和定位 记一次印象深刻的故障&#xff1f; 结合Linux 和 JDK命令一起分析&#xff0c;步骤如下 使用top命令找出CPU占比最高的 ps -ef 或者 jps 进一步定位&#xff0c;得知是一个怎么样的后台程序出的问题 定位…

夏驰和徐策的解决数学问题思路——反证法

反证法是一种证明方法&#xff0c;它的基本思路是通过假设某个结论不成立&#xff0c;然后构造出一个矛盾的情况来推导出原先假设的结论是成立的。 具体来说&#xff0c;反证法一般包含以下步骤&#xff1a; 1. 假设所要证明的命题不成立。 2. 通过这个假设&#xff0c;构造…

网易云音乐开发--个人中心页效果实现

内网穿透 就是我们真机调试&#xff0c;是没有数据的 就是我们手机上去访问我们电脑上自己搭的服务器&#xff0c;肯定是访问不到的 此时就需要我们内网穿透 1.winR 输入 cmd 输入ipconfig 2.找到无线局域网适配器的IPv4 3.重新设置一个新的地址&#xff0c;只需将host中…