堆的实现【C++】

堆的实现

  • 概念
  • 实现
  • 完整代码

概念

介绍堆之前得说一下二叉树,因为堆的逻辑结构是二叉树二叉树的子集,树只有一个根节点,向下衍生出了很多节点,并且这个节点之间相互没有连接,除非是父子节点,如果有连接了那就是;如果拥有很多独立不连接的树,这样的数据结构称之为森林
如果对树作进一步的限制:每一个节点(包括根节点)最多只有两个子树,那么这样的树叫做二叉树,比如:
在这里插入图片描述
这种也是:
在这里插入图片描述

二叉树呢又可以分为完全二叉树和满二叉树:
完全二叉树就是每一个节点都有两个子树(子节点),例如:
在这里插入图片描述

满二叉树就是除了最后一层,也就是最底下的一层,其他的每层都是完全二叉树,并且最后一层的节点得从左到右存在,下面这种就属于满二叉树:
在这里插入图片描述

但是这种就不属于满二叉树:
在这里插入图片描述
堆的逻辑数据结构就是使用满二叉树实现的,为什么说是逻辑呢?这是因为我们在实际实现的时候使用的是数组来实现的,原因如下:
如果我们想使用数组来存储下面这个二叉树:
在这里插入图片描述

这样对吗?像这样写的话,岂不是没有二叉树的特点了,比如怎么判断左右子树呢?所以我们得从上到下,从左到右的添加进数组。
在这里插入图片描述
例如:
在这里插入图片描述
这样就可以轻松知道左右子树从而通过数组构建出二叉树了,但是这个数组的中间浪费了不少空间,如何避免呢?
那就是使用满二叉树。

数组中的任意一个节点怎么能够得到他的左右子节点和父节点呢?可以通过下面的几个公式:
如果想知道数组arr里面的节点i的左右子节点和父节点:

  • 父节点=arr[(i-1)/2]
  • 左节点=arr[i*2+1];
  • 右节点=arr[i*2+2];

概念说完了,我们接下来可以用这种数据结构来构建一个大堆和小堆,大堆的特点是父节点一定大于子节点,但是左右节点没有讲究,小堆则相反。
利用大小堆可以实现topk问题,也就是求出一堆数据中第几大(小)的数是哪一个。也可以实现堆排序。

实现

先实现一个大堆:
在这里插入图片描述

然后就得插入数据:
在这里插入图片描述
每次插入数据都是插入到数组的最后面,也就是二叉树的最下一层的最右边,此时需要维护大堆的规则,也就是父节点的值大于子节点的值,由于每次插入时都是在最下面,所以如果不符合规则,当前插入的值大于父节点,我们就利用adjustUp函数进行向上调整。如下:

void Heap::adjustUp()
{int curIndex = _heap.size() - 1;while (curIndex > 0){int parentIndex = (curIndex - 1) / 2;if (_heap[parentIndex] < _heap[curIndex]){std::swap(_heap[parentIndex], _heap[curIndex]);curIndex = parentIndex;}else{break;}}}

写一下测试跑跑看吧:

#include "heap.h"int main() {Heap heap;heap.insert(1);heap.insert(2);heap.insert(3);
}

喔吼!拿不到数组的值,我们要得到堆顶的值,那就写一个吧:

int Heap::top()
{if (_heap.size() > 0){return _heap[0];}}

运行得到:
在这里插入图片描述
可以看到堆顶的数据3,数组下标0处的数据是3,可以我们在插入的时候,0处一开始插入的是1,但是结果是3,这就说明没有问题。

如果要实现topk问题,我们需要每次都将堆顶的元素给删除了,如果求第3大的数,我们就删除两次,然后此时堆顶的元素就是第3大的数了下面我们来实现删除功能:

在数组中如果直接删除下标0处的元素,在进行调整,这样会需要变动整个数组,效率低下;因此,我们选择将下标0处的元素与最后一个元素交换,然后直接删除最后一个元素,在进行向下调整即可:

void Heap::pop()
{if (_heap.size() > 0){std::swap(_heap[0], _heap[_heap.size() - 1]);_heap.pop_back();adjustDown();}
}void Heap::adjustDown()
{int size = _heap.size();int curIndex = 0, leftIndex = 1;while (leftIndex < size){if (leftIndex + 1 < size && _heap[leftIndex] < _heap[leftIndex + 1]){leftIndex++;}if (_heap[curIndex] < _heap[leftIndex]){std::swap(_heap[curIndex], _heap[leftIndex]);curIndex = leftIndex;leftIndex = curIndex * 2 + 1;}else{break;}}}

在adjustDown这个函数中,利用leftIndex + 1 < size && _heap[leftIndex] < _heap[leftIndex + 1]来判断左右节点哪一个更大一些,然后用较大值和父节点的值进行比较,如果父节点的值小于子节点的值,那么就要向下调整,也就是需要交换,之后更新当前的父节点和左节点,再次进行判断。

下面进行测试:
在这里插入图片描述
结果也没有问题。

然后我们根据这个进行堆排序的编写:
测试代码如下:

int main() {srand((unsigned int)time(NULL));std::vector<int> v;for (int i = 0; i < 100; i++){v.push_back(rand()%100);}Heap heap;heap.sort(v);for (auto& e : v){std::cout << e << " ";}
}

在这里插入图片描述
结果也没有问题。

完整代码

//heap.h
#pragma once
#include <vector>
#include <iostream>
#include <cassert>class Heap {
public:Heap();~Heap();void insert(int value);void adjustUp();int top();void pop();void adjustDown();void sort(std::vector<int>& v);
private:std::vector<int> _heap;
};
//heap.cpp
#include "heap.h"Heap::Heap()
{}Heap::~Heap()
{}void Heap::insert(int value)
{_heap.push_back(value);adjustUp();
}void Heap::adjustUp()
{int curIndex = _heap.size() - 1;while (curIndex > 0){int parentIndex = (curIndex - 1) / 2;if (_heap[parentIndex] < _heap[curIndex]){std::swap(_heap[parentIndex], _heap[curIndex]);curIndex = parentIndex;}else{break;}}}int Heap::top()
{if (_heap.size() > 0){return _heap[0];}}void Heap::pop()
{if (_heap.size() > 0){std::swap(_heap[0], _heap[_heap.size() - 1]);_heap.pop_back();adjustDown();}
}void Heap::adjustDown()
{int size = _heap.size();int curIndex = 0, leftIndex = 1;while (leftIndex < size){if (leftIndex + 1 < size && _heap[leftIndex] < _heap[leftIndex + 1]){leftIndex++;}if (_heap[curIndex] < _heap[leftIndex]){std::swap(_heap[curIndex], _heap[leftIndex]);curIndex = leftIndex;leftIndex = curIndex * 2 + 1;}else{break;}}}void Heap::sort(std::vector<int>& v)
{for (int i = 0; i < v.size(); i++){insert(v[i]);}for (int i = 0; i < v.size(); i++){v[i] = top();pop();}
}
//main.c
#include "heap.h"
#include <ctime>int main() {/*Heap heap;heap.insert(1);heap.insert(2);heap.insert(3);heap.pop();std::cout << heap.top() << std::endl;heap.pop();std::cout << heap.top() << std::endl;*/srand((unsigned int)time(NULL));std::vector<int> v;for (int i = 0; i < 100; i++){v.push_back(rand()%100);}Heap heap;heap.sort(v);for (auto& e : v){std::cout << e << " ";}
}

         新人创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看。

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

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

相关文章

UI自动化测试:异常截图和page_source

自动化测试过程中&#xff0c;是否遇到过脚本执行中途出错却不知道原因的情况&#xff1f;测试人员面临的不仅是问题的复现&#xff0c;还有对错误的快速定位和分析。而异常截图与页面源码&#xff08;Page Source&#xff09;的结合&#xff0c;正是解决这一难题的利器。 在实…

语法

jQuery 的 .toggleClass() 方法用于在选定的元素上添加或移除一个或多个类。这个方法会检查每个元素中指定的类,如果类不存在,则添加它;如果类已设置,则删除它。这被称为切换效果。此外,可以通过使用 "switch" 参数来规定只删除或只添加类。 语法 (selector).…

Node.js 版本管理工具完全指南

Node.js 版本管理工具完全指南 目录 1. nvm (Node Version Manager)2. n (Node Package Manager)3. fnm (Fast Node Manager)4. Volta5. 工具对比 1. nvm (Node Version Manager) 1.1 安装指南 macOS/Linux # 使用 curl 安装 curl -o- https://raw.githubusercontent.com…

线程池底部工作原理

线程池内部是通过线程和队列实现的&#xff0c;当我们通过线程池处理任务时&#xff1a; 如果线程池中的线程数量小于corePoolSize&#xff0c;无论是否有处于空闲的线程&#xff0c;都创建新的线程来处理被添加的任务。 如果线程池中的线程数量等于corePoolSize&#xff0c;…

ASP.NET Core - 依赖注入(三)

ASP.NET Core - 依赖注入&#xff08;三&#xff09; 4. 容器中的服务创建与释放 4. 容器中的服务创建与释放 我们使用了 IoC 容器之后&#xff0c;服务实例的创建和销毁的工作就交给了容器去处理&#xff0c;前面也讲到了服务的生命周期&#xff0c;那三种生命周期中对象的创…

【C++】面试题整理(未完待续)

【C】面试题整理 文章目录 一、概述二、C基础2.1 - 指针在 32 位和 64 位系统中的长度2.2 - 数组和指针2.3 - 结构体对齐补齐2.4 - 头文件包含2.5 - 堆和栈的区别2.6 - 宏函数比较两个数值的大小2.7 - 冒泡排序2.8 - 菱形继承的内存布局 三、智能指针3.1 - 智能指针是线程安全的…

gitlab runner正常连接 提示 作业挂起中,等待进入队列 解决办法

方案1 作业挂起中,等待进入队列 重启gitlab-runner gitlab-runner stop gitlab-runner start gitlab-runner run方案2 启动 gitlab-runner 服务 gitlab-runner start成功启动如下 [rootdocserver home]# gitlab-runner start Runtime platform …

麦田物语学习笔记:构建游戏的时间系统

基本流程 1.代码思路 (1)新建一个TimeManager.cs (2)创建枚举变量来表示四季,在TimeManager里需要的变量有: 游戏内的秒,分钟,小时,天,月,年;游戏内的季节;控制一个季节有多少个月;控制时间的暂停;计时器tikTime (3)在Settings里添加计时器的阈值,以及各个时间的进位 (4)初始化…

Spring Boot教程之五十七:在 Apache Kafka 上发布 JSON 消息

Spring Boot | 如何在 Apache Kafka 上发布 JSON 消息 Apache Kafka是一个发布-订阅消息系统。消息队列允许您在进程、应用程序和服务器之间发送消息。在本文中&#xff0c;我们将了解如何在 Spring Boot 应用程序中向 Apache Kafka 发送 JSON 消息。 为了了解如何创建 Spring…

c++类和对象---下

文章目录 一、类的静态成员 1.1.静态成员变量&#xff1a;所有对象共享的成员变量。 1.2.静态成员函数&#xff1a;可以访问静态成员变量&#xff0c;但不能访问非静态成员变量。 二、类的继承 2.1.继承&#xff1a;子类继承父类的成员变量和成员函数。 2.2.多态&#xff1a;基…

计算机网络 (44)电子邮件

一、概述 电子邮件&#xff08;Electronic Mail&#xff0c;简称E-mail&#xff09;是因特网上最早流行的应用之一&#xff0c;并且至今仍然是因特网上最重要、最实用的应用之一。它利用计算机技术和互联网&#xff0c;实现了信息的快速、便捷传递。与传统的邮政系统相比&#…

代码随想录算法训练营day02| 977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

977. 有序数组的平方 双指针&#xff0c;新数组用k&#xff1b; 由于已经排序&#xff0c;所以比较两侧数据即可&#xff1b; class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int> ans(nums.size());int k nums.siz…

Unity 语音转文字 Vosk 离线库

市场有很多语音库&#xff0c;这里介绍Vosk SDK 除了支持untiy外还有原生开发服务器等 目录 安装unity示例demo下载语音训练文件运行demo结尾一键三联 注意事项 有可能debug出来的文本是空的&#xff0c;&#xff08;确保麦克风正常&#xff0c;且索引正确&#xff09;分大…

网络网络层ICMP协议

网络网络层ICMP协议 1. ICMP 协议介绍 ICMP&#xff08;Internet Control Message Protocol&#xff09;是 TCP/IP 协议簇中的网络层控制报文协议。用于在 IP 主机、路由器之间传递控制消息&#xff0c;提供可能有关通信问题的反馈信息。 以及用于网络诊断或调试&#xff08;…

Lianwei 安全周报|2025.1.13

新的一周又开始了&#xff0c;以下是本周「Lianwei周报」&#xff0c;我们总结推荐了本周的政策/标准/指南最新动态、热点资讯和安全事件&#xff0c;保证大家不错过本周的每一个重点&#xff01; 政策/标准/指南最新动态 01 美国国土安全部发布《公共部门生成式人工智能部署手…

计算机网络(五)运输层

5.1、运输层概述 概念 进程之间的通信 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时…

本地用docker装mysql

目录 拉取镜像查看镜像 启动容器查看运行中的容器连接到 MySQL 容器其他一些操作 装WorkBench链接mysql——————————————允许远程登录MySql 拉取镜像 docker pull mysql查看镜像 docker image lsREPOSITORY TAG IMAGE ID CREATED SIZE mysq…

C# PDF下载地址转图片(Base64 编码)

实现思路&#xff1a; 步骤一、根据PDF地址下载pdf文件保存为临时文件&#xff0c;获得pdf文件的byte[]数组 /// 从指定的 URL 下载 PDF 文件public byte[] DownloadPdf(string url){try{using (WebClient client new WebClient()){return client.DownloadData(url);}}catc…

卷积神经02-CUDA+Pytorch环境安装

卷积神经02-CUDAPytorch环境安装 在使用Python进行pytorch的使用过程中遇到各种各样的版本冲突问题&#xff0c;在此进行记录 0-核心知识脉络 1&#xff09;根据自己电脑的CUDA版本安装对应版本的Pytorch&#xff0c;充分的使用GPU性能2&#xff09;电脑要先安装【CUDA ToolKi…

shell 脚本基本练习

一、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 1. 创建ex1.sh文件 [rootopenEuler mnt]# vim ex1.sh创建如下&#xff1a; 2. 根据题目编写脚本 n"/tmp/siz.log"if [ -f "$n" ] thenc…