选择排序(堆排序和topK问题)

选择排序

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

如果我们用扑克牌来举例,那么选择排序就像是提前已经把所有牌都摸完了,而再进行牌之间的排序;而插入排序则是边摸边排。

直接选择排序

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

那么下面我先将代码展示给大家,然后再为大家讲解其中奥妙

void SelectSort(int* a, int n) {
	int begin = 0;
	int end = n - 1;
	while (begin < end) {
		int min = begin;
		int max = begin;
		for (int i = begin + 1; i <= end; i++) {
			if (a[i] < min) {
				min = i;
			}

			if (a[i] > max) {
				max = i;
			}
		}
		Swap(&a[begin], &a[min]);
		if (max == begin)
		{
			max = min;
		}
		Swap(&a[end], &a[max]);
		begin++;
		end--;
	}
}

首先呢,我们先把起始位置的下标和最后位置的下标给记录下来,并将最小值和最大值的下标都初始化为begin,外面再套上一层循环,限制条件为begin<end,当两个下标走到一起或者错开时,就会结束循环,也就排好了。

而while循环里面的才是排序的逻辑部分,for循环从begin的下一个位置开始,到end的位置结束,并在其中进行比较,改变每一次循环中最大值和最小值的下标,并在循环结束后交换最小值和begin下标值的位置,最大值与end下标值的位置,最后begin和end都往中间走,开始下一轮循环

不过需要注意的是,我们加入了一个if判断语句:其实这是为了防止最大值就在begin下标时,原来的最大值会和最小值交换位置,然后最小值会被换到end的位置上成为最大值,那样子的话就会出现错误,排序便失败了;但加上这个之后,在第一次交换过后,max的值到了min的下标,这个时候只需要把max下标也改为min,这个时候替换就不会再把最小值给替换到最后,而是最大值了。这样讲可能也有点绕,给大家画个图便于理解。
在这里插入图片描述
相信大家根据函数看就可以看懂啦!还是很好理解的!

堆排序

相比于刚才的直接选择排序,想必当然还是堆排序更加吸引大家的注意,那就让我们开始学习吧!

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

代码如下~

void HeapSort(int* a, int n) {
	for (int i = 0; i < n; i++) {
		AdjustUp(a, i);//建大堆
	}

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

虽然堆排序的本体很小,但是千万不能忽视了向上调整算法和向下调整算法,所以还是把这一串代码附在下面

void AdjustUp(int* a, int child) {
	int parent = (child - 1) / 2;
	while (child > 0) {
		if (a[child] > a[parent]) {
			Swap(&a[child], &a[parent]);
		}

		else {
			break;//这里没必要return
		}

		child = parent;
		parent = (parent - 1) / 2;
	}
}

void AdjustDown(int* a, int size, int parent) {
	int child = parent * 2 + 1;//假设左子节点小于右子节点
	//右子节点不一定有,可能会越界
	while (child < size) {
		if (child + 1 < size && a[child + 1] > a[child]) {
			child++;//其实就是左子节点转换到右子节点上
		}

		if (a[child] > a[parent]) {
			Swap(&a[child], &a[parent]);
			parent = child;//parent移动到原来child的位置上
			child = child * 2 + 1;//child来寻找自己的下一个左子节点
		}

		else {
			break;
		}
	}
}

虽然堆排序中有堆,但是我们不可能真的建一个堆然后再进行排序,毕竟手搓一个堆的函数还是挺麻烦的,所以我们本质上是模拟堆插入的过程建堆,并利用其逻辑对数组中的元素进行排序,我们还是用例子说话。并且在建堆之前还有一个需要注意的,因为现在给的例子是以升序排列,所以我们现在建立的是大堆(需要在向上调整算法和向下调整算法中改变大于小于符号)

建立大堆的原因还有一个,那就是如果建立小堆的话,当删除堆顶元素(最小值)时,剩下的数还看作堆的话,关系就全乱了,需要重新建堆,浪费时间。

第一步:建堆
在这里插入图片描述
第二步:排序
其实就是将end定为数组的最后一个下标n-1,然后堆顶元素和最后一个元素交换,向下调整之后,删除最后一个元素,最后end走到0下标的时候就结束,写一两步大家看看
在这里插入图片描述
实际上,虽然在堆中删除了,但我们直到此时9已经到了n-1下标的位置,也就是排在了最大值的位置上。而向下调整之后,我们会发现,8又到了最上方,并且也是目前的最大值,也就是下一次,8会与2交换,成为次大的值;2又与7交换,2又与6交换,那么很明显,下一次循环交换的数就是7了,之后就是6,这样,最大值就慢慢的被调节到了end的位置,最后数组中的元素都正序排列。

优化

除了使用向上调整建堆,其实我们还可以使用向下调整建堆,进行讲解后,大家甚至还会发现向下调整更加简洁方便

for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

以上便是将向上调整改为向下调整算法后的函数,为什么从(n-1-1)/2开始呢,是因为n-1是最后一个元素的下标,而(n-1-1)/2则是找到其父节点,然后从底端进行调整。

而至于为什么向下建堆更简洁呢?给大家用数学写写,大家就懂啦!

在这里插入图片描述
在这里插入图片描述
eg.n=2^h-1上面的T(n)都是T(h),到下面才是T(n),写错了QAQ
由此可以看出,向下调整建堆的时间复杂度为O(n),下面我们计算向上调整建堆的时间复杂度

在这里插入图片描述
由此可知:向上调整建堆的时间复杂度为O(nlogn),是大于向下调整建堆的,这样子的话,我们以后如果使用堆排序,我们就可以直接忽略向上调整算法,只写向下调整算法,代码量可以更少,时间复杂度也更精简

topK问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
void PrintTopK(int* a, int n, int k) {
    int* heap = (int*)malloc(sizeof(int) * k);
    if (heap == NULL) {
        perror("malloc fail");
        return;
    }

    for (int i = 0; i < k; ++i) {
        heap[i] = a[i];
        AdjustUp(heap, i);//先用前k个数创建一个小堆
    }

    for (int i = k; i < n; ++i) {
        if (a[i] > heap[0]) {
            heap[0] = a[i];//从k下标开始遍历数据,如果大于heap[0],就让其成为heap[0]
            AdjustDown(heap, k, 0);//然后向下调整
        }
    }

    for (int i = 0; i < k; ++i) {
        printf("%d ", heap[i]);//打印最大的k个数
    }
    printf("\n");

    free(heap);//记住释放heap开辟的内存
}

我们还是照样举一个例子,虽然topK是在n大于k很多的情况下才使用的,但为了看上去简单,我们选择两个相近的n与k
在这里插入图片描述
我们在插入后进行了两次向下调整,由此可知,当我们进行完所有的向下调整之后,留在k个元素小堆中的元素一定是最大的几个

当然,除了从数组中取出的方法,我们还可以写出一种从文件中拿出数据并排序的topK函数,大家请看!

void CreateNDate()
{
	// 造数据
	int n = 10000000;  // 设置要生成的数据数量
	srand(time(0));  // 使用当前时间作为随机数种子,确保每次运行生成的随机数不同

	const char* file = "data.txt";  // 指定数据文件的名称
	FILE* fin = fopen(file, "w");  // 以写模式打开文件
	if (fin == NULL)
	{
		perror("fopen error");  // 输出文件打开错误信息
		return;
	}

	// 随机生成n个整数,并将其写入文件
	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 10000000;  // 生成0到9999999之间的随机整数,加i是因为随机数最多只可以生成3万多个,会有重复的,这样能保证重复率大大降低
		fprintf(fin, "%d\n", x);  // 将随机数写入文件
	}

	fclose(fin);  // 关闭文件
}
void PrintTopK(const char* file, int k)
{
	FILE* fout = fopen(file, "r");  // 以只读模式打开文件
	if (fout == NULL)
	{
		perror("fopen error");  // 输出文件打开错误信息
		return;
	}

	// 建一个k个数小堆
	int* minheap = (int*)malloc(sizeof(int) * k);  // 分配大小为k的整型数组内存
	if (minheap == NULL)
	{
		perror("malloc error");  // 输出内存分配错误信息
		return;
	}

	// 读取前k个数,构建最小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);  // 从文件中读取整数,构建最小堆
		AdjustUp(minheap, i);  // 执行向上调整,维护最小堆性质
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)  // 从文件中读取整数,直到文件结束
	{
		if (x > minheap[0])  // 如果当前数字大于堆顶元素
		{
			minheap[0] = x;  // 将堆顶元素替换为当前数
			AdjustDown(minheap, k, 0);  // 执行向下调整,维护最小堆性质
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);  // 输出最小堆中的前k个元素
	}
	printf("\n");

	free(minheap);  // 释放动态分配的堆内存
	fclose(fout);  // 关闭文件
}

以上就是选择排序中的几个问题,下一节排序,我们讲解的是交换排序,欢迎大家持续收看!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

消息中间件之RocketMQ(三)

常见问题 1.重复消费 产生的原因是发送消息时采用了多数分布式消息中间件产品提供的最少一次(at least once)的投递保障&#xff0c;对于这个问题最常见的解决方案,就是消息消费端实现业务幂等&#xff0c;只要保持幂等性&#xff0c;不管来多少条重复消息&#xff0c;最后处…

视频监控方案设计:EasyCVR视频智能监管系统方案技术特点与应用

随着科技的发展&#xff0c;视频监控平台在各个领域的应用越来越广泛。然而&#xff0c;当前的视频监控平台仍存在一些问题&#xff0c;如视频质量不高、监控范围有限、智能化程度不够等。这些问题不仅影响了监控效果&#xff0c;也制约了视频监控平台的发展。 为了解决这些问…

【LMDeploy 大模型量化部署实践】学习笔记

参考学习教程【LMDeploy 的量化和部署】 理论 作业 使用 LMDeploy 以本地对话、网页Gradio、API服务中的一种方式部署 InternLM-Chat-7B 模型&#xff0c;生成 300 字的小故事 本地对话 API服务 Client 命令 端口转发 网页Gradio

C语言每日一题(48)回文链表

力扣 234 回文链表 题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1…

【渗透测试】借助PDF进行XSS漏洞攻击

简介 在平时工作渗透测试一个系统时&#xff0c;常常会遇到文件上传功能点&#xff0c;其中大部分会有白名单或者黑名单机制&#xff0c;很难一句话木马上传成功&#xff0c;而PDF则是被忽略的一个点&#xff0c;可以让测试报告更丰富一些。 含有XSS的PDF制作步骤 1. 编辑器…

JavaSE基础学习

一、编程入门 二、Java语言概述 三、Java基本语法 四、程序流程控制 五、数组 六、面向对象(上) 数组工具类的封装: 七、面向对象(中) 八、面向对象(下) 九、异常处理 十、多线程 十一、常用类 十二、枚举类与注解 十三、集合 十四、泛型 十五、IO流 十六、网络编程 十七、反射…

Linux-----Shell编程之循环语句

一、小命令 1、echo echo -n 表示不换行输出 echo -e 表示输出转义符 常用的转义符 选项作用\r光标移至行首&#xff0c;并且不换行\s当前shell的名称&#xff0c;如bash\t插入Tab键&#xff0c;制表符\n输出换行\f换行&#xff0c;但光标仍停留在原处\表示插入"\&qu…

Idea上操作Git回退本地版本,怎么样保留已修改的文件,回退本地版本的四种方式代表什么?

Git的基本概念:Git是一个版本控制系统,用于管理代码的变更历史记录。核心概念包括仓库、分支、提交和合并。 1、可以帮助开发者合并开发的代码 2、如果出现冲突代码的合并,会提示后提交合并代码的开发者,让其解决冲突 3、代码文件版本管理 问题描述 当我们使用git提交代码…

unity 装饰器模式(实例详解)

文章目录 简介1. **组件装饰器&#xff08;Component Decorators&#xff09;**:2. **游戏对象特效装饰器&#xff08;GameObject Effects Decorator&#xff09;**:3. **输入处理装饰器&#xff08;Input Handling Decorators&#xff09;**:4. **性能优化装饰器&#xff08;P…

2022年至2023年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题

2022 年至 2023 年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题 一、 第一阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛第一阶段试题&#xff0c;第一阶段内容包 括&#xff1a;网络平台搭建、网络安全设备配置与防护。 本阶段比赛时间为 180 分钟…

Make.com的发送邮件功能已经登峰造极

make.com的发送邮件功能已经做到了登峰造极。 我给你个任务&#xff0c;让你发送个新邮件给谁谁&#xff0c;你一定想到SMTP服务器不就行了。 我给你第二个任务&#xff0c;我让你自动回复一个邮件&#xff0c;注意是回复。 做不到了吧&#xff5e;&#xff5e;&#xff01;…

[C#]winform部署yolov7+CRNN实现车牌颜色识别车牌号检测识别

【官方框架地址】 https://github.com/WongKinYiu/yolov7.git 【框架介绍】 Yolov7是一种目标检测算法&#xff0c;全称You Only Look Once version 7。它是继Yolov3和Yolov4之后的又一重要成果&#xff0c;是目标检测领域的一个重要里程碑。 Yolov7在算法结构上继承了其前…

C++ 设计模式之责任链模式

【声明】本题目来源于卡码网&#xff08;卡码网KamaCoder&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 --什么是责任链模式&#xff08;第21种设计模式&#xff09; 责任链模式是⼀种行为型设计模式&am…

计算机工作原理解析和解剖(基础版)

我们会从软件⼯程师的⻆度解释计算机是如何⼯作的&#xff0c;我们的主要⽬标既不是期待 ⼤家可以造出⾃⼰的计算机&#xff0c;也不是介绍如何编程&#xff0c;⽽是希望让⼤家了解计算机的核⼼⼯作机制后&#xff0c;打破计算机的神秘感&#xff0c;并且有利于理解我们平时编程…

心理学大纲

简介 psychology&#xff0c;“psyche”(ψυχή):意为"soul"(灵魂)&#xff0c;即对我们灵魂的研究 我的学习的目的 了解人精神世界的模型&#xff0c;人格的形成]&#xff0c;作为观察分析他人内心的理论指导&#xff0c;便于我实践了解情绪的机理&#xff0c;…

vite项目创建

1.使用命令创建一个vite项目 npm init vuelatest vite.config.js配置 import { fileURLToPath, URL } from "node:url";import { defineConfig, loadEnv } from "vite"; import vue from "vitejs/plugin-vue"; export default defineConfig(({…

ZK监控方法以及核心指标

文章目录 1. 监控指标采集1.1 zk版本高于3.6.0监控指标采集1.2 zk版本低于3.6.0监控指标采集1.3 配置promethues采集和大盘 2. 核心告警指标3. 参考文章 探讨zk的监控数据采集方式以及需要关注的核心指标&#xff0c;便于日常生产进行监控和巡检。 1. 监控指标采集 3.6.0 版本…

哪吒汽车与经纬恒润合作升级,中央域控+区域域控将于2024年落地

近日&#xff0c;在2024哪吒汽车价值链大会上&#xff0c;哪吒汽车与经纬恒润联合宣布合作升级&#xff0c;就中央域控制器和区域域控制器展开合作&#xff0c;合作成果将在山海平台新一代车型上发布。 哪吒汽车首席技术官戴大力、经纬恒润副总裁李伟 经纬恒润在智能驾驶领域拥…

Java入门高频考查基础知识6-深入挖掘Java集合框架的奇幻世界(45题3.6万字参考答案)

在Java编程语言中&#xff0c;集合&#xff08;Collection&#xff09;指的是存储一组对象的容器。Java提供了一套丰富的集合框架&#xff0c;以及包含在Java标准库中的集合类。这些集合类提供了各种功能和操作&#xff0c;可以方便地对一组对象进行管理和操作。 目录 一、集合…

VUE引入DataV报错记录

DataV官网&#xff08;不支持Vue3&#xff09;&#xff1a;Welcome | DataV 一、按照官网引入后报错 【1】 Failed to resolve entry for package "dataview/datav-vue3". The package may have incorrect main/module/exports specified in its package.json. 将…