比特数据结构与算法(第四章_中)堆的分步构建

news2024/9/27 7:21:30

不清楚堆的概念和性质公式可以先到上一篇看看

链接:比特数据结构与算法(第四章_上)树和二叉树和堆的概念及结构_GR C的博客-CSDN博客

堆的逻辑结构是完全二叉树,物理(存储)结构是数组

1.完整Heap.h

和以前学的数据结构一样,先定义出堆的结构体,然后实现接口函数。

先给出Heap.h的代码:

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap 
{
    HPDataType* array;  //指向动态开辟的数组
    int size;           //有效数据的个数
    int capacity;       //容量空间的大小
} Heap;

void HeapInit(Heap* php);//初始化堆
void HeapDestroy(Heap* php);//堆的销毁
void HeapPrint(Heap* php);//堆的打印
bool HeapEmpty(Heap* php);//判断堆是否为空
HPDataType HeapTop(Heap* php);//返回堆顶数据
int HeapSize(Heap* php);//统计堆的个数
void HeapCheckCapacity(Heap* php);//检查容量
void Swap(HPDataType* px, HPDataType* py);//交换函数

void HeapPush(Heap* php, HPDataType x);//堆的插入

void BigAdjustUp(int* arr, int child);//大根堆上调

void SmallAdjustUp(int* arr, int child);//小根堆上调

void HeapPop(Heap* php);//堆的删除

void SmallAdjustDown(int* arr, int n, int parent);//小根堆下调

void BigAdjustDown(int* arr, int n, int parent);//大根堆下调

2.八个简单函数

这八个函数前几篇数据结构文章都讲过了

void HeapInit(Heap* php)//初始化堆
{
    assert(php);
    php->array = NULL;
    php->size = php->capacity = 0;
}

void HeapDestroy(Heap* php) //堆的销毁
{
    assert(php);
    free(php->array);
    php->capacity = php->size = 0;
}

void HeapPrint(Heap* php) //堆的打印
{
    for (int i = 0; i < php->size; i++) 
    {
        printf("%d ", php->array[i]);
    }
    printf("\n");
}

bool HeapEmpty(Heap* php)//判断堆是否为空
{
    assert(php);

    return php->size == 0; // 如果为size为0则表示堆为空
}

HPDataType HeapTop(Heap* php) //返回堆顶数据
{
    assert(php);
    assert(!HeapIsEmpty(php));

    return php->array[0];
}

int HeapSize(Heap* php) //统计堆的个数
{
    assert(php);

    return php->size;
}

void HeapCheckCapacity(Heap* php) //检查容量 写过很多次了
{
    if (php->size == php->capacity) 
    {
        int newCapacity = php->capacity == 0 ? 4 : (php->capacity * 2); 
   HPDataType* tmpArray = (HPDataType*)realloc(php->array, sizeof(HPDataType) * newCapacity);
        if (tmpArray == NULL)
        {  
            printf("realloc failed!\n");
            exit(-1);
        }
        //更新他们的大小
        php->array = tmpArray;
        php->capacity = newCapacity;
    }
}

void Swap(HPDataType* px, HPDataType* py) //交换函数
{
    HPDataType tmp = *px;
    *px = *py;
    *py = tmp;
}

3.大堆的插入(HeapPush)

void HeapPush(HP* php, HPDataType x) 
{
    assert(php);  
    // 写到这里写一个检查是否需要增容
    HeapCheckCapacity(php);
    // 插入数据
    php->array[php->size] = x;
    php->size++;
    // 写到这里写一个向上调整 传入目标数组,和插入的数据(即 size - 1)。
    AdjustUp(php->array, php->size - 1); 
}

插入的核心思路:

① 先将元素插入到堆的末尾,即最后一个孩子之后。

② 插入之后如果堆的性质遭到破坏,就将新插入的节点顺着其的父亲往上调整到合适位置。

直到调到符合堆的性质为止。

根据堆的性质,如果不满足大堆和小堆,出现子大于父或父大于子的情况,

为了保证插入之后堆还是堆,我们就需要进行自下往上的调整。

堆插入数据对其他节点没有影响,只是可能会影响从它到根节点路径上节点的关系。

比如下面的情况:新插入的为 60,子大于父(60 > 56 ),这时就需要交换。

先把父亲赋值给孩子,再把孩子赋值给父亲,再让父亲往上走,判断是否比父亲大,如果大就再进行交换。 为了搞定这些情况,我们就需要写一个 "向上调整" 的算法(最坏调到根停止):

3.1大堆向上调整(BigAdjustUp)

void BigAdjustUp(int* arr, int child) //大根堆上调
{
    assert(arr);
    // 首先根据公式计算算出父亲的下标
    int parent = (child - 1) / 2;
    // 最坏情况:调到根,child=parent 当child为根节点时结束(根节点永远是0)
    while (child > 0) //不能写parent >= 0
    {                 //为什么呢?最后一次往上走时,
                     //child = parent;  此时child = 0
                     //parent = (child - 1) / 2;
                     //(0 - 1) / 2 = 0  这么一来parent仍然会是0
                     //导致parent根本就不会小于0
        if (arr[child] > arr[parent]) // 如果孩子大于父亲(不符合堆的性质)
        {  
            // 交换他们的值
            Swap(&arr[child], &arr[parent]);
            // 往上走
            child = parent;
            parent = (child - 1) / 2;
        }
        else// 如果孩子小于父亲(符合堆的性质)
        {  
            break;// 跳出循环
        }
    }
}

3.2小堆向上调整(SmallAdjustUp)

如果我们想改成小堆向上调整呢?

只需要改变一下大于小于判断条件即可:

void SmallAdjustUp(int* arr, int child) //小堆上调
{
    assert(arr);
    // 首先根据公式计算算出父亲的下标
    int parent = (child - 1) / 2;
    // 最坏情况:调到根,child=parent 当child为根节点时结束(根节点永远是0)
    while (child > 0)
    {
        if (arr[child] < arr[parent])  // 如果孩子大于父亲(不符合堆的性质)
        { 
            Swap(&arr[child], &arr[parent]);
            // 往上走
            child = parent;
            parent = (child - 1) / 2;
        }
        else  // 如果孩子小于父亲(符合堆的性质)
        {  
            break;
        }
    }
}

4.堆的删除(HeapPop)

因为在讲堆的插入时我们用大堆演示的,我们这里先用小堆来演示。

删除的核心思路:删除堆,删除的是堆顶的数据。就是删除这个树的根。

void HeapPop(HP* php)
 {
    assert(php);
    assert(!HeapIsEmpty(php));
    // 删除数据
    Swap(&php->array[0], &php->array[php->size - 1]);
    php->size--;
    // 写到这里写一个向下调整 传入目标数组,,数组的大小,调整位置的起始位置
    SmalljustDown(php->array, php->size, 0);
}

将堆顶的数据跟最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

① 将堆顶元素与堆中最后一个元素进行交换。

② 删除堆中的最后一个元素。

③ 将堆顶元素向下调整到满足堆特性为止。

向下调整,把它调整成堆,跟左右孩子中小的那个交换。

结束条件(有一个成立即可):

① 父<=小的孩子,则停止。

② 调整到叶子(因为叶子特征为没有孩子,左孩子下标超出数组范围,就不存在了)。

4.1小堆向下调整(SmallAdjustDown)

//小根堆下调 左右子树为小根堆,根节点不满足时使用  左右子树不满足就对其进行下调
void SmallAdjustDown(int* arr, int n, int parent) 
{
    int child = parent * 2 + 1; // 默认为左孩子
    while (child < n) // 叶子内
    { 
        // 选出左右孩子中小的那一个
        if (child + 1 < n && arr[child + 1] < arr[child]) //左孩子+1为右孩子
        {    //如果 child + 1 比 n 大,就说明没有右孩子,默认是对的
            child++;//左孩子+1更新为右孩子
        }
        if (arr[child] < arr[parent])// 如果孩子小于父亲(不符合小堆的性质)
        { 
            // 交换它们的值
            Swap(&arr[child], &arr[parent]);
            // 往下走
            parent = child;
            child = parent * 2 + 1;
        }
        else // 如果孩子大于父亲(符合小堆的性质)
        { 
            break;
        }
    }
}

如果我们想改成大堆呢?

也只需要改变一下大于小于判断条件即可:

① 选出左右孩子大的那一个。

② 小堆是所有父亲都小于孩子,大堆则是所有父亲都要大于孩子,我们来改一下:

4.2大堆向下调整(BigAdjustDown)

void BigAdjustDown(int* arr, int n, int parent) //大堆下调
{
    int child = parent * 2 + 1; // 默认为左孩子
    while (child < n) { // 叶子内
        // 选出左右孩子中大的那一个
        if (child + 1 < n && arr[child + 1] > arr[child]) {
            child++;
        }
        if (arr[child] > arr[parent]) { // 如果孩子大于父亲(不符合大堆的性质)
            // 交换它们的值
            Swap(&arr[child], &arr[parent]);
            // 往下走
            parent = child;
            child = parent * 2 + 1;
        }
        else  // 如果孩子小于父亲(符合大堆的性质)
        {
            break;
        }
    }
}

5.完整Heap.c

#include "Heap.h"

void HeapInit(Heap* php)//初始化堆
{
    assert(php);
    php->array = NULL;
    php->size = php->capacity = 0;
}

void HeapDestroy(Heap* php) //堆的销毁
{
    assert(php);
    free(php->array);
    php->capacity = php->size = 0;
}

void HeapPrint(Heap* php) //堆的打印
{
    for (int i = 0; i < php->size; i++) 
    {
        printf("%d ", php->array[i]);
    }
    printf("\n");
}

bool HeapEmpty(Heap* php)//判断堆是否为空
{
    assert(php);

    return php->size == 0; // 如果为size为0则表示堆为空
}


HPDataType HeapTop(Heap* php) //返回堆顶数据
{
    assert(php);
    assert(!HeapIsEmpty(php));

    return php->array[0];
}

int HeapSize(Heap* php) //统计堆的个数
{
    assert(php);

    return php->size;
}

void HeapCheckCapacity(Heap* php) //检查容量  写过很多次了
{
    if (php->size == php->capacity) 
    {
        int newCapacity = php->capacity == 0 ? 4 : (php->capacity * 2);
  HPDataType* tmpArray = (HPDataType*)realloc(php->array, sizeof(HPDataType) * newCapacity); 
        if (tmpArray == NULL)
        {  //检查realloc
            printf("realloc failed!\n");
            exit(-1);
        }
        //更新他们的大小
        php->array = tmpArray;
        php->capacity = newCapacity;
    }
}

void Swap(HPDataType* px, HPDataType* py) //交换
{
    HPDataType tmp = *px;
    *px = *py;
    *py = tmp;
}

void BigAdjustUp(int* arr, int child) //大根堆上调
{
    assert(arr);
    // 首先根据公式计算算出父亲的下标
    int parent = (child - 1) / 2;
    // 最坏情况:调到根,child=parent 当child为根节点时结束(根节点永远是0)
    while (child > 0) //不能写parent >= 0
    {                 //为什么呢?最后一次往上走时,
                     //child = parent;  此时child = 0
                     //parent = (child - 1) / 2;
                     //(0 - 1) / 2 = 0  这么一来parent仍然会是0
                     //导致parent根本就不会小于0
        if (arr[child] > arr[parent]) // 如果孩子大于父亲(不符合堆的性质)
        {  
            // 交换他们的值
            Swap(&arr[child], &arr[parent]);
            // 往上走
            child = parent;
            parent = (child - 1) / 2;
        }
        else// 如果孩子小于父亲(符合堆的性质)
        {  
            break;// 跳出循环
        }
    }
}

void SmallAdjustUp(int* arr, int child) //小堆上调
{
    assert(arr);
    // 首先根据公式计算算出父亲的下标
    int parent = (child - 1) / 2;
    // 最坏情况:调到根,child=parent 当child为根节点时结束(根节点永远是0)
    while (child > 0)
    {
        if (arr[child] < arr[parent])  // 如果孩子大于父亲(不符合堆的性质)
        { 
            Swap(&arr[child], &arr[parent]);
            // 往上走
            child = parent;
            parent = (child - 1) / 2;
        }
        else  // 如果孩子小于父亲(符合堆的性质)
        {  
            break;
        }
    }
}

void HeapPush(Heap* php, HPDataType x) 
{
    assert(php);
    // 检查是否需要扩容
    HeapCheckCapacity(php);
    // 插入数据
    php->array[php->size] = x;
    php->size++;
    // 向上调整 [目标数组,调整位置的起始位置(刚插入的数据)]
    BigAdjustUp(php->array, php->size - 1);
}


//小根堆下调  左右子树为小根堆,根节点不满足时使用  左右子树不满足就对其进行下调
void SmallAdjustDown(int* arr, int n, int parent) 
{
    int child = parent * 2 + 1; // 默认为左孩子
    while (child < n) // 叶子内
    { 
        // 选出左右孩子中小的那一个
        if (child + 1 < n && arr[child + 1] < arr[child]) //左孩子+1为右孩子
        {    //如果 child + 1 比 n 大,就说明没有右孩子,默认是对的
            child++;//左孩子+1更新为右孩子
        }
        if (arr[child] < arr[parent])// 如果孩子小于父亲(不符合小堆的性质)
        { 
            // 交换它们的值
            Swap(&arr[child], &arr[parent]);
            // 往下走
            parent = child;
            child = parent * 2 + 1;
        }
        else // 如果孩子大于父亲(符合小堆的性质)
        { 
            break;
        }
    }
}

void BigAdjustDown(int* arr, int n, int parent) //大根堆下调
{
    int child = parent * 2 + 1; // 默认为左孩子
    while (child < n) // 叶子内
    { 
        // 选出左右孩子中大的那一个
        if (child + 1 < n && arr[child + 1] > arr[child]) 
        {
            child++;
        }
        if (arr[child] > arr[parent]) { // 如果孩子大于父亲(不符合大堆的性质)
            // 交换它们的值
            Swap(&arr[child], &arr[parent]);
            // 往下走
            parent = child;
            child = parent * 2 + 1;
        }
        else  // 如果孩子小于父亲(符合大堆的性质)
        {
            break;
        }
    }
}

void HeapPop(Heap* php) 
{
    assert(php);
    assert(!HeapIsEmpty(php));
    // 删除数据
    Swap(&php->array[0], &php->array[php->size - 1]);
    php->size--;
    // 向下调整 [目标数组,数组的大小,调整位置的起始位置]
    //SmallAdjustDown(php->array, php->size, 0);
    BigAdjustDown(php->array, php->size, 0);
}

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

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

相关文章

计算机网络概述 第一部分

前言 为了准备期末考试&#xff0c;同时也是为了之后复习方便&#xff0c;特对计算机网络的知识进行了整理。本篇内容大部分是来源于我们老师上课的ppt。而我根据自己的理解&#xff0c;将老师的PPT整理成博文的形式以便大家复习查阅&#xff0c;同时对于一些不是很清楚的地方…

centos7搭建svn配置

基本概述 Apache Subversion&#xff08;简称SVN&#xff0c;svn&#xff09;&#xff0c;一个开放源代码的版本控制系统&#xff0c;相较于RCS、CVS&#xff0c;它采用了分支管理系统&#xff0c;它的设计目标就是取代CVS。互联网上很多版本控制服务已从CVS转移到Subversion。…

【Vue3源码】第五章 ref的原理 实现ref

【Vue3源码】第五章 ref的原理 实现ref 上一章节我们实现了reactive 和 readonly 嵌套对象转换功能&#xff0c;以及shallowReadonly 和isProxy几个简单的API。 这一章我们开始实现 ref 及其它配套的isRef、unRef 和 proxyRefs 1、实现ref 接受一个内部值&#xff0c;返回一…

3款实用又强的软件,值得收藏,不妨试试

1、白描 白描是一款高效准确的OCR文字识别、翻译与文件扫描软件&#xff0c;文字识别、表格识别转Excel、识别后翻译、文件扫描等功能&#xff0c;都非常方便&#xff0c;免费使用无任何广告。白描可以自动识别文档边界&#xff0c;生成清晰的扫描件&#xff0c;高效批量处理文…

Java8 Stream流Collectors.toMap当key重复时报异常(IllegalStateException)

一、问题 在使用Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)&#xff08;两个参数的&#xff09;时&#xff0c;如果 key 有重复&#xff0c;则会报异常&#xff08;IllegalStateException…

工赋开发者社区 | (案例)中译语通:差别化纺纱柔性智慧工厂

中译语通&#xff1a;差别化纺纱柔性智慧工厂01应用成效中译语通科技股份有限公司是一家大数据和人工智能高科技公司。在机器翻译、跨语言大数据、产业链科技、科研数据分析、数字城市和工业互联网等领域拥有自主研发的先进系统平台&#xff0c;能够为全球企业级用户提供全方位…

[oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星

编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物而是 生活中常见的 家用电器 ibm 蓝色巨人脚步沉重 dec 小型机不断蚕食低端市场甚至组成网络干掉大型机…

【仔细理解】计算机视觉基础1——特征提取之Harris角点

Harris角点是图像特征提取中最基本的方法&#xff0c;本篇内容将详细分析Harris角点的定义、计算方法、特点。 一、Harris角点定义 在图像中&#xff0c;若以正方形的小像素窗口为基本单位&#xff0c;按照上图可以将它们划分三种类型如下&#xff1a; 平坦区域&#xff1a;在任…

【C++】Visual Studio C++使用配置Json库文件(老爷式教学)

在visual studio中使用C调用Json的三方库有很多种办法&#xff0c;这里简述一种比较方便的方法。绝对好用&#xff0c;不好用你砍我。 文章目录在visual studio中使用C调用Json的三方库有很多种办法&#xff0c;这里简述一种比较方便的方法。绝对好用&#xff0c;不好用你砍我。…

百度前端一面高频react面试题指南

React 高阶组件、Render props、hooks 有什么区别&#xff0c;为什么要不断迭代 这三者是目前react解决代码复用的主要方式&#xff1a; 高阶组件&#xff08;HOC&#xff09;是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分&#xff0c;它是一…

Python 零基础入门必看,这些知识点你都掌握了吗?

导读 Python 作为当今最受欢迎的编程语言之一&#xff0c;几乎各个领域都会涉及到&#xff0c;所以学习 Python 自然刻不容缓&#xff01;作为一个没有接触过 Python 的小白&#xff0c;一开始要想的不是如何使用以及各种高深莫测的玩法&#xff0c;从最基础的了解以及构建环境…

node笔记

一、FS模块 fs.readFile查询文件 fs.writeFile修改文件 __dirname表示当前文件所处的目录&#xff08;双下划线&#xff09; 二、path模块 什么是path 路径模块 path模块是Node.,js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性&#xff0c;用来满足用户…

Spring 之bean的生命周期

文章目录IOCBean的生命周期运行结果实例演示实体类实例化前后置代码初始化的前后置代码application.xml总结今天我们来聊一下Spring Bean的生命周期&#xff0c;这是一个非常重要的问题&#xff0c;Spring Bean的生命周期也是比较复杂的。IOC IOC&#xff0c;控制反转概念需要…

华为OD机试题,用 Java 解【合规数组】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

JAVA基础常见面试题

1.Java接口和抽象类的区别&#xff1f; 接口 接口中不能定义构造器 方法全部都是抽象方法&#xff0c;JDK8提供方法默认实现 接口中的成员都是public的 接口中定义的成员变量实际上都是常量 一个类可以实现多个接口 抽象类 抽象类中可以定义构造器 可以有抽象方法和具体…

字节序

字节序 字节序&#xff1a;字节在内存中存储的顺序。 小端字节序&#xff1a;数据的高位字节存储在内存的高位地址&#xff0c;低位字节存储在内存的低位地址 大端字节序&#xff1a;数据的低位字节存储在内存的高位地址&#xff0c;高位字节存储在内存的低位地址 bit ( 比特…

设计模式第八讲:观察者模式和中介者模式详解

一. 观察者模式1. 背景在现实世界中&#xff0c;许多对象并不是独立存在的&#xff0c;其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如&#xff0c;某种商品的物价上涨时会导致部分商家高兴&#xff0c;而消费者伤心&#xff1b;还有&#x…

内网部署api接口文档服务器端口如何让外网访问?

计算机业内人士对于swagger并不陌生&#xff0c; 不少人选择用swagger做为API接口文档管理。Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法&#x…

Apache Hadoop生态部署-3台设置的免密登录,xsync分发脚本,jpsall脚本

目录 查看服务架构图-服务分布、版本信息 集群服务器间的免密登录 jpsall集群jps查看脚本 xsync集群分发脚本 查看服务架构图-服务分布、版本信息 系统环境&#xff1a;centos7 Java环境&#xff1a;Java8 集群服务器间的免密登录 作用&#xff1a;这里配置的是root用户…

联想昭阳E5-ITL电脑开机后绿屏怎么U盘重装系统?

联想昭阳E5-ITL电脑开机后绿屏怎么U盘重装系统&#xff1f;有用户电脑正常开机之后&#xff0c;出现了屏幕变成绿屏&#xff0c;无法进行操作的情况。这个问题是系统出现了问题&#xff0c;那么如何去进行问题的解决呢&#xff1f;接下来我们一起来分享看看如何使用U盘重装电脑…