【C++习题集】-- 堆

(用于复习)

目录

树概念及结构

名词概念

二叉树概念及结构

特殊的二叉树

满二叉树

完全二叉树

运算性质

二叉树存储结构

顺序存储

链式存储

堆 - 顺序存储

堆的性质

堆的实现

堆的应用

堆排序

直接建堆法


树概念及结构

        概念非线性的数据结构(形成的倒挂似树的结构 - 根朝上,叶朝下,子树之间不能有交集)。

名词概念

  • 节点的度一个节点含有的子树的个数称为该节点的度。
  • 叶节点或终端节点:度为0的节点称为叶节点。
  • 非终端节点或分支节点:度不为0的节点。
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点。
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点。
  • 树的度一棵树中,最大的节点的度称为树的度。
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
  • 树的高度或深度树中节点的最大层次。
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟。
  • 节点的祖先:从根到该节点所经分支上的所有节点。
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m(m>0)棵互不相交的树的集合称为森林。

二叉树概念及结构

        由一个根节点加上两棵别称为左子树和右子树的二叉树组成 - 子树可为空。

  • 不存在度大于2的结点。

特殊的二叉树

满二叉树

        每一个层的结点数都达到最大值,则结点总数:2^k - 1(K层数)

完全二叉树

        特殊的完全二叉树 - 最后一层不满,但是是左到右是连续的

        (满二叉树是特殊的完全二叉树)

运算性质

  • 根节点的层数为1,则第i层上最多有2^(i - 1)个结点
  • 根节点的层数为1,则深度h的最大结点数是2^h - 1
  • 根节点的层数为1,n个结点的满二叉树的深度h = log2(n + 1)
  • 如果度为0其叶结点个数为n,度为2的分支结点个数为m,则有:n = m + 1
  • n个结点的完全二叉树,以数组顺序对所有节点开始编号:
  1. 若i>0,i位置节点的双亲序号:(i - 1) / 2
  2. 若2i + 1 < n,左孩子序号:2i + 1,2i + 1 >= n否则无左孩子
  3. 若2i + 2 < n,右孩子序号:2i + 2,2i + 2 >= n否则无右孩子

一个具有767个节点的完全二叉树,其叶子节点个数为()

A、383
B、384
C、385
D、386
------------------------------------------
正确答案:B
------------------------------------------
解析:
        不要只想最后一层,倒数第二层也是会有叶子节点的。
首先以:

        可以推算出是第1 ~ 9层为满二叉树,对应节点数:511。可以知道最后一层一定为叶子节点:256个。

        然后根据完全二叉树是最后一层不满,但是是左到右是连续的,于是256 / 2 = 128,所以倒数第二层有128个是最后一层的父节点。

 再根据:

        可知倒数第二层有256个节点,于是叶子节点:256 + 256 - 128 = 384。

二叉树存储结构

顺序存储

        用数组来存储,适合表示完全二叉树。

  • 物理上:数组
  • 逻辑上:二叉树

链式存储

        链表来表示一棵二叉树。

  • 二叉链:数据域和左右指针域
  • 三叉链:数据域和左右上指针域

堆 - 顺序存储

        堆是一种特殊的完全二叉树,只不过父亲与儿子节点间有关系。顺序存储的完全二叉树典型的就是堆。(普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储)

堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值
    • 小堆:父亲位,比孩子位,要小
    • 大堆:父亲位,比孩子位,要大
  • 堆总是一棵完全二叉树

堆的实现

#include <iostream>
#include <cassert>

namespace qcr_heap
{
    typedef int HeapType;
    struct Heap
    {
        int64_t _capacity; // 动态开辟可用大小
        int64_t _size;     // 实际数据占用大小
        HeapType *_array;  // 动态开辟一维数组
    };

    /*********
     * 初始化堆
     *********/
    void HeapInit(Heap *heap)
    {
        assert(heap);

        heap->_capacity = 0;
        heap->_size = 0;
        heap->_array = 0;
    }

    /*********
     * 销毁堆
     *********/
    void HeapDestory(Heap *heap)
    {
        assert(heap);

        heap->_capacity = 0;
        heap->_size = 0;
        free(heap->_array);
        heap->_array = nullptr;
    }

    /*********
     * 小根堆
     *********/
    bool less(HeapType element_1, HeapType element_2)
    {
        return element_1 < element_2;
    }

    /*********
     * 大根堆
     *********/
    bool greater(HeapType element_1, HeapType element_2)
    {
        return element_1 > element_2;
    }

    /*********
     * 交换数据
     *********/
    void swap(HeapType *element_1, HeapType *element_2)
    {
        HeapType tmp = *element_1;
        *element_1 = *element_2;
        *element_2 = tmp;
    }

    /*****************************
     * 向上调整
     *   heap: 输入型参数,堆地址
     *   child: 输入型参数,排序的插入节点
     *   Func: 输入型参数,大小堆
     *****************************/
    void AdjustUp(Heap *heap, int64_t child, bool (*Func)(HeapType, HeapType))
    {
        assert(heap);

        int64_t parent = (child - 1) / 2;
        while (child > 0)
        {
            if (Func(heap->_array[child], heap->_array[parent]))
            {
                swap(&(heap->_array[child]), &(heap->_array[parent]));
                child = parent;
                parent = (child - 1) / 2;
            }
            else
                break;
        }
    }

    /*****************************
     * 向下调整
     *   heap: 输入型参数,堆地址
     *   root: 输入型参数,排序的根节点
     *   Func: 输入型参数,大小堆
     *****************************/
    void AdjustDown(Heap *heap, int64_t root, bool (*Func)(HeapType, HeapType))
    {
        assert(heap);

        int64_t parent = root;
        int64_t child = parent * 2 + 1;
        while (child < heap->_size)
        {
            if (child + 1 < heap->_size && Func(heap->_array[child + 1], heap->_array[child]))
            {
                child++;
            }

            if (Func(heap->_array[child], heap->_array[parent]))
            {
                swap(&(heap->_array[child]), &(heap->_array[parent]));
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break; // 符合堆就成立了,就没必要进行交换了。
            }
        }
    }

    /*****************************
     * 存入数据
     *   heap: 输入型参数,堆地址
     *   data: 输入型参数,插入的数据
     *   Func: 输入型参数,大小堆
     *****************************/
    void HeapPush(Heap *heap, HeapType data, bool (*Func)(HeapType, HeapType))
    {
        assert(heap);

        if (heap->_capacity == heap->_size)
        {
            int64_t newcapacity = heap->_capacity == 0 ? 5 : heap->_capacity * 2;
            HeapType * tmp = (HeapType *)realloc(heap->_array, heap->_capacity*sizeof(HeapType);
            if (tmp == nullptr)
		    {
			    printf("Capacuty Get Error!\n");
			    exit(-1);
		    }
            heap->_array = tmp;
            heap->_capacity = newcapacity;
        }

        heap->_array[heap->_size] = data;
        AdjustUp(heap, heap->_size, Func);
        (heap->_size)++;
    }

    /*****************************
     * 按顺序全部输出
     *   heap: 输入型参数,堆地址
     *****************************/
    void HeapPrint(Heap *heap)
    {
        assert(heap);

        for (uint64_t i = 0; i < heap->_size; i++)
        {
            std::cout << heap->_array[i] << " ";
        }
        std::cout << '\n';
    }

    /*****************************
     * 首元素
     *   heap: 输入型参数,堆地址
     *****************************/
    HeapType HeapTop(Heap *heap)
    {
        assert(heap);
        assert(heap->_size > 0);
        return heap->_array[0];
    }

   /*****************************
     * 判空
     *   heap: 输入型参数,堆地址
     *****************************/
    bool HeapEmpty(Heap *heap)
    {
        assert(heap);
        return heap->_size == 0;
    }

   /*****************************
     * 有效数据个数
     *   heap: 输入型参数,堆地址
     *****************************/
    int HeapSize(Heap *heap)
    {
        assert(heap);
        return heap->_size;
    }

   /*****************************
     * 判空
     *   heap: 输入型参数,堆地址
     *   Func: 输入型参数,大小堆
     *****************************/
    void HeapPop(Heap *heap, bool (*Func)(HeapType, HeapType))
    {
        assert(heap);
        assert(heap->_size > 0);

        heap->_array[0] = heap->_array[heap->_size - 1];
        (heap->_size)--;
        AdjustDown(heap, 0, Func);
    }
}
已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是()
A、1
B、2
C、3
D、4
------------------------------------------
正确答案:B
------------------------------------------
解析:
        首先我们需要知道,删除对应的调整算法是向下调整,所以其实在比较中有一个很重要的一项就是左右节点的比较,于是此处本质上的比较是需要在加上一次左右节点的比较。

堆的应用

堆排序

        利用堆删除思想来进行排序。

TOP-K问题

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

  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
面试题 17.14. 最小K个数 - 力扣(LeetCode)

class Solution
{
public:
    // 向上建堆
    void adjustUp(vector<int> &nums, int child)
    {
        int parent = (child - 1) / 2;
        while (child > 0)
        {
            if (nums[child] > nums[parent])
            {
                swap(nums[child], nums[parent]);
                child = parent;
                parent = (child - 1) / 2;
            }
            else
            {
                break;
            }
        }
    }

    // 向下建堆
    void adjustDown(vector<int> &nums, int parent)
    {
        int child = parent * 2 + 1;
        while (child < nums.size())
        {
            if (child + 1 < nums.size() && nums[child + 1] > nums[child])
            {
                child++;
            }

            if (nums[child] > nums[parent])
            {
                swap(nums[child], nums[parent]);
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }

    // 堆排序的TOP-k问题
    vector<int> smallestK(vector<int> &arr, int k)
    {
        vector<int> nums;
        nums.reserve(k);

        // 前K个元素来建堆
        for (int i = 0; i < k; i++)
        {
            nums.push_back(arr[i]);
            adjustUp(nums, nums.size() - 1);
        }

        // 对比堆顶元素
        if (k != 0)
        {
            for (int i = k; i < arr.size(); i++)
            {
                if (arr[i] < nums[0])
                {
                    nums[0] = arr[i];
                    adjustDown(nums, 0);
                }
            }
        }
        return nums;
    }
};

        并不是最优的,并且还实现了两个堆算法,编码效率过低。

直接建堆法

        原本利用向上建堆的方式,是并不够完美的,建堆的时间复杂度为O(N)。

        而直接建堆法时间复杂度O(logn),其根本是利用向下建堆实现。

for (int i = (size - 1 - 1) / 2; i >= 0; i--)
{
	ADjustDown(nums, i);
}
class Solution
{
public:
    // 向下建堆
    void adjustDown(vector<int> &nums, int parent)
    {
        int child = parent * 2 + 1;
        while (child < nums.size())
        {
            if (child + 1 < nums.size() && nums[child + 1] > nums[child])
            {
                child++;
            }

            if (nums[child] > nums[parent])
            {
                swap(nums[child], nums[parent]);
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }

    // 堆排序的TOP-k问题
    vector<int> smallestK(vector<int> &arr, int k)
    {
        vector<int> nums;
        nums.reserve(k);

        // 前K个元素来建堆
        for (int i = 0; i < k; i++)
        {
            nums.push_back(arr[i]);
        }

        for(int i = (k - 1 - 1) / 2; i >= 0; i--)
        {
            adjustDown(nums, i);
        }

        // 对比堆顶元素
        if (k != 0)
        {
            for (int i = k; i < arr.size(); i++)
            {
                if (arr[i] < nums[0])
                {
                    nums[0] = arr[i];
                    adjustDown(nums, 0);
                }
            }
        }
        return nums;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unity 之 Input.GetMouseButtonDown 的使用

文章目录 Input.GetMouseButtonDown Input.GetMouseButtonDown 当涉及到处理鼠标输入的时候&#xff0c;Input.GetMouseButtonDown 是一个常用的函数。它可以用来检测鼠标按键是否在特定帧被按下。下面我会详细介绍这个函数&#xff0c;并举两个例子说明如何使用它。 函数签名…

功能性需求与非功能性需求的区别

如果你曾经负责过软件项目开展的全过程&#xff0c;就会知道需求定义在项目后期的重要性。清晰、明确的需求定义不仅有助于有效地管理客户期望&#xff0c;也有助于指导项目的顺利开展。 在项目前期阶段&#xff0c;如果需求定义不清晰&#xff0c;就会导致项目范围和成果定义…

【HCIP】10.路由策略

&#x1f4ce;13 路由策略与路由控制.pptx 通过修改路由的属性&#xff0c;影响了路由的生成及选路&#xff0c;最终影响了转发流量的路径&#xff1b;控制平面。 ACL IP prefix Filter-Policy Router-Policy 笔记

Delphi 安卓App自动升级

Androidapi.JNI.Support引用这个单元 procedure _InstallApk(Apk: string); varLFile: JFile;LIntent: JIntent; beginLFile : TJFile.JavaClass.init(StringToJString(ExtractFilePath(Apk)), StringToJstring(ExtractFileName(Apk)));LIntent : TJIntent.Create;LIntent.set…

无人机工程安全巡检:主要应用与实施策略

无人机工程安全巡检是指使用无人机技术&#xff0c;对工程项目进行系统的、周期性的监测和检查&#xff0c;以确保工程的安全性、稳定性及其与设计的符合性。这包括但不限于建筑物、桥梁、道路、隧道、大坝等各种大型工程项目。无人机工程安全巡检不仅大大提高了效率&#xff0…

【论文阅读】HOLMES:通过关联可疑信息流进行实时 APT 检测(SP-2019)

HOLMES: Real-time APT Detection through Correlation of Suspicious Information Flows S&P-2019 伊利诺伊大学芝加哥分校、密歇根大学迪尔伯恩分校、石溪大学 Milajerdi S M, Gjomemo R, Eshete B, et al. Holmes: real-time apt detection through correlation of susp…

RabbitMQ启动服务报错1067解决方案

首先&#xff1a; 你的 Erlang正确下载安装&#xff0c;且配置完成环境变量&#xff0c;可在命令行键入erl&#xff0c;若显示erlang版本则说明环境变量配置成功。如下&#xff1a; 原因分析&#xff1a; 1. 电脑名称为中文 2. erlang和rabbitmq版本不匹配 3. 安装目录有空格…

插入排序优化——超越归并排序的超级算法

插入排序及优化 插入排序算法算法讲解数据模拟代码 优化思路一、二分查找二、copy函数 优化后代码算法的用途题目&#xff1a;数星星&#xff08;POJ2352 star&#xff09;输入输出格式输入格式&#xff1a;输出格式 输入输出样例输入样例输出样例 题目讲解步骤如下AC 代码 插入…

排序(七种排序)

1.插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 1.插入排序 1.1思路 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为 止&#xff0c;得到一个新的有序序列 1.2实现 //插入排…

【BASH】回顾与知识点梳理(三十六)

【BASH】回顾与知识点梳理 三十六 三十六. 认识与分析登录档36.1 什么是登录档CentOS 7 登录档简易说明登录档的重要性Linux 常见的登录档档名登录档所需相关服务 (daemon) 与程序CentOS 7.x 使用 systemd 提供的 journalctl 日志管理 登录档内容的一般格式 36.2 rsyslog.servi…

C#程序配置读写例子 - 开源研究系列文章

今天讲讲关于C#的配置文件读写的例子。 对于应用程序的配置文件&#xff0c;以前都是用的ini文件进行读写的&#xff0c;这个与现在的json类似&#xff0c;都是键值对应的&#xff0c;这次介绍的是基于XML的序列化和反序列化的读写例子。对于ini文件&#xff0c;操作系统已经提…

JavaScript 进阶 第四天

深浅拷贝异常处理处理this性能优化 一. 深浅拷贝 深浅拷贝只针对引用类型 1.1 浅拷贝 拷贝的是地址常见方法 &#xff08;1&#xff09;拷贝对象&#xff1a;Object.assign() / 展开运算符 {...obj} &#xff08;2&#xff09;拷贝数组&#xff1a;Array.prototype.c…

Centos 8 网卡connect: Network is unreachable错误解决办法

现象1、ifconfig没有ens160配置 [testlocalhost ~]$ ifconfig lo: flags73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopba…

windows安装使用RocketMQ常见问题,及springboot整合

win安装rocketmq 官网下载二进制包&#xff1a;https://rocketmq.apache.org/download 解压到不包含中文及空格的目录&#xff0c;配置环境变量 ROCKETMQ_HOME4. 修改runbroker.cmd和runserver.cmd文件 文件地址在rocketmq安装目录下的bin文件夹中。 如果不修改可能会遇见以…

opencv进阶03-图像与鼠标的交互示例

在处理图像时&#xff0c;可能需要与当前正在处理的图像进行交互。OpenCV 提供了鼠标事件&#xff0c;使用户可以通过鼠标与图像交互。鼠标事件能够识别常用的鼠标操作&#xff0c;例如&#xff1a;针对不同按键的单击、双击&#xff0c;鼠标的滑动、拖曳等。 例如&#xff0c;…

windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】

windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】 文章目录 windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访…

数字化转型能带来哪些价值?_光点科技

随着科技的迅猛发展&#xff0c;数字化转型已成为企业和组织的一项重要战略。它不仅改变了商业模式和运营方式&#xff0c;还为各行各业带来了诸多新的机遇和价值。在这篇文章中&#xff0c;我们将探讨数字化转型所能带来的价值。 数字化转型能够显著提升效率和生产力。通过引入…

Ubuntu 20.04使用Livox mid 360 测试 FAST_LIO

前言 Livox mid360需要使用Livox-SDK2&#xff0c;而非Livox-SDK&#xff0c;以及对应的livox_ros_driver2 。并需要修改FAST_LIO中部分代码。 1. 安装Livox-SDK2 参考官方教程。 1.1. 安装CMake sudo apt install cmake1.2. 安装编译Livox-SDK2 git clone https://github…

C++多态

一、多态的定义及实现 多态是在不同继承关系的类对象&#xff0c;去调用同一函数&#xff0c;产生了不同的行为。比如Student继承了 Person。Person对象买票全价&#xff0c;Student对象买票半价。 构成多态的两个条件&#xff1a; 1、必须通过基类的指针或者引用调用虚函数…

废品回收抢单派单小程序开源版开发

废品回收抢单派单小程序开源版开发 用户注册和登录&#xff1a;用户可以通过手机号码注册和登录小程序&#xff0c;以便使用废品回收抢单派单功能。废品回收订单发布&#xff1a;用户可以发布废品回收订单&#xff0c;包括废品种类、数量、回收地点等信息。废品回收抢单&#…
最新文章