【数据结构】堆的原理实现

news2024/11/18 5:30:23

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ 数据结构与算法
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

这里写目录标题

  • 堆的性质
  • 堆的实现
  • 堆的声明
  • 普通构造和析构的实现
  • 插入函数push
    • 什么是向上调整????
    • push函数代码
  • 移除节点函数pop
    • 什么是向下调整?
    • pop代码
  • top函数
  • 引入仿函数

堆的性质

1.堆是一颗完全二叉树。

2.堆的每一个父节点都大于等于或小于等于它的子节点。

所有父节点都大于等于子节点的堆叫大堆。

所有父节点都小于等于子节点的堆叫小堆。

在这里插入图片描述

在这里插入图片描述

堆的实现

我们可以用链表或者数组的形式来实现堆。如果用链表来实现堆的话,那么我们需要定义一个二叉树节点的结构体。并至少要包括左孩子,右孩子,存储的值三个结构体成员。例如:

struct HeapNode
{
    HeapNode* _left;  //左孩子
    HeapNode* _right; //右孩子
    int _val;  //存储的值
};

而这也意味着一个节点至少必须包含了三个成员,这对空间是一种消耗。那么我们可以用数组来实现堆。因为堆是一个完全二叉树。那么用数组实现就不会存在中间有空间浪费的情况,也可以用父节点的下标 * 2 + 1计算出左孩子的下标。也可以用(子节点 - 1 ) / 2 来计算出其父节点的下标。我们把下面这个堆抽象成一个数组。

在这里插入图片描述

	template<class T,class Functor = Less<T>>
	class Heap
	{
	public:
		T* _hp;
		size_t _size;
		size_t _capacity;

用数组存储的话,那么它的逻辑图应该是这样的。

在这里插入图片描述

以20这个节点为例,它的下标是2,那么它的父节点所在的位置就是(2 - 1) / 2。也就是10的位置。它的左孩子则是2*2 + 1,也就是5这个位置。右节点则是 2 * 2 + 2。

我们理清楚了堆如何在数组中存储堆。那么我们就可以开始实现堆了。

堆的声明

我们创建一个堆的类。并引用模板,因为堆存储的值的类型是不确定的。而堆需要提供以下几个成员。

成员变量:

T* _hp : 指向堆内存(非此数据结构的堆)的一块空间,存储的是当前堆中的数据。

size_t _size : 记录堆的大小,也是下一次push时的下标位置

size_t _ capacity : 记录堆的容量,如果和_size相等,则说明需要扩容了

成员函数:

Heap(size_t capacity = 16); 构造函数,采用了缺省参数。也可以用户手动传入一个参数,指明堆的容量。

~Heap(); 析构函数,对当前对象动态资源的销毁

template<class T>
class Heap
{
private:
	T* _hp;
	size_t _size;
	size_t _capacity;
public:
    Heap(size_t capacity = 16);
    ~Heap();
}

普通构造和析构的实现

我们的构造函数只需要为_hp开辟空间,并初始化 _size 和 _capacity即可。析构函数直接释放 _hp即可。

构造函数

Heap(size_t capacity = 16):_capacity(capacity),_size(0){
			_hp = new T[_capacity];
		}

析构函数

~Heap() {
			delete _hp;
			_size = _capacity = 0;
		}

插入函数push

那么我们要往堆中插入数据怎么办,我们可以写个push函数来为堆插入数据。 那么在写这个函数之前,我们要知道堆是如何push的。

堆的push过程:

1.把插入的值放入数组的末尾(也是堆的最后一个叶节点)

2.对这个节点进行向上调整

什么是向上调整????

就是让当前节点与它的父亲进行比较,我们拿大堆来说,如果父节点比当前节点小。那么就交换这两个节点,一直循环直到父节点比当前节点到或者当前节点已经是根节点时才结束。 举两个例子,我们有一个大堆。

在这里插入图片描述

这时候我们要新增一个节点,35,那么我们先把它加在末尾。

在这里插入图片描述

然后让当前节点与它的父节点比较大小。如果当前节点比父节点大,则交换位置(大堆的情况下)。

在这里插入图片描述

随后再让该节点和它的父亲比较,比它的父亲小,调整完毕。

第二个例子

依旧是刚刚那个堆,只不过我们把插入的数据换成了70

在这里插入图片描述

那么它会一直调整到根节点才结束。如图

在这里插入图片描述

搞明白了向上调整的原理后。我们就可以写出push函数代码了!

push函数代码

void UpDateCapacity()
{
    T* tmp = new T[_capacity * 1.5];  //开辟一个新的堆
    assert(tmp); //断言,扩容失败则崩溃
    memcpy(tmp, _hp, _size * sizeof(T)); //转移资源
    delete _hp;  //释放旧空间
    std::swap(tmp, _hp); //交换两个地址
    _capacity = _capacity * 1.5; //更新容量
}
//交换两个数的值
void swap(T& x, T& y)
{
    T tmp = x; 
    x = y; 
    y = tmp;
}
void Up()
{
    //从最后一个开始向上调整
    int child = _size - 1; 
    int parent = (child - 1) / 2; 
    while (child) //child != 0 
    {
        //大堆 , 孩子大于父亲则交换
        if (_hp[child] > _hp[parent])
        {
            swap(_hp[child], _hp[parent]);
            child = parent; //更新孩子
            parent = (child - 1) / 2; //更新父节点
         }
        else break;
    }
}
void push(const T& val)
{
    if (_size == _capacity) //检查当前空间是否满了
    {
        //扩容 
        UpDateCapacity();
    }
    _hp[_size++] = val; //插入
    Up();  //向上调整
}

移除节点函数pop

堆移除末尾节点是没有意义的,堆pop的都是根节点,也叫做堆顶。因为堆顶不是最大的,就是最小的。所以我们的pop函数就是移除堆顶中的元素。当然不能直接移除,如果直接移除会破坏堆的结构。所以我们时必须让堆顶与末尾节点交换。随后让堆顶向下调整即可。

pop的流程:

1.堆顶位置与末尾位置进行交换。

2.堆顶开始向下调整

什么是向下调整?

和向上调整差不多,向上调整是和父亲比较后决定是否交换。向下调整也是。如果是大堆的情况下,父亲与较大的那个孩子比较大小,如果比较大的孩子比父节点的值要大。那么则交换,小堆的情况则相反。

那么我们来演示一下。还是刚刚那个堆,不过我们要把70这个节点pop掉。

在这里插入图片描述

第一步,我们让堆顶与末尾交换。

在这里插入图片描述

随后删掉末尾元素(很简单,就是_size–即可)。

然后我们要先让它的两个子节点进行比较,因为是大堆,所以取较大的那个,也就是50。再拿50和35进行比较,50大于35,则交换。

在这里插入图片描述

然后我们继续向下调整,可是此时它已经没有右孩子了。那么就直接和左孩子比较。比左孩子大,则结束。

整个流程图:

在这里插入图片描述

pop代码

原理弄明白之后,直接上代码。

void Down()
{
    int parent = 0; //父节点
    int child = parent * 2 + 1;  // 左孩子节点
    while (parent < _size) /
    {
        if (child + 1 >= _size - 1 && _hp[child + 1] > _hp[child])
            child++; //如果右孩子存在且右孩子 > 左孩子,那么child变成右孩子
        if (child >= _size - 1) break; //左孩子不存在
        if (_hp[child] > _hp[parent]) //子节点>父节点
         {
            swap(_hp[parent], _hp[child]); //交换
            parent = child; //更新
            child = parent * 2 + 1; 
        }
        else break;
    }
}
void pop()
{
    assert(_size > 0); //保证堆里有元素
    //根节点和最后一个节点交换
    swap(_hp[0], _hp[_size-- - 1]); //堆顶和末尾交换
    Down(); //向下调整
}

top函数

top函数是获取堆顶元素的函数,我们可以在保证堆里有元素的情况下直接返回_hp的第0个元素即可。

T top()
{
    assert(_size > 0);
    return _hp[0];
}

引入仿函数

我们上面演示的是大堆的情况下,但是如果我们又想让它在不写多个类的情况下,既可以用大堆,也可以用小堆,那么该如何实现呢?这里我们可以在模板参数中引入仿函数。

首先我们要有2结构体/类,并重载它们的()操作符,传入两个参数用来比较,Greate类比较 >,Less类 比较小于

template<class T>
    struct Greater
    {
        bool operator()(T& x, T& y)
        {
            return x > y;
        }
    };
template<class T>
    struct Less
    {
        bool operator()(T& x, T& y)
        {
            return x < y;
        }
    };

然后我们在类模板声明处加上一个类型,并设置缺省。

template<class T,class Functor = Less<T>> 
class Heap
{
	.....
};

这样我们的类中就有一个Functor类型了,我们可以用这个类型创建一个对象,这个对象及你创建Heap对象时传入的模板参数。传入Less则创建Less对象,传入Greater则创建Greater对象。不传则用缺省的。

随后我们就可以修改向上和向下调整的代码了

void Up()
{
    Functor func; //创建一个Functor对象
    //从最后一个开始向上调整
    int child = _size - 1; 
    int parent = (child - 1) / 2; 
    while (child) //child != 0 
    {
        //大堆
        if (func(child,parent)) //用Functor对象的()操作符重载返回的bool值来判断真或者假
        {
            swap(_hp[child], _hp[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else break;
    }
}

void Down()
{
    Functor func; //创建一个Functor对象
    int parent = 0;
    int child = parent * 2 + 1; 
    while (parent < _size)
    {
         //用Functor对象的()操作符重载返回的bool值来判断真或者假
        if (child + 1 >= _size - 1 && func(_hp[child + 1],_hp[child]))
            child++; //如果右孩子存在且右孩子 > 左孩子,那么child变成右孩子
        if (child >= _size - 1) break; //左孩子不存在
        if (func(_hp[child],_hp[parent])) //用Functor对象的()操作符重载返回的bool值来判断真或者假
        {
            swap(_hp[parent], _hp[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else break;
    }
}

这样我们堆的基本功能也实现了,当然还有拷贝构造,赋值重载,移动构造…并没有完善。由于我们只是演示堆的实现,所以这些函数就不实现了,大家有兴趣的话也可以实现一下。以下是演示中的所有代码。

template<class T>
    struct Greater
    {
        bool operator()(T& x, T& y)
        {
            return x > y;
        }
    };
//建大堆
template<class T>
    struct Less
    {
        bool operator()(T& x, T& y)
        {
            return x < y;
        }
    };

template<class T,class Functor = Less<T>>
    class Heap
    {
        public:
        T* _hp;
        size_t _size;
        size_t _capacity;

        public:
        Heap(size_t capacity = 16):_capacity(capacity),_size(0){
            _hp = new T[_capacity];
        }
        ~Heap() {
            delete _hp;
            _size = _capacity = 0;
        }
        private:
        void UpDateCapacity()
        {
            T* tmp = new T[_capacity * 1.5];  //开辟一个新的堆
            assert(tmp);
            memcpy(tmp, _hp, _size * sizeof(T));
            std::swap(tmp, _hp);
            _capacity = _capacity * 1.5;
        }
        //交换两个数的值
        void swap(T& x, T& y)
        {
            T tmp = x; 
            x = y; 
            y = tmp;
        }
        void Up()
        {
            Functor func;
            //从最后一个开始向上调整
            int child = _size - 1; 
            int parent = (child - 1) / 2; 
            while (child) //child != 0 
            {
                //大堆
                if (func(child,parent))
                {
                    swap(_hp[child], _hp[parent]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else break;
            }
        }
        void Down()
        {
            Functor func;
            int parent = 0;
            int child = parent * 2 + 1; 
            while (parent < _size)
            {
                if (child + 1 >= _size - 1 && func(_hp[child + 1],_hp[child]))
                    child++; //如果右孩子存在且右孩子 > 左孩子,那么child变成右孩子
                if (child >= _size - 1) break; //左孩子不存在
                if (func(_hp[child],_hp[parent]))
                {
                    swap(_hp[parent], _hp[child]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else break;
            }
        }
        public:
        void push(const T& val)
        {
            if (_size == _capacity)
            {
                //扩容 
                UpDateCapacity();
            }
            _hp[_size++] = val;
            Up(); 
        }
        T top()
        {
            assert(_size > 0);
            return _hp[0];
        }
        void pop()
        {
            assert(_size > 0);
            //根节点和最后一个节点交换
            swap(_hp[0], _hp[_size-- - 1]);
            Down();
        }
    };

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

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

相关文章

C语言的浮点类型:float,double,long double

文章目录 浮点型概述浮点型常量浮点型常量的后缀 溢出上溢 overflow下溢 underflow 一个特殊的浮点值&#xff1a;NaN初始化 浮点型概述 float 称为单精度浮点型。 double 称为双精度浮点型。 long double 称为长双精度浮点型。 C 标准规定&#xff0c;float 必须至少精确到…

MySQL存储过程(二十四)

你相信吗&#xff0c; 相信那一天的夕阳吗? 上一章简单介绍了 MySQL的索引(二十三),如果没有看过,请观看上一章 一. 存储过程 MySQL从5.0版本开始支持存储过程和函数。存储过程和函数能够将复杂的SQL逻辑封装在一起&#xff0c; 应用程序无须关注存储过程和函数内部复杂的S…

【DBeaver】CLIENT_PLUGIN_AUTH is required

1、右键打开编辑连接弹窗&#xff0c;进入编辑驱动设置 2、选择“库” 3、全部删掉&#xff0c;然后点击“重置为默认状态” 4、选中然后点击“下载更新” 5、点击版本号&#xff0c;选择与mysql相同的版本 6、最后一步

使用可视化docker浏览器,轻松实现分布式web自动化

01、前言 顺着docker的发展&#xff0c;很多测试的同学也已经在测试工作上使用docker作为环境基础去进行一些自动化测试&#xff0c;这篇文章主要讲述我们在docker中使用浏览器进行自动化测试如果可以实现可视化&#xff0c;同时可以对浏览器进行相关的操作。 02、开篇 首先…

leetcode 33.搜索旋转排序数组

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;搜索旋转排序数组 ps&#xff1a; 本题是二分查找的变形&#xff0c;旋转排序数组之后其实会形成两个有序的区间。算出平均下标先判断是否与 target 相等&#xff0c;因为这样可以减少代码的冗余。如果前者不成立则使用平…

组合总和 II——力扣40

文章目录 题目描述法一 回溯 题目描述 法一 回溯 class Solution{ public:vector<pair<int, int>>freq;vector<vector<int>> res;vector<int> seq;void dfs(int pos, int rest){//如果目标值为0&#xff0c;说明可能有一个组合或者rest本身为0 …

(7.28-8.3)【大数据新闻速递】《数字孪生工业软件白皮书》、《中国绿色算力发展研究报告》发布;华为ChatGPT要来了

【数字孪生工业软件白皮书&#xff08;2023&#xff09;】 近日&#xff0c;第七届数字孪生与智能制造服务学术会议成功举行&#xff0c;2023《数字孪生工业软件白皮书》在会上正式发布。《白皮书》在《Digital Twin》国际期刊专家顾问委员会指导下&#xff0c;由国家重点研发计…

万字长文之 Serverless 实战详细指南

目录 前言 简易博客系统功能概要 云函数的初始化与基础配置 Tencent Serverless Toolkit for VS Code 数据库选择和设计 数据库选择 数据库设计 云函数自定义域名与 API 网关映射 域名解析 API 网关映射 云函数中的路由设计 云函数中的代码组织 Controller Servi…

搭建Django+pyhon+vue自动化测试平台

Django安装 使用管理员身份运行pycharm使用local 1 pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple 检查django是否安装成功 1 python -m django --version 创建项目 1 1 django-admin startproject test cd 切换至创建的项目中启动django项目…

欧拉函数与筛法求欧拉函数

目录 欧拉函数欧拉函数的定义欧拉函数的公式欧拉函数的公式推导欧拉定理典型例题代码实现 筛法求欧拉函数思路分析经典例题代码实现 欧拉函数 欧拉函数的定义 对于任意正整数 n n n,欧拉函数 φ ( n ) φ(n) φ(n) 表示小于或等于 n n n 的正整数中&#xff0c;与 n n n …

企业网盘解析:高效的企业文件共享工具

伴随着信息技术的发展&#xff0c;越来越多的企业选择了基于云存储的企业网盘来进行企业数据存储。那么企业网盘是什么意思呢&#xff1f; 企业网盘是什么意思&#xff1f; 企业网盘&#xff0c;又称企业云盘&#xff0c;顾名思义是为企业提供的网盘服务。除了服务对象不同外&…

【maven】构建项目前clean和不clean的区别

其实很简单&#xff0c;但是百度搜了一下&#xff0c;还是没人能简单说明白。 搬用之前做C项目时总结结论&#xff1a; 所以自己在IDE里一遍遍测试程序能否跑通的时候&#xff0c;不需要clean&#xff0c;因为反正还要改嘛。 但是这个项目测试好了&#xff0c;你要打成jar包给…

全面升级:华为鸿蒙HarmonyOS4正式发布,玩趣个性化,小艺AI升级

8月4日新闻&#xff0c;今天下午&#xff0c;华为正式发布了最新版本的鸿蒙操作系统——HarmonyOS 4&#xff01; 在华为发布会上&#xff0c;鸿蒙HarmonyOS迎来了一系列令人激动的功能升级。其中包括个性化空间、多种生产力工具以及增强的手机AI助手"小艺"。这次更…

Docker入门及安装

文章目录 1.Docker概述:1.什么是docker2.为什么使用docker3.docker优点4.docker资源网址 2.Docker安装1.卸载旧版本dorcker(非必要)2.设置Docker仓库安装docker引擎4.启动验证docker卸载docker 3.Docker底层原理1.docker的结构和基本概念2.docker为什么比虚拟机快 1.Docker概述…

【新版系统架构补充】-传输介质、子网划分

传输介质 双绞线&#xff1a;无屏蔽双绞线UTP和屏蔽双绞线STP&#xff0c;传输距离在100m内 网线安装标准&#xff1a; 光纤&#xff1a;由纤芯和包层组成&#xff0c;分多模光纤MMF、单模光纤SMF 无线信道&#xff1a;分为无线电波和红外光波 通信方式和交换方式 单工…

简单易懂的生鲜蔬果小程序开发指南

随着人们对健康意识的提高&#xff0c;越来越多的人开始注重饮食健康&#xff0c;选择新鲜的果蔬产品。为了满足市场需求&#xff0c;制作一个果蔬配送小程序成为了一个不错的选择。本文将详细介绍如何快速制作一个果蔬配送小程序。 第一步&#xff1a;登录乔拓云网后台&#x…

InterProcessMutex 类的作用和使用

InterProcessMutex 类是Curator框架中的一个分布式锁的实现&#xff0c;用于在分布式环境下实现互斥锁。 InterProcessMutex 的使用步骤如下&#xff1a; 创建 CuratorFramework 客户端实例&#xff0c;并启动客户端连接到 ZooKeeper 服务器。使用 CuratorFramework 的 creat…

leetcode37. 解数独(java)

解数独 解数独题目描述回溯算法代码演示 回溯算法 解数独 难度 困难 leetcode37. 解数独 题目描述 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 1.数字 1-9 在每一行只能出现一次。 2.数字 1-9 在每一列只能出现一次。 3.数字…

2020-2023中国高等级自动驾驶产业发展趋势研究

1.1 概念界定 2020-2023中国高等级自动驾驶产业发展趋势研究Trends in China High-level Autonomous Driving from 2020 to 2023自动驾驶发展过程中&#xff0c;中国出现了诸多专注于研发L3级以上自动驾驶的公司&#xff0c;其在业界地位也越来越重要。本报告围绕“高等级自动…

如何搭建自动化测试框架?资深测试整理的PO模式,一套打通自动化...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Po模型介绍 1、简…