当前位置: 首页 > news >正文

【数据结构】堆排序与TOP-K问题

🌈个人主页:Yui_
🌈Linux专栏:Linux
🌈C语言笔记专栏:C语言笔记
🌈数据结构专栏:数据结构

文章目录

  • 1.堆排序
    • 1.1 建堆
    • 1.2 利用堆删除思想来进行排序
    • 1.3 堆排序的时间复杂度
  • 2.TOP-K问题

1.堆排序

堆排序就是利用堆的思想进行排序,总共分为两个步骤:

1.1 建堆

  • 升序:建大堆
  • 降序:建小堆
    利用向下调整建堆
    提问:为什么向下调整可以建堆
    回答:
    向下调整的关键就除了要调节的地方不对,其下方都为正确的堆。
    以下图为例:以10为根的左右子树,都满足小堆(堆)的性质,只有根节点不满足时,因此只需将根节点往下调,整合到合适的位置就可以了。
    向下调整

为了我只有由后往前向下调整就可以了,第一次要传的参数就是最后一个节点的父节点。然后依次传入前面是位置就可以了。

#include <stdio.h>void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//建大堆
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] > a[child]){child = child + 1;}if (a[child] > a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}int main()
{int arr[] = { 2,3,5,7,4,6,8,65,100,70,32,50,60 };//初始化一个小堆int n = sizeof(arr) / sizeof(arr[0]);for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(arr, n, i);//通过向下调整建立大堆}for (int i = 0; i < n; ++i){printf("%d ", arr[i]);}return 0;
}
//打印结果:
/*
100 70 60 65 32 50 8 3 7 4 2 5 6
*/

1.2 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整就可以完成堆排序。
提问:为什么升序需要建大堆呢?
回答:堆排序的本质是选择排序,每次都要选择一个最大的数到数组的最后一位,那么问题就变成了如何选择最大的数到数组最后一位,要找出堆中最大数就必须要用到大堆,因为大堆中最大的数就在堆堆顶,通过一次次的选择就可以将数组排序。
下面是画图解释:
利用堆删除思想来进行排序

#include <stdio.h>void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//建大堆
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] > a[child]){child = child + 1;}if (a[child] > a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}int main()
{int arr[] = { 2,3,5,7,4,6,8,65,100,70,32,50,60 };int n = sizeof(arr) / sizeof(arr[0]);for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(arr, n, i);}//排序for (int end = n - 1; end >= 0; --end){swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);}for (int i = 0; i < n; ++i){printf("%d ", arr[i]);}return 0;
}
//打印结果:
/*
2 3 4 5 6 7 8 32 50 60 65 70 100
*/

1.3 堆排序的时间复杂度

O(N*logN)

2.TOP-K问题

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

  1. 用数据集合前K个元素来建堆
  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆
  1. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完后,堆中剩余的K个元素就是所求得前K个最小或者最大的元素。

下面我们将找出1000000个数中最大的10个数,为此我们需要建造一个大小为10的小堆。
主要步骤:

  1. 创建1000000个随机数,数值范围[0,999999]
  2. 创建一个大小为10的小堆
  3. 开始进堆,如果当前数值大于堆顶数据,就将堆顶数值替换为当前数值,然后向下调整,让大的数下沉。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//建小堆
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){child = child + 1;}if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}int main()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(NULL));//生成随机种子for (int i = 0; i < n; ++i){a[i] = rand() % 1000000;//数据范围:[0,999999]}//选出数据中最大的10个数,建小堆//为了证明确实可以找到,我们需要自己设置10个大于范围的数,如果最后被找到就说明正确a[5] = 1000000 + 1;a[55] = 1000000 + 2;a[99] = 1000000 + 3;a[1001] = 1000000 + 4;a[123] = 1000000 + 5;a[124] = 1000000 + 6;a[18] = 1000000 + 7;a[1111] = 1000000 + 8;a[999] = 1000000 + 9;a[100] = 1000000 + 10;int heap[10] = { 0 };for (int i = 0; i < n; ++i){if (a[i] > heap[0]){heap[0] = a[i];AdjustDown(heap, 10, 0);}}for (int i = 0; i < 10; ++i){printf("%d ", heap[i]);}return 0;
}
//打印结果:
/*
1000001 1000002 1000003 1000004 1000009 1000006 1000005 1000007 1000008 1000010
*/

最后的结果确实是我能自己修改的数,说明查找成功。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Naive UI+vue一些组件的注意事项
  • element plus el-select修改后缀图标
  • 【双向链表】的建立、插入、删除、查找和销毁
  • 量化策略开发步骤系列(3)关键投资组合指标
  • firefly推理和微调qwen
  • Appium基础
  • 背包九讲(动态规划)
  • IO流(完善)
  • 2.4 playwright 实战-爬取某宝商品信息
  • 四款录屏大师,一键搞定!新手也能快速上手?
  • Python数值计算(24)——PCHIP
  • Chapter 9 Operational Amplifiers
  • 快速上手Spring Boot
  • 6G:融合5G与AI,重塑网络交互与智能决策的未来
  • NB模组AT 命令用法记录
  • 77. Combinations
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • create-react-app项目添加less配置
  • exif信息对照
  • git 常用命令
  • Git同步原始仓库到Fork仓库中
  • mockjs让前端开发独立于后端
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 对象管理器(defineProperty)学习笔记
  • 机器学习学习笔记一
  • 试着探索高并发下的系统架构面貌
  • 云大使推广中的常见热门问题
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #pragma预处理命令
  • #Z0458. 树的中心2
  • (2)空速传感器
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (55)MOS管专题--->(10)MOS管的封装
  • (Java入门)抽象类,接口,内部类
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (笔试题)合法字符串
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (十三)Flink SQL
  • (实战篇)如何缓存数据
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)EOS中账户、钱包和密钥的关系
  • (转)mysql使用Navicat 导出和导入数据库
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .NET Core中如何集成RabbitMQ
  • .NET/C#⾯试题汇总系列:⾯向对象
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • .Net转前端开发-启航篇,如何定制博客园主题
  • /bin/bash^M: bad interpreter: No such file or directory
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • @ConfigurationProperties注解对数据的自动封装
  • [20190113]四校联考
  • [Android]常见的数据传递方式