数据结构——堆的实现

堆的实现-----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.mzph.cn/news/168139.shtml

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

相关文章

vscode项目推送到git

1、打开项目文件 打开文件后点击vs code左侧工具栏中第三个源代码管理图标&#xff0c;点击初始化仓库&#xff0c;此时会创建一个本地仓库会检查该项目中的文件变更 2、创建远程仓库 点击克隆/下载&#xff0c;复制HTTPS地址 3、添加远程地址 1&#xff09;图形化操作 2…

Leetcode刷题之用队列实现栈(C语言版)

Leetcode刷题之用队列实现栈&#xff08;C语言版&#xff09; 一、题目描述二、题目要求三、题目示例四、题目解析Ⅰ、MyStack* myStackCreateⅡ、void myStackPush(MyStack* obj, int x)Ⅲ、int myStackPop(MyStack* obj)Ⅳ、int myStackTop(MyStack* obj)Ⅴ、bool myStackEmp…

文件夹重命名:彻底摆脱数字困扰,批量修改文件夹名去除数字

在日常生活和工作中&#xff0c;经常会遇到需要修改文件夹名称的情况。有时候是因为文件夹名称中包含了数字&#xff0c;有时候是因为文件夹名称不符合规范。无论出于什么原因&#xff0c;修改文件夹名称都是一件非常繁琐的事情。尤其是需要修改大量文件夹名称时&#xff0c;手…

Jenkins 整合 Docker 自动化部署

Docker 安装 Jenkins 配置自动化部署 1. Docker 安装 Jenkins 1.1 拉取镜像文件 docker pull jenkins/jenkins1.2 创建挂载文件目录 mkdir -p $HOME/jenkins_home1.3 启动容器 docker run -d -p 8080:8080 -v $HOME/jenkins_home:/var/jenkins_home --name jenkins jenkin…

k8s部署的java服务查看连接nacos缓存的配置文件

一、问题描述 k8s部署的java服务&#xff0c;使用nacos中的配置文件&#xff0c;需要在缓存中查看该服务具体是使用到了哪些配置文件 二、解决 参考文档: https://nacos.io/zh-cn/docs/system-configurations.html 文档描述如下: 进入java服务容器进入用户目录下的nacos&a…

Java枚举详解

一、什么是枚举类型 枚举类型是一种特殊的数据类型&#xff0c;用于定义一组固定的命名常量。枚举类型提供了一种更强大、更安全和更易读的方式来表示一组相关的常量。 在Java中&#xff0c;枚举类型是通过使用enum关键字来定义的。枚举类型可以包含一个或多个枚举常量&#xf…

vue005——vue组件入门(非单文件组件和单文件组件)

一、非单文件组件 1.1、单文件组件的使用 1.1.1、局部注册 1、第一步&#xff1a;创建school组件 2、第二步&#xff1a;注册组件&#xff08;局部注册&#xff09; 3、第三步&#xff1a;使用组件&#xff08;编写组件标签&#xff09; <!DOCTYPE html> <html>…

设计模式—里氏替换原则

1.概念 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说&#xff0c;任何基类可以出现的地方&#xff0c;子类一定可以出现。 LSP是继承复用的基石&#xff0c;只有当衍生类可以替换掉基类&#xff0c;软件单位的功能不受到影…

Mac Ubuntu双系统解决WiFi和WiFi 5G网络不可用问题

文章目录 设备信息1. Ubuntu WiFi不可用解决方式查看Mac的网卡型号根据网卡型号搜索获取到的解决方法查看WiFi名字问题参考链接 2. 解决WiFi重启后失效问题打开终端创建.sh脚本文件编辑脚本文件复制粘贴脚本修改脚本权限创建并编辑systemd service文件复制粘贴下文到systemd se…

只考数据结构,计算机评级C+,成都信息工程大学考情分析

成都信息工程大学(C) 考研难度&#xff08;☆☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、24专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1715字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 …

Java实现求最大值

1 问题 接收用户输入的3个整数&#xff0c;如何将最大值作为结果输出。 2 方法 采用“截图文字代码”的方式描述。 引入输入包调用main()函数&#xff0c;提示并接收用户输入的3个整数&#xff0c;并交由变量a b c来保存。对接收的3个数据进行比较&#xff0c;先比较a和b&#…

原型 原型对象 原型链

在面向开发对象开发过程中对每一个实例添加方法&#xff0c;会使每一个对象都存在该添加方法造成空间浪费 通过对原型添加公共的属性或方法&#xff0c;使所有实例对象都可访问 原型为了共享公共的成员 prototype 原型: JS为每个构造函数提供一个属性prototype(原型),它的值…

深眸科技聚焦AI机器视觉检测,驱动3C电子行业集成创新实现新需求

随着消费的升级及国家政策的助推&#xff0c;国内3C电子市场不断扩大&#xff0c;行业实现高速发展。近年来&#xff0c;3C电子产品持续迭代&#xff0c;生产工艺也逐渐复杂化&#xff0c;相关生产线定位组装、零部件检测、整机产品检测等环节&#xff0c;亟需使用具备较强适应…

举个栗子!Quick BI 技巧(4):创建面积图

面积图又叫区域图&#xff0c;是在折线图的基础之上形成的, 它将折线图中折线与自变量坐标轴之间的区域使用颜色或者纹理填充&#xff0c;这样一个填充区域我们叫做面积&#xff0c;颜色的填充也可以更好的突出趋势信息。 有数据粉好奇如何使用 Quick BI 来制作面积图&#xf…

NVMe-oF E-JBOF设计解析:WD RapidFlex网卡、OpenFlex Data24

OpenFlex Data24 NVMe-oF Storage Platform WD的SN840 NVMeSSD新品并没有太吸引我注意&#xff0c;因为它还是PCIe 3.0接口的&#xff0c;要知道Intel的PCIe 4.0 SSD都已经推出了。 但上面这个NVMe-oF&#xff08;NVMe over Fabric&#xff09;EBOF&#xff08;区别于普通JBO…

css三角,鼠标样式,溢出文字

目录 css三角 鼠标样式 例子&#xff1a;页码模块 溢出文字表示方式 margin负值运用 css三角强化 css三角 css三角中&#xff1a;line-height&#xff1a;0和font-size&#xff1a;0是防止兼容性的问题 jd {position: relative;width: 120px;height: 249px;background-…

在 Ubuntu 上安装最新版的 Calibre

目录 前言 方法1&#xff1a;从 Ubuntu 的仓库安装 Calibre 卸载 Calibre 方法2&#xff1a;获取最新版本的 Calibre 卸载 Calibre 结语 前言 Calibre 是一款自由开源的电子书软件。下面介绍如何在 Ubuntu Linux 上安装它。 作为电子书管理的瑞士军刀&#xff0c;Calibre …

线程-Thread类及常见方法

目录 一、创建线程 1.继承 Thread 类 2. 实现 Runnable 接口 3.匿名内部类创建 Thread 子类对象 4. 匿名内部类创建 Runnable 子类对象 5. lambda 表达式创建 Runnable 子类对象 二、Thread 类及常见方法 2.1 Thread 的常见构造方法 2.2 Thread 的几个常见属性 2.3 启…

C++初级项目webserver项目流程介绍(2)

一、引言 C的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。 这个项目的效果是可以在浏览器通过输入网络IP地址和端口&#xff0c;然后打开对应的文件目录 效果如下&#xff1a; 也可以打开文件夹后点击目录&#xff0c;打开到对应的文件夹…

ES之x-pack-core-7.14.2许可证修改为白金版

X-Pack是什么 X-pack是elasticsearch的一个扩展包&#xff0c;将安全&#xff0c;警告&#xff0c;监视&#xff0c;图形和报告功能捆绑在一个易于安装的软件包中&#xff0c;虽然x-pack被设计为一个无缝的工作&#xff0c;但是你可以轻松的启用或者关闭一些功能。 主要分一下步…