数据结构——二叉树_堆

news2024/12/25 9:25:14

目录

一、堆的概念

二、堆的结构

三、性质

(1)堆的性质

(2)二叉树的性质 

四、堆的实现

(1)头文件——Heap.h

(2)源文件——Heap.c

1.堆的初始化

2.堆的销毁

 3.向上调整算法

4.堆的插入

4.判断堆是否为空

5. 向下调整算法

6.堆的删除

 7.取堆顶数据

8.求有效数据个数

 五、两种调整算法

(1)向上调整算法

1.作用

2.复杂度

(2)向下调整算法

1.前提

2.作用

3.复杂度

六、堆的应用

(1)堆排序

1.版本一

2.版本二

3.复杂度

(2)TOP-K问题

1.描述

2.思路

3.实现

4.时间复杂度

七、写在最后


一、堆的概念

堆一般使用顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备其他的特性。

如果有⼀个关键码的集合K  =  {k0 , k1 , k2 , ...,kn−1 } ,把它的所有元素按完全⼆叉树的顺序存储方式存储,在⼀个⼀维数组中,并满足:K  =  {k0 , k1 , k2 , ...,kn−1 }, i = 0、1、2... ,则称为小堆(或大堆)。

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆

二、堆的结构

三、性质

(1)堆的性质

1.堆中某个结点的值总是不大于或不小于其父结点的值;

2.堆总是一棵完全二叉树。

(2)二叉树的性质 

对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从 0 开始编号,则对于序号为 i 的结点有:

1. 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ;当i=0 , i 为根结点编号,无双亲结点 ;

2. 若 2i+1< n,左孩子序号: 2i+1 ;若 2i+1>=n 则无左孩子;

3. 若 2i+2< n,右孩子序号: 2i+2 ;若2i+2>=n 则无右孩子。

四、堆的实现

(1)头文件——Heap.h

typedef int HPDatatype;
typedef struct Heap
{
    HPDatatype* arr;
    int capacity;
    int size;
}Heap;

//堆的初始化
void HeapInit(Heap* php);

//堆的销毁
void HeapDestroy(Heap* php);

//向上调整算法
void AdjustUp(HPDatatype* arr,int child);

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

//判空
bool HeapEmpty(Heap* php);

//向下调整算法
void AdjustDown(HPDatatype* arr, int n, int parent);

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

//取堆顶数据
HPDatatype HeapTop(Heap* php);

//求有效数据个数
int HeapSize(Heap* php);

(2)源文件——Heap.c

1.堆的初始化

void HeapInit(Heap* php)
{
    assert(php);
    php->arr = NULL;
    php->capacity = php->size = 0;
}

2.堆的销毁

void HeapDestroy(Heap* php)
{
    assert(php);
    if(php->arr != NULL)
    {
        free(php->arr);
    }
    php->arr = NULL;
    php->capacity = php->size = 0;
}

 3.向上调整算法

void swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void AdjustUp(Heap* arr, int child)
{
    //父结点
    int parent = (child - 1) / 2;
    while(child > 0)
    {
        //如果child<parent,则交换位置
        if(arr[child] < arr[parent])
        {
            swap(&arr[child], &arr[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else 
        {
            break;
        }
    }
}

4.堆的插入

void HeapPush(Heap* php, HPDatatype x)
{
    assert(php);
    //判断空间是否充足
    if(php->capacity == php->size)
    {
        int newCapacity = php->capacity == 0 ? 4 : 2*php->capacity;
        HPDatatype* tmp = (HPDatatype*)realloc(php->arr, sizeof(HPDatatype)*newCapacity);
        if(tmp == NULL)
        {
            perror("realloc fail!");
            exit(1);
        }
        php->arr = tmp;
        php->capacity = newCapacity;
    }
    php->arr[php->size] = x;
    AdjustUp(php->arr, php->size);
    php->size++;//这句要放在最后
    //因为如果放在倒数第二句,插入之后size实际上应该加1,此时的size对应的数据和初始时不一样了
   
}

4.判断堆是否为空

bool HeapEmpty(Heap* php)
{
    assert(php);
    return php->size == 0;
}

5. 向下调整算法

void AdjustDown(HPDatatype* arr, int n, int parent)
{
    assert(php);
    //左孩子
    int child = 2 * parent + 1;
    while(child < n)
    {
        //找左右孩子中最小的
        if(child < n && arr[child] > arr[child + 1])
        {
            child ++;
        }
        if(arr[child] < arr[parent])
        {
            swap(&arr[child], &arr[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

6.堆的删除

//堆的删除
void HeapPop(Heap* php)
{
    assert(php);
    assert(!HeapEmpty(php));
    //先交换第一个与最后一个的位置
    swap(&php->arr[0], &php->arr[php->size - 1]);
    php->size--;
    将新的第一个数据进行向下调整
    AdjustDown(php->arr, 0, php->size);
}

 7.取堆顶数据

HPDatatype HeapTop(Heap* php)
{
    assert(php && php->size);
    return php->arr[0];
}

8.求有效数据个数

int HeapSize(Heap* php)
{
    assert(php);
    return php->size;
}

 五、两种调整算法

(1)向上调整算法

1.作用

堆的插入:元素插入到堆的末尾后,即为最后一个孩子。

向上调整算法:插入之后如果堆的性质遭到破坏,将新结点顺着双亲结点往上调整到合适位置。

2.复杂度

向上调整算法建堆的时间复杂度为:O(n* log2  n)。


由于堆是完全二叉树,而满二叉树是特殊的完全二叉树,因此我们用满二叉树来简化证明。

分析:

第一层:2^0个结点,需要向上移动0层;

第二层:2^1个结点,需要向上移动1层;

第三层:2^2个结点,需要向上移动2层;

……

第h层:2^(h-1)个结点,需要向上移动h-1层;

因此:需要移动的结点的总的移动步数:每层结点的个数*向上调整的次数。


(2)向下调整算法

1.前提

左右子树必须是一个堆才能调整。

2.作用

删除:是删除堆顶的数据,将堆顶的数据与最后一个数据互换,然后删除最后一个数据,进行向下调整算法。

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

3.复杂度

向下调整算法建堆的时间复杂度为:O(n)。 


分析:

第一层:2^0个结点,需要向下移动h-1层;

第二层:2^1个结点,需要向下移动h-2层;

第三层:2^2个结点,需要向下移动h-3层;

……

第h-1层:2^(h-2)个结点,需要向下移动1层;

第h层:2^(h-1)个结点,需要向上移动0层;

因此:需要移动的结点的总的移动步数:每层结点的个数*向下调整的次数。


六、堆的应用

(1)堆排序

1.版本一

基于已有的数组建堆、取堆顶元素完成排序。

void HeapSort(int* arr, int n)
{
    Heap hp;
    //将已有的数组中的元素插入到堆中(同时进行了排序)
    for(int i = 0 ; i < n ; i ++)
    {
        HeapPush(&hp, arr[i]);
    }

    int i = 0;
    //将堆中的数据更新到数组中
    while(!HeapEmpty(&hp))
    {
        arr[i++] = HeapTop(&hp);
        HeapPop(&hp);
    }
    //销毁
    HeapDestroy(&hp);
}

2.版本二

 数组建堆,首尾交换,交换后的堆尾数据从堆中删除,将堆顶数据向下调整选出次大的数据。

前提:必须有现成的数据结构堆。

//升序,建大堆
//降序,建小堆

void HeapSort(int* arr, int n)
{
    //数组建堆
    //从最后一个父结点开始调整
    for(int i = (n - 1 - 1) / 2; i >= 0 ; i--)
    {
        AdjustDown(arr, n, i);
    }
    int end = n - 1;
    while(end > 0)
    {
        swap(&arr[0], &arr[end]);
        AdjustDown(arr, end, 0);
        end--;
    }
}

3.复杂度

 

堆排序的时间复杂度为:O(n*logn)。


分析:

第一层:2^0个结点,交换到根结点后,需要向下移动0层; 

第二层:2^1个结点,交换到根结点后,需要向下移动1层; 

第三层:2^2个结点,交换到根结点后,需要向下移动2层;

……

第h层:2^(h-1)个结点,交换到根结点后,需要向下移动h-1层。

堆排序第⼆个循环中的向下调整与建堆中的向上调整算法时间复杂度计算⼀致。因此,堆排序的时间复杂度为O(n + n ∗ log n) ,即O(n log n)。


(2)TOP-K问题

1.描述

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,⼀般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

2.思路

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 (可能数据都不能⼀下子全部加载到内存中)。

最佳的方式就是用来解决:

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

前k个最大的元素,则建小堆;前k个最小的元素,则建大堆。

(2)用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

3.实现

void CreateNDate()
{
     // 造数据
     int n = 100000;
     srand(time(0));
     const char* file = "data.txt";
     FILE* fin = fopen(file, "w");

     if (fin == NULL)
     {
         perror("fopen error");
         return;
     }

     for (int i = 0; i < n; ++i)
     {
         int x = (rand()+i) % 1000000;
         fprintf(fin, "%d\n", x);
     }

     fclose(fin);
}

void topk()
{
     printf("请输⼊k:>");
     int k = 0;
     scanf("%d", &k);
     const char* file = "data.txt";
     FILE* fout = fopen(file, "r");

     if (fout == NULL)
     {
         perror("fopen error");
         return;
     }

     int val = 0;
     int* minheap = (int*)malloc(sizeof(int) * k);
     if (minheap == NULL)
     {
         perror("malloc error");
         return;
     }

     for (int i = 0; i < k; i++)
     {
         fscanf(fout, "%d", &minheap[i]);
     }

     // 建k个数据的⼩堆
     for (int i = (k - 1 - 1) / 2; i >= 0; i--)
     {
         AdjustDown(minheap, k, i);
     }

     int x = 0;
     while (fscanf(fout, "%d", &x) != EOF)
     {
         // 读取剩余数据,⽐堆顶的值⼤,就替换他进堆
         if (x > minheap[0])
         {
             minheap[0] = x;
             AdjustDown(minheap, k, 0);
         }
     }

     for (int i = 0; i < k; i++)
     {
         printf("%d ", minheap[i]);
     }

     fclose(fout);
}

4.时间复杂度

O(n) = k + (n - k)log2  k。 

七、写在最后

二叉树不仅能够顺序实现,而且可以通过链式结构实现。

敬请期待“链式结构实现二叉树”~

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

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

相关文章

​人工智能薪酬排行榜:2024年哪些岗位最吃香?

近日&#xff0c;智联招聘发布了2024年二季度《中国企业招聘薪酬报告》。报告显示&#xff0c;人工智能行业平均薪酬为13594元/月&#xff0c;位居行业榜首。 同时&#xff0c;在“2024年二季度企业招聘薪酬TOP20职业”中&#xff0c;人工智能工程师以平均月薪22003元排名第一。…

口碑最好的麦克风品牌有哪些,无线领夹麦克风十大品牌推荐

​在内容丰富的自媒体世界里&#xff0c;每一个细节都可能是吸引观众的关键&#xff0c;尤其是音质。面对这么多的无线领夹麦克风&#xff0c;到底哪款更值得入手呢&#xff1f;我试用并挑选了几款性价比高的无线领夹麦克风&#xff0c;它们能够为各类视频制作提供令人印象深刻…

7.5寸电子日历

7.5英寸无线智能纸质显示屏幕办公名牌电子墨水数字显示ESL标签仓库使用

ONVIF 摄像头视频流获取 - 步骤与Python例程

1.基本流程 加入组播udp接口&#xff0c;查询子网内在线的ONVIF摄像头的设备地址&#xff1a; 设备地址形如&#xff1a;http://192.168.0.6/onvif/device_service 这一步&#xff0c;参看上一篇发文&#xff1a;[ONVIF系列 - 01] 简介 - 设备发现 - 相关工具-CSDN博客查询med…

叉车车队管理系统怎么选,满足监管要求!

在现代物流与仓储管理中&#xff0c;选择一个高效且可靠的叉车车队管理系统至关重要。一个高性能的管理系统不仅提升作业效率&#xff0c;还能保障作业安全。企业管理者在采购叉车车队管理系统前需要考虑几个关键因素&#xff1a; 1、提升管理效率&#xff1a;‌确保系统能够实…

机器学习赋能的智能光子学器件系统研究与应用

在人工智能与光子学设计融合的背景下&#xff0c;科研的边界持续扩展&#xff0c;创新成果不断涌现。从理论模型的整合到光学现象的复杂模拟&#xff0c;从数据驱动的探索到光场的智能分析&#xff0c;机器学习正以前所未有的动力推动光子学领域的革新。据调查&#xff0c;目前…

【计算机毕业设计】707高校宿舍管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

电脑数据有没有办法恢复?适用于 Windows电脑的的 14 款Windows 恢复工具

如果您在笔记本电脑上安装了 Windows 10&#xff0c;却发现文件均未复制到新系统&#xff0c;该怎么办&#xff1f;您可以在线搜索 Windows 10 恢复工具下载&#xff0c;结果会有很多。 确实&#xff0c;优秀的 Windows 恢复工具能够有效地恢复已删除的文件。问题是市场上有太…

为什么有时候银行贷款审核会查大数据信用?

在申请银行贷款时&#xff0c;不少人会疑惑为何银行会深入审查申请人的大数据信用信息。这背后&#xff0c;其实是银行风险控制与精准决策的体现。 首先&#xff0c;大数据信用信用能全面反映申请人的信用状况 它不仅仅局限于传统的征信报告&#xff0c;还涵盖了消费行为、社交…

无线麦克风可以唱歌吗?领夹麦克风十大品牌,麦克风什么牌子好

追求卓越音质与无拘无束表现力的今天&#xff0c;无线领夹麦克风已成为众多创作者、主播、演讲者以及表演艺术家的得力助手。它们不仅提供了更大的自由度和灵活性&#xff0c;还确保了声音的清晰度和传输的稳定性&#xff0c;有专业的装备才能生成高质量的视频作品&#xff0c;…

WPF中调用UWP API

最近在github上看到一个音乐播放器项目&#xff0c;dopamine(项目地址&#xff1a;GitHub - digimezzo/dopamine-windows: Audio player which tries to make organizing and listening to music as simple and pretty as possible.) 在编译时&#xff0c;提示有一个库找不到 …

免费泛域名证书申请(永久免费,不限申请次数)

免费泛域名证书&#xff08;也称为通配符SSL证书&#xff09;的申请过程通常涉及以下几个步骤。以下是一个详细的指南&#xff0c;帮助您了解如何申请免费泛域名证书&#xff1a; 一、选择合适的证书提供商 目前&#xff0c;市场上提供免费泛域名证书的服务商主要有Lets Encr…

从零开始学习网络安全渗透测试之基础入门篇——(六)网络抓包全局协议封包监听网卡模式科来wiresharkPC应用

网络抓包技术是一种用于捕获和分析网络数据包的技术手段。它能够帮助我们深入了解网络中的数据传输情况&#xff0c;对于网络故障排查、性能优化、安全监测等方面都具有重要意义。 网络抓包的工作原理通常是通过将网络接口设置为混杂模式&#xff0c;使其能够接收流经该接口的…

openEuler使用mariadb

1.安装mariadb 两者互为依赖 解决&#xff1a; 2.开机自启 3.进入数据库 数据库默认没有密码&#xff0c;直接回车进入 4.安全初始化 5.修改密码 远程用户无法登录 6.创建远程登录用户 7.创建数据库 8.赋予远程用户bbs数据库的操作权限 查看用户权限 9.使用普通账号&…

[Docker][Docker NetWork][下]详细讲解

目录 1.网络管理命令1.docker network creatre2.docker network inspect3.docker network connect4.docker network disconnect5.docker network prune6.docker network rm7.docker network ls 2.docker bridge 详解0.基本概念1.默认 bridge2.自定义 bridge3.DNS解析4.端口暴露…

45 标准库 collections 中与字典有关的类

Python 标准库中提供了很多扩展功能&#xff0c;大幅度提高了开发效率。主要介绍 collections 中 OrderedDict 类、defaultdict 类和 Counter类。 1 OrderedDict 类 Python 内置字典 dict 是无序的&#xff0c;如果需要一个可以记住元素插入顺序的字典&#xff0c;则可以使用…

大厂的风控引擎架构设计

1 架构师能力思维模型 全局思维抽象思维 2 新需求的思考路径 需求是否合理&#xff0c;是否能解决问题&#xff1f; 能划分多少个子系统&#xff1f; 每个子系统能划分多少个模块&#xff1f;这个系统需要可靠性吗&#xff0c;需要扩展能力吗&#xff1f;成本需要控制吗&a…

路径规划——广度优先搜索与深度优先搜索

路径规划——广度优先搜索与深度优先搜索 https://www.hello-algo.com/chapter_graph/graph_traversal/ 1.广度优先搜索 Breath-First-Search 在图论中也称为广度优先遍历&#xff0c;类似于树的层序遍历。 算法原理 从起始节点出发&#xff0c;首先访问它的邻近节点&…

openEuler中安装数据库

目录 一.安装数据库 二.出现报错解决方法 1.根据报错查看冲突软件包 2.忽略软件依赖性解决 3.再次查看是否删掉冲突软件 三.再次执行安装数据库命令 四.启动数据库可直接输入mysql进入数据库&#xff0c;此时不安全 五.安全初始化 1.是否有root密码&#xff0c;没有直…