【C++习题集】-- 堆

news2024/12/25 14:57:04

(用于复习)

目录

树概念及结构

名词概念

二叉树概念及结构

特殊的二叉树

满二叉树

完全二叉树

运算性质

二叉树存储结构

顺序存储

链式存储

堆 - 顺序存储

堆的性质

堆的实现

堆的应用

堆排序

直接建堆法


树概念及结构

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

名词概念

  • 节点的度一个节点含有的子树的个数称为该节点的度。
  • 叶节点或终端节点:度为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.coloradmin.cn/o/905261.html

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

相关文章

shell脚本——文件三剑客之sed

目录 一.sed基本用法及选项 ​二.sed脚本语法及命令 三.sed的查找替换使用 四.后向引用 五.变量 一.sed基本用法及选项 sed [选项]... {自身脚本语法};.... [input file...] seq 10 |sed #生成1-10数字传给sed #该格式报错&#xff0c;基本格式中的{自身脚本语法}不…

Stable Diffusion的使用以及各种资源

Stable Diffsuion资源目录 SD简述sd安装模型下载关键词&#xff0c;描述语句插件管理controlNet自己训练模型 SD简述 Stable Diffusion是2022年发布的深度学习文本到图像生成模型。它主要用于根据文本的描述产生详细图像&#xff0c;尽管它也可以应用于其他任务&#xff0c;如…

【SA8295P 源码分析】06 - SA8295P XBL Loader 阶段 sbl1_main_ctl 函数代码分析

【SA8295P 源码分析】06 - SA8295P XBL Loader 阶段 sbl1_main_ctl 函数代码分析 一、XBL Loader 汇编源码分析1.1 解析 boot\QcomPkg\XBLLoader\XBLLoader.inf1.2 boot\QcomPkg\XBLDevPrg\ModuleEntryPoint.S&#xff1a;跳转 sbl1_entry 函数1.3 XBLLoaderLib\sbl1_Aarch64.s…

shell脚本之循环语句

循环语句 循环含义 将某代码段重复运行多次&#xff0c;通常有进入循环的条件和退出循环的条件 for循环语句 一般知道循环次数使用for循环 第一类 格式1&#xff1a; for名称 in 取值次数;do;done; 格式2&#xff1a; for 名称 in {取值列表} do done# 打印20次 for i i…

docker安装redis7-分片集群

说明 系统&#xff1a;CentOS7.9 redis&#xff1a;7.0.5 因资源有限所有节点部署在一台宿主机上&#xff0c;总共启动6个redis实例&#xff0c;实例对应端口分别从6380-6385&#xff0c;文章中给的有执行脚本&#xff0c;方便配置redis和操作redis实例 下载镜像 docker …

JVM的元空间了解吗?

笔者近期在面试的时候被问到了这个问题&#xff0c;元空间也是Java8当时的一大重大革新&#xff0c;之前暑期实习求职的时候有专门看过&#xff0c;但是近期秋招的时候JVM相关的内容确实有点生疏了&#xff0c;故在此进行回顾。 结构 首先&#xff0c;我们应了解JVM的堆结构&a…

c++——引用(语法、引用特性、常引用、函数返回值引用和指针与引用的不同点)

c中的引用 一、引用 1、引用的概念&#xff1a;给变量取别名。 形式&#xff1a;原类型名& 别名 引用实体旧名&#xff1b; 2、特性&#xff1a; ①引用定义时必须初始化 ②引用一旦初始化之后就不能再改变引用的指向 ③不能引用NULL ④&再等号的左边为引用&…

FPGA原理与结构——ROM IP的使用与测试

一、前言 本文介绍Block Memory Generator v8.4 IP核 实现ROM&#xff0c;在学习一个IP核的使用之前&#xff0c;首先需要对于IP核的具体参数和原理有一个基本的了解&#xff0c;具体可以参考&#xff1a; FPGA原理与结构——块RAM&#xff08;Block RAM,BRAM&#xff09;http…

04_18内存反碎片技术,什么时候适合进行内存碎片整理

前言 内存碎片分为内部碎片和外部碎片&#xff0c;内部碎片指内存页里面的碎片&#xff0c;外部碎片指空闲的内存页分散&#xff0c;很难找到一组物理地址连续的空间内存页&#xff0c;无法满足超过一页的内存分配请求。 虚拟可移动区域 可移动区域&#xff08;ZONE_MOVABLE…

A 题国际旅游网络的大数据分析-详细解析与代码答案(2023 年全国高校数据统计与调查分析挑战赛

请你们进行数据统计与调查分析&#xff0c;使用附件中的数据&#xff0c;回答下列问题&#xff1a; ⚫ 问题 1: 请进行分类汇总统计&#xff0c;计算不同国家 1995 年至 2020 年累计旅游总人数&#xff0c;从哪个国家旅游出发的人数最多&#xff0c;哪个国家旅游到达的人数最多…

【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用

【JavaEE】Spring AOP&#xff08;1&#xff09; 文章目录 【JavaEE】Spring AOP&#xff08;1&#xff09;1. Spring AOP 是什么1.1 AOP 与 Spring AOP1.2 没有AOP的世界是怎样的1.3 AOP是什么 2. Spring AOP 框架的学习2.1 AOP的组成2.1.1 Aspect 切面2.1.2 Pointcut 切点2.1…

微人事 部门管理 模块 (十五)

部门管理的树展示和搜索 数据展示页是个树&#xff0c;我们一次性把数据加载出来也可以通过点一次id加载查询出来出来子部门&#xff0c;我们用一次拿到说有json数据加载出来 数据不多可以用递归&#xff0c;数据很多就用懒加载的方式 由于子部门比较深就不适合&#xff0c;权…

# 59. python的类与对象-更新

[目录] 文章目录 59. python的类与对象-更新1.面向对象编程2.什么是类3.什么是对象4.如何描述对象5.对象的属性和方法6.Python中的类7.type()函数查看数据类型8.类在Python中的应用9.总结 【正文】 59. python的类与对象-更新 1.面向对象编程 本节内容特别抽象&#xff0c;初…

Web3和去中心化:互联网的下一个演化阶段

文章目录 Web3和去中心化的定义Web3&#xff1a;去中心化&#xff1a; 为什么Web3和去中心化如此重要&#xff1f;数据隐私和安全&#xff1a;去中心化的创新&#xff1a;去除中间商&#xff1a; Web3和去中心化的应用领域去中心化金融&#xff08;DeFi&#xff09;&#xff1a…

wustojc2003求整数均值

#include <stdio.h> int main() {int a,b,c,d;double A;scanf("%d%d%d%d",&a,&b,&c,&d);A(double)((abcd)/4.0);//强转成doubleprintf("Sum %d\n",abcd);printf("Average %.2lf",A);return 0;}

C++笔记之基类指针动态地指向某一个子类情况列举

C笔记之基类指针动态地指向某一个子类情况列举 code review! 文章目录 C笔记之基类指针动态地指向某一个子类情况列举1.基本的多态示例2.基类中的成员函数可以设置为纯虚函数3.将基本示例修改为使用智能指针并在堆上实例化子类4.父类指针指向基类后&#xff0c;可以去调用只有…

【JUC系列-01】深入理解JMM内存模型的底层实现原理

一&#xff0c;深入理解JMM内存模型 1&#xff0c;什么是可见性 在谈jmm的内存模型之前&#xff0c;先了解一下并发并发编程的三大特性&#xff0c;分别是&#xff1a;可见性&#xff0c;原子性&#xff0c;有序性。可见性指的就是当一个线程修改某个变量的值之后&#xff0c…

shell脚本之sed

sed sed 即 Stream EDitor&#xff0c;和 vi 不同&#xff0c;sed是行编辑器 基本用法 sed [选项] 自身脚本用法 [支持输入标准输入管道] 常用选项&#xff1a;-n 不输出模式空间内容到屏幕&#xff0c;即不自动打印-e 多点编辑-f FILE 从指定文件中读取编辑脚本-r, -E 使用…

【TypeScript】内置对象

JavaScript 中很多内置对象&#xff0c;可以直接在 TypeScript 中当做定义好了的类型。 ECMAScript 的内置对象 Boolean、Number、String、RegExp、Date、Error、XMLHttpRequest 等 let num: Number new Number(1) let date: Date new Date() let reg: RegExp new RegExp(…

WTF Langchain极简入门: 03. 数据连接

加载文档 langchain提供多种文档加载器&#xff0c;支持多种格式、来源的文件。可以从本地存储系统加载文件&#xff0c;也可以从网上加载远端文件。想了解LangChain所支持的所有文档加载器&#xff0c;请参考Document Loaders。 在本系列课程中&#xff0c;我们将使用最基本…