数据结构:从零开始掌握二叉树(2)二叉树的顺序存储-堆 - 教程

news/2025/11/5 13:04:55/文章来源:https://www.cnblogs.com/slgkaifa/p/19193326

数据结构:从零开始掌握二叉树(2)二叉树的顺序存储-堆 - 教程

目录

一. 二叉树的顺序结构

二.堆的概念及结构

个人理解与心得:

三. 堆的实现

1.向上调整算法和向下调整算法

1.1 向上调整算法

1.2 向下调整算法

2.向下调整建堆

2.1 建堆

2.2 时间复杂度

3.堆的应用

3.1 堆排序

3.2 top-k问题

四. 堆的其他操作及全部代码


一. 二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

二.堆的概念及结构

如果有一个关键码的集合K = ,把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中并满足:

其中i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。简单来说,大根堆的每个结点都不小于它的孩子结点,小根堆的每个结点都不大于它的孩子结点。

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树

个人理解与心得:

以该大根堆为例,除根结点外,每个结点 i 的父节点下标都是该结点下标 (i-1)/2,无论该结点是左孩子还是右孩子,每个结点 i 的左孩子结点都是该结点下标 i*2+1,右孩子节点都是该结点下标 i*2+1+1,即左孩子的下标加一,因为这些数据都是存储在一维数组中的,下标是连续的。

以其中的56为例,它在一维数组中的下标是1,它在堆中的父节点是70,70的下标是0,即(1-1)/2=0,

它在堆中的左孩子结点是25, 25的下标是3,即(1*2)+1=3,右孩子是15, 15的下标是4,即1*2+1+1=4,写好堆的第一步就是理解每个结点与其父节点和孩子结点的下标关系!

三. 堆的实现

1.向上调整算法和向下调整算法

在理解了堆结点之间的下标关系后,我们来看堆实现的两种必备算法

1.1 向上调整算法

假设我们已经拥有了一个大根堆,怎么往堆中插入元素而保持堆的性质呢?用向上调整算法,只需要将新插入的结点作为孩子结点,与其父节点比较,如果该结点大于其父节点,交换两个结点位置,再继续将与父节点交换位置后的新结点与它的新父节点比较大小,如果大于新父节点,就继续交换,以此类推,一直到该结点不大于其父节点,或者该结点成为新的根结点。

typedef int HPDatatype;
typedef struct Heap
{HPDatatype* data;//存储数据int size;        //数据个数int capacity;    //堆的容量
}Heap;
//向上调整算法
void adjustup(HPDatatype* a, int n)
{int child = n - 1;           //child为新插入的元素下标,因为n是数据个数,比下标大1int parent = (child - 1) / 2;//parent为新元素的父节点while (child > 0)
/*这里用parent>0能跳出循环是因为下边的if条件不满足
通过break结束了循环,而parent始终是不小于0的,能结束属于巧合*/{if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
//堆的插入,向上调整
void Heappush(Heap* php, HPDatatype x)
{assert(php);                    //暴力检查if (php->size == php->capacity)//扩容{HPDatatype* temp = (HPDatatype*)realloc(php->data,sizeof(HPDatatype) * php->capacity * 2);if (temp == NULL){perror("malloc fail");return;}php->capacity *= 2;php->data = temp;     // 异地扩容,需要重置data指向}php->data[php->size] = x; //将新元素插入php->size++;adjustup(&php->data, php->size);//向上调整}
}
1.2 向下调整算法

学会了插入,接下来看删除。堆的删除是删的根结点。怎么才能删除根结点而保存堆的性质呢?用向下调整算法,首先将根结点与size-1处(即尾结点)交换位置,然后size--即可删除该结点,之后将新的根结点和孩子结点中大的那个交换位置,到底(下标大于size)或者到不小于两个孩子结点为止。

typedef int HPDatatype;
typedef struct Heap
{HPDatatype* data;int size;int capacity;
}Heap;
void adjustdown(HPDatatype* a, int n)
{int parent = 0;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1])//这里要加上child+1 a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}
//堆的删除,用向下调整
void Heappop(Heap* php)
{assert(php);Swap(&php->data[php->size - 1], &php->data[0]);//先交换php->size--;                                   //交换完删除最后一个元素adjustdown(php, php->size);
}

2.向下调整建堆

2.1 建堆

向下调整算法建堆的前提是左右子树必须都是堆。下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

代码也非常简单:

void Heapcreat(HPDatatype* a, int n)//n是数组大小
{for (int i = (n-2)/2;i >=0;i--){adjustdown(a, i);}for (int i = 0;i < n;i++)//输出看看建堆是否成功{printf("%d ", a[i]);}
}
2.2 时间复杂度

3.堆的应用

3.1 堆排序

void Heapsort(HPDatatype* a, int n)
{for (int i = (n-2)/2;i >=0;i--){adjustdown(a, i);}for (int i = 0;i < n;i++){printf("%d ", a[i]);}printf("\n");int end = n;while (end > 0){Swap(&a[0], &a[end - 1]);adjustdown(a, end - 1);end--;}for (int i = 0;i < n;i++){printf("%d ", a[i]);}
}

3.2 top-k问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

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

  1.  用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 前k个最小的元素,则建大堆
  2.  用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

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

void PrintTopK(int* a, int n, int k)
{// 1. 建堆--用a中前k个元素建堆int i;for (i = (k - 2) / 2;i >= 0;i--){adjustdown(a, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,大于则替换for (i = k;i < n ;i++){if (a[i] > a[0]){Swap(&a[i], &a[0]);adjustdown(a, k, 0);}}for (i = 0;i < k;i++){printf("%d ", a[i]);}
}
void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);srand(time(0));for (size_t i = 0; i < n; ++i){a[i] = rand() % 1000000;}a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[531] = 1000000 + 3;a[5121] = 1000000 + 4;a[115] = 1000000 + 5;a[2335] = 1000000 + 6;a[9999] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}

四. 堆的其他操作及全部代码

头文件:

#pragma once
#include
#include
#include
#include
#include
typedef int HPDatatype;
typedef struct Heap
{HPDatatype* data;int size;int capacity;
}Heap;
void Heapinit(Heap* php);
//堆的销毁
void Heapdestory(Heap* php);
//堆的插入
void Heappush(Heap* php,HPDatatype x);
//堆的删除
void Heappop(Heap* php);
//取堆顶数据
HPDatatype Heaptop(Heap* php);
//堆的数据个数
int Heapsize(Heap* php);
//堆的判空
bool Heapempty(Heap* php);
//向下调整
void adjustdown(HPDatatype *a,int n,int m);
//向上调整
void adjustup(HPDatatype* a, int n);
//交换
void Swap(HPDatatype* x, HPDatatype* y);
//堆排序
void Heapsort(HPDatatype* a, int n);
//topk测试
void TestTopk();
//topk输出
void PrintTopK(int* a, int n, int k);

函数实现:

#include"HP.h"
void Swap(HPDatatype* x, HPDatatype* y)
{HPDatatype t = *x;*x = *y;*y = t;
}
void Heapinit(Heap* php)
{assert(php);php->data = (HPDatatype*)malloc(sizeof(HPDatatype) * 4);if (php->data == NULL){perror("malloc fail");return;}php->capacity = 4;php->size = 0;
}
//堆的销毁
void Heapdestory(Heap* php)
{assert(php);free(php->data);php->data = NULL;
}
//堆的插入,向上调整
void Heappush(Heap* php, HPDatatype x)
{assert(php);if (php->size == php->capacity){HPDatatype* temp = (HPDatatype*)realloc(php->data, sizeof(HPDatatype) * php->capacity * 2);if (temp == NULL){perror("malloc fail");return;}php->capacity *= 2;php->data = temp; // 异地扩容,需要重置data指向}php->data[php->size++] = x;HPDatatype child = php->size - 1;HPDatatype parent = (child - 1) / 2;while (child > 0)/*这里用parent>0能跳出循环是因为下边的if条件不满足通过break结束循环,而parent始终是不小于0的,属于巧合*/{if (php->data[child] > php->data[parent]){Swap(&php->data[child], &php->data[parent]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
//堆的删除,用向下调整
void Heappop(Heap* php)
{assert(php);Swap(&php->data[php->size - 1], &php->data[0]);php->size--;HPDatatype parent = 0;HPDatatype child = parent * 2 + 1;while (child < php->size){if (child + 1 < php->size && php->data[child] < php->data[child + 1]){child++;}if (php->data[child] > php->data[parent]){Swap(&php->data[child], &php->data[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}
//取堆顶数据
HPDatatype Heaptop(Heap* php)
{assert(php);return php->data[0];
}
//堆的数据个数
int Heapsize(Heap* php)
{assert(php);return php->size;
}
//堆的判空
bool Heapempty(Heap* php)
{assert(php);return php->size == 0;
}
void adjustup(HPDatatype* a, int n)
{int child = n - 1;int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
void adjustdown(HPDatatype* a, int n,int m)
{int parent = m;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}
void Heapsort(HPDatatype* a, int n)
{for (int i = (n-2)/2;i >=0;i--){adjustdown(a,n,i);}for (int i = 0;i < n;i++){printf("%d ", a[i]);}printf("\n");int end = n;while (end > 0){Swap(&a[0], &a[end - 1]);adjustdown(a, end - 1,0);end--;}for (int i = 0;i < n;i++){printf("%d ", a[i]);}
}
void PrintTopK(int* a, int n, int k)
{// 1. 建堆--用a中前k个元素建堆int i;for (i = (k - 2) / 2;i >= 0;i--){adjustdown(a, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换for (i = k;i < n ;i++){if (a[i] > a[0]){Swap(&a[i], &a[0]);adjustdown(a, k, 0);}}for (i = 0;i < k;i++){printf("%d ", a[i]);}
}
void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);srand(time(0));for (size_t i = 0; i < n; ++i){a[i] = rand() % 1000000;}a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[531] = 1000000 + 3;a[5121] = 1000000 + 4;a[115] = 1000000 + 5;a[2335] = 1000000 + 6;a[9999] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}

测试:

#include"HP.h"
int main()
{/*Heap hp;Heapinit(&hp);Heappush(&hp, 6);Heappush(&hp, 7);Heappush(&hp, 4);Heappush(&hp, 26);while (!Heapempty(&hp)){printf("%d ", Heaptop(&hp));Heappop(&hp);}Heapdestory(&hp);*///堆排序测试/*HPDatatype a[10] = { 6,8,7,9,2,4,3,5,1 };Heapsort(a, 10);*///topk测试//TestTopk();return 0;
}

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

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

相关文章

2025年牛大力养生酒选品指南:广东醉王侯,醉王侯牛大力酒业/牛大力养生酒加盟/广东牛大力养生酒加盟/醉王侯牛大力酒加盟/五星推荐的健康之选

随着居民健康意识提升、养生消费需求升级及传统养生文化复兴,牛大力养生酒凭借独特的草本养生价值与饮用体验,从养生饮品细分领域逐步走进大众视野,2025 年市场规模预计持续增长。但市场扩张也带来品牌品质、原料把…

Python的`__call__`方法:让对象变成“可调用函数”

Python的__call__方法:让对象变成“可调用函数” 在Python中,()是“调用符号”——我们用它调用函数(如func())、创建类实例(如MyClass())。但你可能不知道:普通对象也能通过__call__方法变成“可调用对象”,像…

【拾遗补漏】.NET 常见术语集

前言 .NET 常见术语集旨在为 .NET 初学者提供一份清晰的入门指引,也为经验丰富的 .NET 开发者提供一份便捷的参考,帮助大家更准确地表达、更高效地学习、更深入地探索 .NET 的强大功能(有遗漏的欢迎文末留言)。 应…

2025评价高的PFA管阀接头厂家供应商推荐榜:江盛达,国产力量崛起,精准匹配高端制造需求,最好的PFA管接头厂家推荐

PFA管作为半导体、生物医药、精细化工等高端制造领域的关键流体输送组件,其纯度、耐腐蚀性与稳定性直接影响生产系统的可靠性。随着国产替代进程加速,2025 年市场涌现出一批技术过硬、服务完善的优质企业。本文结合行…

2025正规的广东AI营销公司推荐榜:复禹信息,技术与场景的深度融合之选,诚信的内地AI营销公司推荐

在人工智能技术全面渗透营销领域的 2025 年,企业对 AI 营销服务的需求已从单纯的流量获取转向 "技术合规 + 场景适配 + 效果可溯" 的综合解决方案。为帮助企业精准筛选合作伙伴,本文基于技术架构、数据治理…

2025食堂承包供应商优质企业推荐榜:专业力量守护团餐品质,食堂承包企业

随着团餐行业向标准化、集约化加速转型,食堂承包服务已从基础餐饮保障升级为涵盖安全管控、营养定制、智慧运营的综合服务体系。本次推荐聚焦 2025 年食堂承包领域表现突出的企业,结合服务口碑、安全管理、创新能力等…

2025年DHB多极柔性一体式滑触线厂家推荐榜:瑞能电器,动力传输设备的专业之选,DHR单极柔性一体式滑触线厂家推荐

在工业自动化与智能化升级的浪潮中,滑触线作为移动设备的 “动力生命线”,其性能稳定性直接影响生产效率。本文结合技术实力、产品适配性及服务体系等维度,筛选出 3 家具备突出优势的滑触线专业厂家,并附上实用选择…

2025年优质的石英管行业厂商推荐榜:江盛达,赋能高端制造的材料基石,石英管阀,石英管阀接头厂家推荐榜

石英管作为以高纯度二氧化硅为核心的特种工业制品,凭借耐高温、强化学惰性、优异光学性能等特质,已成为半导体、光伏、光通信等高端产业不可或缺的基础材料。随着 2025 年新能源与电子信息产业的加速升级,市场对石英…

四川腊肠腊肉烘干房厂家推荐:腊肠腊肉烘干房,专注风干鱼烘干房研发与生产,助力产业干燥需求

烘干设备作为农产品加工、食品生产及工业制造等领域的关键装备,其性能直接影响物料品质与生产效率。本文结合生产规模、技术实力、产品适配性及服务能力等核心维度,筛选出 2025 年值得关注的烘干房厂家,为行业采购决…

2025年安徽电厂电伴热带厂家精选榜单:钢铁厂电伴热带厂家技术与服务双优品牌推荐

随着工业智能化升级与新能源产业扩张,电伴热带作为管道保温、设备温控的核心装备,市场需求持续攀升。2025 年全球电伴热带市场规模预计向 263.5 亿元迈进,但市场中产品质量参差不齐、服务能力差异显著的问题仍待解决…

2025诚信的泰国货架厂家推荐榜:豪威金属,立体货架厂家与服务双驱动下的优选之选,可靠的高位货架厂家推荐

2025 年中国货架市场规模预计将突破 310 亿元,在新零售升级与仓储物流需求激增的双重推动下,行业正加速向智能化、绿色化、定制化方向转型。本次结合市场口碑、技术实力与服务能力,筛选出两家具备核心竞争力的货架企…

2025进口艺术涂料厂家推荐榜:布雷诺,意大利进口艺术涂料厂家,从专业视角解锁墙面美学与品质之选

在家居审美升级与健康需求凸显的当下,艺术涂料凭借丰富的肌理质感与环保属性,成为家装墙面装饰的热门之选。经过对市场口碑、产品性能与实际应用场景的综合调研,2025 年艺术涂料推荐榜单新鲜出炉,同时附上专业选择…

2025石牌坊厂家推荐榜:嘉祥盛,农村石牌坊厂家传统工艺与现代匠心的传承之路,景区石牌坊厂家推荐

石牌坊作为承载中国传统建筑文化的瑰宝,其制作工艺融合了选材智慧、雕刻技艺与文化内涵,在乡村振兴、文旅开发等领域的需求持续攀升。本文基于工艺实力、文化传承度、项目口碑等维度,筛选出 2025 年值得关注的石牌坊…

APP快速集成即时通讯系统-多语言支持

一、集成开发核心机制 信贸通即时通讯系统集成开发的核心优势在于低耦合、高安全、快部署,无需开发人员直接操作底层数据库,仅通过调用封装好的类函数与标准化接口,即可在现有或待开发应用系统中完成集成,实现业务…

接雨水问题反思与最大容器问题对比

接雨水问题反思&与最大容器问题对比在数组类经典问题中,“接雨水”与“盛最多水的容器”因场景高度相似(均围绕柱子与水的交互)常被混淆,但二者的核心目标、储水逻辑和解法路径差异显著。 本文将系统拆解两类问…

2025东莞餐桌滑轨厂家推荐榜:万利亨通,非标定制服务器滑轨厂家从家居到工业的优质选择指南

滑轨作为家具制造、工业设备等领域的核心五金配件,其品质直接决定产品使用体验与耐用性。本文结合市场调研与行业口碑,筛选出两家在技术实力、产品适配性与市场认可度上表现突出的滑轨企业,并附上实用选购指南,为不…

2025高尔夫模拟器品牌推荐榜:佛山高尔夫模拟器生产厂家聚焦实用与适配

随着室内高尔夫运动的大众化普及,高尔夫模拟器逐渐成为家庭休闲、商业会所及专业训练的核心设备。但市场上品牌繁杂,技术参数差异大,如何挑选适配需求的产品成为用户难题。基于技术测评、用户反馈及场景适配能力,本…

2025打圈机厂家推荐榜:佛山首域领衔,数控打圈机厂家聚焦精度与效率的实力之选

在金属线材成型领域,打圈机的精度、稳定性直接决定制造企业的生产效率与产品品质。结合生产规模、技术实力与用户口碑,梳理出 2025 年值得关注的打圈机厂家,助力行业伙伴精准选型。 一、优质空心管打圈机厂家推荐 推…

2025年U字型/不锈钢自动升降/智能不锈钢下排风/不锈钢取材台推荐榜:北京中宝元公司领衔,这些实力派企业凭什么脱颖而出?

2025年,不锈钢取材台已从基础操作设备升级为适配细分工艺、提升作业效率的核心工具,在材料加工、电子元件制备、科研样品处理等领域需求激增。基于市场口碑与产品实力,以下 4 家企业跻身推荐榜单。 北京中宝元科技发…

2025小红书种草/代运营/营销/推广/探店服务推荐榜:广州布马网络以全链路运营领跑,这些专业服务商成品牌破圈新选择

在小红书平台用户规模突破 3.5 亿、种草经济进入精细化运营的 2025 年,优质的小红书种草服务成为品牌触达消费群体、提升转化的关键。2024 年国内小红书种草相关服务市场规模已达 98.7 亿元,2025 年预计将以 18.2% 的…