堆,堆排序和TOP—K问题(C语言版)

前言

        堆是一种重要的数据结构,堆分为大根堆和小根堆,大根堆堆顶的数据是最大的,小根堆堆顶的数据是最小的,堆在逻辑结构上是一颗完全二叉树,这棵树中如果满足根节点大于左右子树,每个节点都满足这个条件就是大根堆,反之就是小根堆。

1.堆的概念和性质

        堆标准的概念是:如果有一个关键码的集合K = {k0,k1,k2,...,kn-1},把它的所有元素按照完全二叉树的顺序存储方式存储在一个数组中,并且满足: i = 0,1,2..,则称为小堆(或大堆)。将根节点最大的堆称为最大堆或者大根堆,根节点最小的堆叫做最小堆或者小根堆。 

         堆的性质:

        1.堆中某个节点的值总是不大于或者不小于其父节点的值

        2.堆是一颗完全二叉树

        从堆是一颗完全二叉树来理解堆是更容易理解的。

  

2.堆的实现

        2.1向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过根节点开始的向下调整算法可以把它调整为一个小堆。向下调整算法的前提是左右子树都必须是小堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

         

        2.2堆的创建

        下面我们给出一个数组,这个数组在逻辑上可以看出一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点的左右子树不是堆,我们怎么调整呢?我们可以从叶子节点开始调整,但是没有必要,因为每个叶子结点都可以看成一个堆。我们可以从倒数第一个非叶子节点开始调整,一直调整到根节点的树,就可以调成一个堆。 

        int a[] = {1,5,3,8,7,6};

          

        2.3建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值) ,多几个节点没有影响):

        假设树的高度为h

        第一层有2的零次方个节点,需要向下移动h - 1层

        第二层有2的一次方个节点,需要向下移动h - 2层

        第三层有2的二次方个节点,需要向下移动h - 3层

        第四层有2的三次方个节点,需要向下移动h - 4层

        第h - 1层有2的h - 2次方个节点,需要向下移动1层

        则需要移动节点总的移动步数为:

        因此:建堆的时间复杂度为O(N)

        2.4堆的插入

        堆的插入要插入到数组的末尾,在进行向上调整算法,直到满足堆的特性。

  

        2.5堆的删除

        删除堆,删除的是堆顶的元素,如果直接删除好吗?

        答案是否定的,直接删除堆顶的数据,这个堆就废了,需要重新建堆,所以正确的操作是运用先交换堆顶和堆最后一个元素,进行一次向下调整即可解决问题。 

        2.6堆的代码实现

        //Heap.h

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>
#include<string.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//存储数据int _size;int _capacity;
}Heap;
void HeapSort(int* a, int size);//堆排序
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法void HeapInit(Heap* php, HPDataType* a, int n);//初始化堆void HeapDestory(Heap* php);//销毁队void HeapPush(Heap* php, HPDataType x);//在堆里面入数据void HeapPop(Heap* php);//出堆顶的数据HPDataType HeapTop(Heap* php);//获取堆顶的数据

        //Heap.c 

#include"Heap.h"
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法void Swap(HPDataType* left, HPDataType* right)
{HPDataType tmp = *left;*left = *right;*right = tmp;
}
void HeapSort(int* a, int size)//堆排序
{//建堆int root = (size - 1 - 1) / 2;//找到非叶子结点while (root >= 0){ADJustDown(a, root, size);--root;}//将堆顶的数据与堆底的数据交换int end = size - 1;while (end > 0){Swap(&a[0], &a[end]);//向下调整ADJustDown(a, 0, end);end--;}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;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;child = parent * 2 + 1;}else{break;//不需要调整}}
}
void ADJustUp(HPDataType* a, int child)//向上调整算法
{assert(a);//确保指针有效int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//交换父子节点//迭代向后走child = parent;parent = (child - 1) / 2;}else{//结束调整break;}}
}void HeapInit(Heap* php, HPDataType* a, int n)//初始化堆
{php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);php->_capacity = php->_size = n;memcpy(php->_a, a, sizeof(HPDataType)*n);//建队=堆int root = (n - 1 - 1) / 2;//找到非叶子结点while(root >= 0){ADJustDown(php->_a, root, n);--root;}
}
void HeapDestory(Heap* php)//销毁队
{assert(php);//堆存在free(php->_a);php->_size = php->_capacity = 0;
}
void HeapPush(Heap* php, HPDataType x)//在堆里面入数据
{//判断是不是需不需要增容if (php->_capacity == php->_size){php->_capacity *= 2;HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);if (tmp == NULL){printf("申请内存失败\n");exit(-1);}php->_a = tmp;}//插入数据php->_a[php->_size] = x;php->_size++;//向上调整ADJustUp(php->_a, php->_size - 1);
}
void HeapPop(Heap* php)//出堆顶的数据
{assert(php);//确保堆不为空assert(php->_size > 0);//确保堆里面存在数据//为了保持堆的特性,需要先将堆顶的数据与堆底的数据交换,然后pop调堆底的数据//在对堆顶开始进行一次向下调整if (php->_size > 1){Swap(&php->_a[0], &php->_a[php->_size - 1]);php->_size--;ADJustDown(php->_a, 0, php->_size);}else if (php->_size == 1){php->_size--;}
}
HPDataType HeapTop(Heap* php)//获取堆顶的数据
{assert(php);//指针存在assert(php->_size > 0);//堆里面有数据return php->_a[0];
}

3.堆的应用

        3.1堆排序

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

        1.建堆

        如果是排升序,是建大堆还是小堆呢? 如果是排降序呢?

        如果排升序,建小堆的话,每次选出最小的数以后,整个堆就不能用来,就要重新建堆,所以,排升序要建大堆,每次选出最大的数放在数组的最后,堆的大小减一,调用一次向下调整就可以再选出堆里面最大的数了。利用这样的方法就可以实现堆排序了。 

        2.利用堆删除的思想进行排序

        建堆和堆删除中都用到了向下调整算法,因此掌握了向下调整算法就掌握了和堆相关的大部分内容了 ,堆就是这么简单又朴实无华,哈哈哈哈。

 

        

void HeapSort(int* a, int size)//堆排序
{//建堆int root = (size - 1 - 1) / 2;//找到非叶子结点while (root >= 0){ADJustDown(a, root, size);//调用向下调整算法--root;}//将堆顶的数据与堆底的数据交换int end = size - 1;while (end > 0){Swap(&a[0], &a[end]);//向下调整ADJustDown(a, 0, end);end--;}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;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;child = parent * 2 + 1;}else{break;//不需要调整}}
}

        3.2TOP-K问题

         TOP-K问题:即求数据集合中前k个最大或者最小的元素,一般情况下数据量会很大。

        比如:专业前10名,世界500强,富豪榜,游戏中前100的活跃玩家等等。

        对于TOP-K问题,能想到的最简单的方式就是排序,但是如果数据量很大,排序就不太可取了(数据可能无法加载到内存中,数据太多了)。最佳的解决方式就是用堆来解决,基本思路如下:

        1.用数据前K个元素来建堆

        如果是求前K大的数,就建一个小堆,如果堆顶的数比剩下的数小就替换堆顶的数据。直到比较完所有的数据。

        如果是求前K小的数据,就建一个大堆,如果堆顶的数比剩下的数大就将堆顶的数替换为正在比较的数,直到比较完所有的数据。

        形象一点来说堆顶数就像是守门员一样,到最后堆顶的数肯定是前K小的数或者前K大数。

        2.用剩余的K-N个元素来和堆顶的数据进行比较,不满足则替换堆顶的元素

将剩余N-K个元素依次与堆顶的元素比较完后,堆里面剩余的K个元素就是所求的前K个最小或者最大的元素。 

        它的时间复杂度是N*log(K)。 

        TOP-K问题

        代码:

void Swap(int* left, int* right)
{int tmp = *left;*left = *right;*right = tmp;
}
void AdJustDown(int* a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;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;child = parent * 2 + 1;}else{break;//不需要调整}}
}
int findKthLargest(int* nums, int numsSize, int k)
{//使用向下调整算法进行建堆int root = (k - 1) / 2;//找到倒数第一个非叶子节点while(root >= 0)//对前k个数组元素建小堆{AdJustDown(nums, root, k);--root;}for(int i = k; i < numsSize; ++i){if(nums[0] < nums[i]){//取代堆顶的数据,进行向下调整int tmp = nums[0];nums[0] = nums[i];nums[i] = tmp;AdJustDown(nums, 0, k);}}for(int i = 0; i < numsSize; ++i){printf("%d ",nums[i]);}return nums[0];//此时堆顶的元素就是第K大的元素
}

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

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

相关文章

Rabbitmq的Shovel

Federation 具备的数据转发功能类似&#xff0c; Shovel 够可靠、持续地从一个 Broker 中的队列 ( 作为源端&#xff0c;即source)拉取数据并转发至另一个 Broker 中的交换器 ( 作为目的端&#xff0c;即 destination) 。作为源端的队列和作为目的端的交换器可以同时位于…

微服务dubbo和nexus

微服务是一种软件开发架构风格&#xff0c;它将一个应用程序拆分成一组小型、独立的服务&#xff0c;每个服务都可以独立部署、管理和扩展。每个服务都可以通过轻量级的通信机制&#xff08;通常是 HTTP/REST 或消息队列&#xff09;相互通信。微服务架构追求高内聚、低耦合&am…

Linux--VMware的安装和Centos

一、VMware和Linux的关系 二、VMware的安装 VM_ware桌面虚拟机 最新中文版 软件下载 (weizhen66.cn) VMware-Workstation-Lite-16.2.2-19200509-精简安装注册版.7z - 蓝奏云 如果安装不成功&#xff0c;则设置BIOS 三、在VMware中加入Centos 下载地址&#xff1a; CentOS-…

Linux 忘记密码解决方法

很多朋友经常会忘记Linux系统的root密码&#xff0c;linux系统忘记root密码的情况该怎么办呢&#xff1f;重新安装系统吗&#xff1f;答案是不需要进入单用户模式更改一下root密码即可。 步骤如下&#xff1a; 重启linux系统 3 秒之内要按一下回车&#xff0c;出现如下界面 …

unity-AI自动导航

unity-AI自动导航 给人物导航 一.地形创建 1.首先我们在Hierarchy面板中创建一个地形对象terrian&#xff0c;自行设定地形外貌&#xff0c;此时我们设置一个如下的地形外观。 二.创建导航系统 1.在主人公的Inspector、面板中添加Nav Mesh Agent &#xff08;导航网格代理&…

Linux操作系统--linux概述

1.Linux概述 Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff08;OS&#xff09;。简单的说就是一种操作系统。在日常中常见的操作系统有一下三种: 2.linux起源和背景 (1).linux的诞生 linux操作系统是由李纳斯托瓦兹&#xf…

C++ 多重继承

所谓多重继承就是一个儿子有好几个爹&#xff0c;然后一个人继承了这几个爹的财产。只需注意构造顺序即可&#xff0c;反正析构的顺序也是一样的。 #include <iostream> #include <string.h> using namespace std;class base_a { public:base_a(const char *str){…

二十、观察者模式

一、什么是观察者模式 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&…

2007-2022年上市公司污染排放数据/2007-2022年上市公司污染排放水平、污染排放量数据

2007-2022年上市公司污染排放数据/2007-2022年上市公司污染排放水平、污染排放量数据 1、时间&#xff1a;2007-2022年 2、指标&#xff1a;证券代码、year、化学需氧量、氨氮排放量、总氮、总磷、水体综合污染当量对数、二氧化硫、氮氧化物、烟尘、空气综合污染当量对数、总…

静态类方法的同步

由于在调用静态方法时&#xff0c;对象实例不一定被创建。因此&#xff0c;就不能使用this来同步静态方法&#xff0c;而必须使用Class对象来同步静态方法。代码如下&#xff1a; 通过synchronized块同步静态方法 public class StaticSyncBlock { public static void…

word表格左侧边线明明有,但却不显示

如题&#xff0c;解决方法&#xff1a; 方案一&#xff1a; 1&#xff09;选中表格 2&#xff09;布局菜单--->自动调整 3&#xff09;自动调整中&#xff0c;选择“根据窗口自动调整表格” 4&#xff09;表格左侧边线就显示出来了。 方案二&#xff1a;把表格粘贴到新…

Kubernetes技术--Kubernetes架构组件以及核心概念

1.Kubernetes集群架构组件 搭建一个Kubernetes环境集群,其架构如下所示: 内容详解: Master:控制节点,指派任务、决策 Node:工作节点,实际干活的。 Master组件内容:

国产芯片设备达到3纳米,还打入台积电,美日荷被彻底赶出市场

由于众所周知的原因&#xff0c;荷兰和日本的光刻机对中国供应面临限制&#xff0c;其他芯片设备和材料也受到很大的限制&#xff0c;这促使国产芯片产业链积极完善&#xff0c;以实现纯国产芯片工艺&#xff0c;虽然在光刻机方面还稍微落后&#xff0c;不过有一项国产芯片设备…

day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常见的传输层协议&#xff0c;用于在计算机网络中提供可靠的数据传输。 1.TCP&#xff1a; 连接导向&#xff1a;TCP是一种面向连接的…

c++ qt--线程(一)(第八部分)

c qt–线程&#xff08;一&#xff09;&#xff08;第八部分&#xff09; 一.进程&#xff08;Process&#xff09; 在任务管理器中的进程页下&#xff0c;可以看到进程&#xff0c;任务管理器将进程分为了三类&#xff0c;应用、后台进程、window进程 应用&#xff1a; 打开…

Docker(三) 创建Docker镜像

一、在Docker中拉取最基本的Ubuntu系统镜像 搜索Ubuntu镜像 Explore Dockers Container Image Repository | Docker Hub 下载镜像 docker pull ubuntu:22.04 二、在镜像中添加自己的内容 使用ubuntu镜像创建容器 docker run -it ubuntu:20.04 /bin/bash 在容器中创建了一个文…

【高性能计算】opencl语法及相关概念(二):索引,队列,核函数

目录 数据并行及任务并行异构编程语言的共性opencl的划分方式opencl上下文定义以字符串为主的程序对象同一设备&#xff0c;多个命令队列同时执行多个核函数的示例 数据并行及任务并行 数据并行是将大规模的计算任务划分为多个子任务&#xff0c;并将这些子任务同时应用于不同…

Android AGP版本

做个记录&#xff1a; Android AGP版本 https://developer.android.com/studio/releases/gradle-plugin?hlzh-cn

Android动态可编辑长度列表

概述 在界面实现一个列表&#xff0c;用户可以随意给列表新增或者删除项目&#xff0c;在开发中比较常用&#xff0c;但是真正做起来又有点花时间&#xff0c;今天花时间做一个&#xff0c;以便在以后的开发中用到。 详细 运行效果&#xff1a; 二、实现思路&#xff1a; 1…

官方推荐使用的OkHttp4网络请求库全面解析(Android篇)

作者&#xff1a;cofbro 前言 现在谈起网络请求&#xff0c;大家肯定下意识想到的就是 okhttp 或者 retrofit 这样的三方请求库。诚然&#xff0c;现在有越来越多的三方库帮助着我们快速开发&#xff0c;但是对于现在的程序员来说&#xff0c;我们不仅要学会如何去用&#xff…