数据结构——堆的实现

堆的实现-----C语言版

  • 目录:
  • 一、堆的实现
    • 1.1堆的定义
    • 1.2堆的实现
      • 1.2.1堆的各个接口
      • 1.2.2堆的向上调整
      • 1.2.3堆的向下调整
      • 1.2.4堆的定义声明和初始化
      • 1.2.5堆的数据处理
      • 1.2.6堆的判空和堆的数据个数以及堆销毁
      • 1.2.7堆的代码实现
  • 二、TOP—K问题

目录:

一、堆的实现

1.1堆的定义

(heap)是特殊的数据结构。堆通常是一个可以被看做一棵完全二叉树(逻辑层面上)的数组对象(物理层面上),常用来在一组变化频繁(发生增删查改的频率较高)的数据中寻找最值.将根结点最大的堆叫做最大堆或大根堆,这样可以找到堆中的最大值(根节点的值);根结点最小的堆叫做最小堆或小根堆,这样可以找到堆中的最小值。

其中堆不一定是完全二叉树,只是为了方便存储索引,我们通常用完全二叉树的形式来表示堆而已。
二叉堆:是一个数组,它可以被看成是一个近似的完全二叉树。

最大堆,最小堆如图:
在这里插入图片描述

最大堆:根结点大于左右子树结点的值,左右子树结点的值大于它自己左右子树结点的值,一种重复下去;最小堆:根结点小于左右子树结点的值,左右子树结点的值小于它自己左右子树结点的值,一种重复下去。

1.2堆的实现

用数组实现一个堆

1.2.1堆的各个接口

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//动态数组int _size;//存储数据的下标int _capacity;//动态数组的容量
}Heap;
//堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataTypeHeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

1.2.2堆的向上调整

//向上调整算法
void HeapJustUp(HPDataType a[], HPDataType child)
{int parsent;parsent = (child - 1) / 2;//找到孩子的父亲while (child > 0){int tmp = 0;if (a[parsent] < a[child])//孩子比父亲的值大,{tmp = a[child];a[child] = a[parsent];a[parsent] = tmp;}elsebreak;child = parsent;parsent = (parsent - 1) / 2;//找到孩子的父亲}
}

对于向上调整,我们把它看做是数组结构,逻辑上看做一颗完全二叉树。我们只要将要插入堆的数据通过向上调整就可以把它调整成一个大堆。向上调整算法有一个前提:除了要插入的数据,其它的数据已经构成了一个大堆,这样才能调整。

1.2.3堆的向下调整

void HeapJustDown(Heap* hp)
{//先假设当前待调整结点的左孩子结点存在//并且是待调整结点的左右孩子结点(不管右孩子结点存不存在,都这样假设)中值最大的int parent = 0;//根节点int child = parent * 2 + 1;//孩子结点while (child < hp->_size){//child+1 < hp->_size说明右孩子结点确实存在//如果hp->_a[child] < hp->_a[child+1]也成立,那说明左右孩子结点中值最大的是右孩子结点if ((child + 1 < hp->_size) && hp->_a[child] < hp->_a[child + 1]){child = child + 1;}//如果a[child]>a[parent],则说明父节点比比左右孩子节点的值都要小,要置换if (hp->_a[child] > hp->_a[parent]){int tmp = hp->_a[parent];hp->_a[parent] = hp->_a[child];hp->_a[child] = tmp;parent = child;child = child * 2 + 1;}//如果a[child] <= a[parent],那就不需要进行调整else{break;}}
}

对于向下调整,我们把它看成是一个数组结构,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个大堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

1.2.4堆的定义声明和初始化

1.堆的声明

typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//动态数组int _size;//存储数据的下标int _capacity;//动态数组的容量
}Heap;

创建一个构成动态数组的结构体

2.堆的初始化

// 堆的初始化
void HeapInit(Heap* hp)
{hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (hp->_a == 0){printf("malloc is error\n");exit(-1);}hp->_capacity = 4;hp->_size = 0;
}

开空间,进行初始化

1.2.5堆的数据处理

1.堆的插入

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{//数据满了,需要扩容if (hp->_capacity == hp->_size){HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity * 2);if (tmp == NULL){printf("realloc is error");exit(-1);}hp->_a = tmp;hp->_capacity = hp->_capacity * 2;}//不需要扩容hp->_a[hp->_size++] = x;//插入数据,然后_size+1//一般数据都是放到数组尾得,建堆,向上调整,这里我们建大堆HeapJustUp(hp->_a, hp->_size - 1);
}

1.容量不够就扩容
2.扩容足够就插入数据
3.然后向上调整建大堆,直到满足堆

2.堆的删除

// 堆的删除,从堆顶开始删
void HeapPop(Heap* hp)
{
assert(hp);//断言为空为假的话就报错
assert(!HeapEmpty(hp));//断言如果不是空为真就执行
//首元素的的值与尾元素交换,然后删除尾元素
int tmp = hp->_a[0];
hp->_a[0] = hp->_a[hp->_size - 1];
hp->_a[hp->_size - 1] = tmp;
hp->_size--;
//堆顶元素进行向下调整
HeapJustDown(hp);
}

1.挪动覆盖删除堆顶元素,重新建堆
2.尽量保证关系不变(首尾数据交换,再删除尾部数据,向下调整建堆)

3.获取堆顶数据

// 取堆顶的数据
HPDataTypeHeapTop(Heap* hp)
{assert(hp->_a);assert(!HeapEmpty(hp));//断言如果不是空为真就执行return hp->_a[0];
}

堆顶数据就是第一个元素

1.2.6堆的判空和堆的数据个数以及堆销毁

1.堆的数据个数

// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);return hp->_size;
}

堆的数据个数就是_size的个数

2.堆的判空

// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}

_size为0,说明堆为空

3.堆销毁

// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_a = NULL;hp->_capacity = hp->_size = 0;
}

开一块空间(malloc),程序结束之前要释放空间(free)

1.2.7堆的代码实现

.h头文件(声明)

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//动态数组int _size;//存储数据的下标int _capacity;//动态数组的容量
}Heap;
//堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataTypeHeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

.c源文件(定义)

#include "Heap.h"
// 堆的构建
void HeapInit(Heap* hp)
{hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (hp->_a == 0){printf("malloc is error\n");exit(-1);}hp->_capacity = 4;hp->_size = 0;
}
//向上调整算法
HeapJustUp(HPDataType a[], HPDataType child)
{int parsent;parsent = (child - 1) / 2;//找到孩子的父亲while (child > 0){int tmp = 0;if (a[parsent] < a[child])//孩子比父亲的值大,{tmp = a[child];a[child] = a[parsent];a[parsent] = tmp;}elsebreak;child = parsent;parsent = (parsent - 1) / 2;//找到孩子的父亲}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{//数据满了,需要扩容if (hp->_capacity == hp->_size){HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity * 2);if (tmp == NULL){printf("realloc is error");exit(-1);}hp->_a = tmp;hp->_capacity = hp->_capacity * 2;}//不需要扩容hp->_a[hp->_size++] = x;//插入数据,然后_size+1//一般数据都是放到数组尾得,建堆,向上调整,这里我们建大堆HeapJustUp(hp->_a, hp->_size - 1);
}
// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);return hp->_size == 0;
}
//堆顶元素进行向下调整
void HeapJustDown(Heap* hp)
{//先假设当前待调整结点的左孩子结点存在//并且是待调整结点的左右孩子结点(不管右孩子结点存不存在,都这样假设)中值最大的int parent = 0;//根节点int child = parent * 2 + 1;//孩子结点while (child < hp->_size){//child+1 < hp->_size说明右孩子结点确实存在//如果hp->_a[child] < hp->_a[child+1]也成立,那说明左右孩子结点中值最大的是右孩子结点if ((child + 1 < hp->_size) && hp->_a[child] < hp->_a[child + 1]){child = child + 1;}//如果a[child]>a[parent],则说明父节点比比左右孩子节点的值都要小,要置换if (hp->_a[child] > hp->_a[parent]){int tmp = hp->_a[parent];hp->_a[parent] = hp->_a[child];hp->_a[child] = tmp;parent = child;child = child * 2 + 1;}//如果a[child] <= a[parent],那就不需要进行调整else{break;}}
}
// 堆的删除,从堆顶开始删
void HeapPop(Heap* hp)
{
assert(hp);//断言为空为假的话就报错
assert(!HeapEmpty(hp));//断言如果不是空为真就执行
//首元素的的值与尾元素交换,然后删除尾元素
int tmp = hp->_a[0];
hp->_a[0] = hp->_a[hp->_size - 1];
hp->_a[hp->_size - 1] = tmp;
hp->_size--;
//堆顶元素进行向下调整
HeapJustDown(hp);
}
// 取堆顶的数据
HPDataTypeHeapTop(Heap* hp)
{assert(hp->_a);assert(!HeapEmpty(hp));//断言如果不是空为真就执行return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);return hp->_size;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_a = NULL;hp->_capacity = hp->_size = 0;
}

.c源文件(测试)

#include "Heap.h"
int main()
{Heap hp;HeapInit(&hp);//初始化HeapPush(&hp, 2);//插入数据HeapPush(&hp, 3);HeapPush(&hp, 4);HeapPush(&hp, 5);HeapPush(&hp, 6);HeapPush(&hp, 1);HeapPush(&hp, 66);HeapPush(&hp, 62);HeapPush(&hp, 4);HeapPush(&hp, 6);HeapPop(&hp);//删除数据,从堆顶开始删int tmp= HPDataTypeHeapTop(&hp);//取堆顶元素// 堆的数据个数int num = HeapSize(&hp);printf("建大堆,栈顶元素为:%d,堆的数据个数:%d\n", tmp,num);for (int i = 0; i < num; i++)printf("%d ", hp._a[i]);HeapDestory(&hp);// 堆的销毁return 0;
}

二、TOP—K问题

TOP—K问题:求数据集合中前k个最大的元素和最小的元素,一般情况数据非常大。
如:专业前10,世界500强,游戏中前100的活跃玩家,各种榜单等等。

1.用数据集合中前k个元素来建堆
求前k个最大的元素,建小堆
求前k个最小的元素,建大堆
2.用剩余的N—K个元素依次与堆顶元素来比较,根据规则替换堆顶元素,N—K个元素依次与堆顶元素比较完成后,堆里的K个元素就是所求的最小或者最大的元素。

例子:

问题:假设1亿个数,内存存不下,数据在文件中找出最大的前k个数。
1.读取文件的前10个数据,在内存数组中建立一个小堆。
2.在依次读取剩下数据,跟堆顶元素比较,大于堆顶的数据就替换它,然后向下调整。
3.所有数据读完,堆里面数据就是最大的前10个数。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define n 100000000
void  WeinteData()//写入1亿数据
{FILE* fp = fopen("top.txt", "w");//打开文件,只写
if (fp == NULL)
{perror("fopen error");exit(-1);
}
srand((unsigned)time(0));
int arr[100] = { 0 };
for (int i = 0; i < n; i++)
{int x = rand() % 10000 + 1;fprintf(fp, "%d\n", x);
}
fclose(fp);//关闭文件
}
//两个数交换
void Swap(int* p, int* q)
{int tmp;tmp = *q;*q = *p;*p = tmp;
}
//向下调整算法
void JustDown(int* arr,int k,int parent)
{int child = parent * 2 + 1;//左孩子结点while (child < k){if ((child + 1 < k) && arr[child] > arr[child + 1])//找到最小值的孩子结点child += 1;//如果arr[child]<arr[parent],则说明父节点比比左右孩子节点的值都要大,要置换if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);//让孩子结点为父节点,并且更新它的儿子结点parent = child;child = child * 2 + 1;}//如果a[child] <= a[parent],那就不需要进行调整else{break;}}
}
//建小堆
void HeapCreate(int* arr,int k)
{//最后一个结点的父亲结点开始向下调整for (int i = (k - 2) / 2; i >= 0; --i){//向下调整算法JustDown(arr, k, i);}
}
void  FileTakeK()
{int k = 10;//10个数int* a = (int*)malloc(sizeof(int) * k);//开辟一块空间用来建堆if (a == NULL){perror("malloc error:");exit(-1);}FILE* file = fopen("top.txt", "r");//打开top.txt文件,只读模式if (file == NULL){perror("fopen error:");exit(-1);}for (int i = 0; i < k; i++){fscanf(file, "%d", &a[i]);}printf("前10个数:\n");for (int i = 0; i < k; i++)printf("%d ", a[i]);//建小堆HeapCreate(a, k);printf("\n建完小堆里面的数:\n");for (int i = 0; i < k; i++)printf("%d ", a[i]);//把剩余的n-k个数与小堆的堆顶比较,比较完成后,堆里的数就是文件里最大的10个数int x = 0;while (fscanf(file, "%d", &x) != EOF){//比堆顶数大,把这个数赋值给堆顶,然后向下调整if (x > a[0])a[0] = x;JustDown(a, k, 0);}printf("\n取最大的10个数:\n");for (int i = 0; i < k; i++)printf("%d ", a[i]);free(a);//释放内存fclose(file);//关闭文件
}
int main()
{//写入1亿数据WeinteData();//从文件中取出k个数,建小堆FileTakeK();return 0;
}

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

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

相关文章

竞赛选题 题目:基于卷积神经网络的手写字符识别 - 深度学习

文章目录 0 前言1 简介2 LeNet-5 模型的介绍2.1 结构解析2.2 C1层2.3 S2层S2层和C3层连接 2.4 F6与C5层 3 写数字识别算法模型的构建3.1 输入层设计3.2 激活函数的选取3.3 卷积层设计3.4 降采样层3.5 输出层设计 4 网络模型的总体结构5 部分实现代码6 在线手写识别7 最后 0 前言…

根据商品ID获取淘宝数据接口|淘宝商品详情接口|淘宝关键词搜索商品列表接口|淘宝到手价接口|淘宝API接口

淘宝API接口可以运用到多种业务场景中&#xff0c;以下列举了一些主要的场景&#xff1a; 商品信息展示&#xff1a;通过调用淘宝API详情接口&#xff0c;可以获取商品的详细信息&#xff0c;如商品标题、价格、库存、销量、评价等数据。这些信息可以用于在自己的网站或应用程…

kafka 集群 KRaft 模式搭建

Apache Kafka是一个开源分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序 Kafka 官网&#xff1a;https://kafka.apache.org/ Kafka 在2.8版本之后&#xff0c;移除了对Zookeeper的依赖&#xff0c;将依赖于ZooKeeper的控制器…

【Git】git 更换远程仓库地址三种方法总结分享

因为公司更改了 gitlab 的网段地址&#xff0c;发现全部项目都需要重新更改远程仓库的地址了&#xff0c;所以做了个记录&#xff0c;说不定以后还会用到呢。 一、不删除远程仓库修改&#xff08;最方便&#xff09; # 查看远端地址 git remote -v # 查看远端仓库名 git rem…

docker环境安装

环境 主机环境 1. 宿主机环境 ubuntu-22.04.3-live-server-amd64 &#xff0c;下载地址&#xff1a; https://mirrors.aliyun.com/ubuntu-releases/22.04.3/ubuntu-22.04.3-live-server-amd64.iso 2. apt 包管理器&#xff0c;镜像源修改 : 将 http://cn.archive.ubunt…

WebSocket 鉴权策略与技巧详解

WebSocket 作为实时通信的利器&#xff0c;越来越受到开发者的青睐。然而&#xff0c;为了确保通信的安全性和合法性&#xff0c;鉴权成为不可或缺的一环。本文将深入探讨 WebSocket 的鉴权机制&#xff0c;为你呈现一揽子的解决方案&#xff0c;确保你的 WebSocket 通信得心应…

RK3588硬编解码MPP环境配置

1. 简介 瑞芯微提供的媒体处理软件平台&#xff08;Media Process Platform&#xff0c;简称 MPP&#xff09;是适用于瑞芯微芯片系列的 通用媒体处理软件平台。该平台对应用软件屏蔽了芯片相关的复杂底层处理&#xff0c;其目的是为了屏蔽不 同芯片的差异&#xff0c;为使用者…

css给盒子写四个角

如图&#xff1a;之前一直用定位 现在发现可以用css写 background: linear-gradient(to top, #306eef, #306eef) left top no-repeat, /*上左*/ linear-gradient(to right, #306eef, #386eef) left top no-repeat, /*左上*/ linear-gradient(to left, #386eef, #306eef) righ…

Linux网络——网络层

目录 一.IP协议&#xff08;IPv4&#xff09; 二.子网划分 三.特殊的IP地址 四.IP地址的数量限制 五.私有IP地址和公网IP地址 六.路由 七.分片 一.IP协议&#xff08;IPv4&#xff09; IP协议&#xff1a;提供一种能力使得数据从一个主机发送到另一个主机的能力。 TCP协…

工业级 S25HS01GTDPBHV030 NOR闪存,L9305EP汽车级驱动器IC,LMK03318RHSR时钟发生器,PLL(中文资料)

一、工业级 S25HS01GTDPBHV030 Semper™ NOR闪存 S25HS01GT SEMPER™ NOR Flash闪存系列是英飞凌高性能、安全而可靠的 NOR Flash解决方案。 它集成了适用于汽车、工业、通信等广泛应用的关键安全功能。 凭借 SEMPER™ NOR Flash闪存&#xff0c;英飞凌推出了业界首款符合 ASI…

ArcGis如何用点连线?

这里指的是根据已有坐标点手动连线&#xff0c;类似于mapgis中的“用点连线”&#xff0c;线的每个拐点是可以自动捕捉到坐标点的&#xff0c;比直接画精确。 我也相信这么强大的软件一定可以实现类似于比我的软件上坐标时自动生成的线&#xff0c;但是目前我还没接触到那里&a…

MIPI 打怪升级之DSI篇

MIPI 打怪升级之DSI篇 目录 1 Overview2 DSI Mode 2.1 Video 模式2.2 Command 模式3 DSI Physical Layer 3.1 数据流控3.2 双向性3.3 Video Mode Interfaces3.4 Command Mode Interfaces3.5 Clock4 多通道管理 4.1 通道数匹配4.2 线上数据分布5 DSI 协议 5.1 包格式 5.1.1 短包…