首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一文彻底搞清楚二叉树和堆:从概念到存储结构解析

一文彻底搞清楚二叉树和堆:从概念到存储结构解析

作者头像
承渊政道
发布2025-12-18 17:25:43
发布2025-12-18 17:25:43
3050
举报

前言:前面小编已经介绍完了线性结构中线性表的栈和队列,自此关于数据结构中的线性结构的介绍就告一段落了,接下来我要介绍一下非线性结构中的树中的二叉树所包含的堆.它又有什么作用和用途呢?废话不多说,下面跟着小编的节奏🎵一起学习吧!

1.树

1.1树的概念与结构

树是⼀种⾮线性的数据结构,它是由 n(n>=0)个有限结点组成⼀个具有层次关系的集合.(n=0时称为空树)把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的. 任意一棵非空的树应满足: 1️⃣有⼀个特殊的结点,称为根(root)结点,根结点没有前驱结点. 2️⃣除根结点外,其余结点被分成 M(M>0) 个互不相交的集合T1、T2、……、Tm ,其中每⼀个集合Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树.每棵⼦树的根结点有且只有⼀个前驱,可以有0个或多个后继.因此,树是递归定义的. 例如;某家庭成员关系图如下:奶奶(用A表示)有三个孩子B、C、D;大儿子B有两个孩子E和F;二儿子C有一个孩子G;三儿子有两个孩子H和I;孙子E有一个孩子J;孙子G有两个孩子K和L.这个家庭关系可以用一棵树来表示,逻辑结构如图所示,如图一棵倒长的树.

树形结构中,⼦树之间不能有交集,否则就不是树形结构. ⾮树形结构:如图举例所示.

1️⃣⼦树是不相交的(如果存在相交就是图了,图以后会有介绍) 2️⃣除了根结点外,每个结点有且仅有⼀个⽗结点. 3️⃣⼀棵N个结点的树有N-1条边.


1.2树的基本术语

①⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点;如上图:A是B的⽗结点. ②⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点;如上图:B是A的孩⼦结点. ③结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0. ④树的度:⼀棵树中,最⼤的结点的度称为树的度;如上图:树的度为6. ⑤叶⼦结点/终端结点:度为 0 的结点称为叶结点;如上图:B、C、H、I… 等结点为叶结点. ⑥分⽀结点/⾮终端结点:度不为 0 的结点;如上图:D、E、F、G… 等结点为分⽀结点. ⑦兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟);如上图:B、C 是兄弟结点. ⑧结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推. 树的⾼度或深度:树中结点的最⼤层次;如上图:树的⾼度为4. ⑨结点的祖先:从根到该结点所经分⽀上的所有结点;如上图:A 是所有结点的祖先. ⑩路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为:A-E-J-Q;H到Q的路径H-D-A-E-J-Q ⑪⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙. ⑫森林:由 m(m>0)棵互不相交的树的集合称为森林.


1.3树的存储结构表示

树的结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法、孩⼦表⽰法、以及孩⼦兄弟表⽰法等. 1️⃣双亲表示法:用一组连续的存储空间来存储树中的结点,整棵树可以用一个一维数组来表示,结点的存储顺序自上而下、同层自左至右.在每个结点中附设一个指示器,用来指示其双亲结点在链表中的位置.

2️⃣孩子表示法:孩子表示法是把树中每个结点的孩子结点排列起来,构成一个线性表.由于每个结点的孩子个数不确定,所以通常采用单链表作为存储结构,称为孩子链表.n个结点有n个孩子链表(其中叶子结点的孩子链表为空表),而n个结点的数据和n个孩子链表头指针构成一个顺序表,用一个一维数组来表示.

3️⃣孩子兄弟表示法:孩子兄弟表示法又称二叉链表表示法,即以二叉链表作为树的存储结构.链表中的每个结点有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟结点. struct TreeNode { struct Node* child; // 左边开始的第⼀个孩⼦结点 struct Node* brother; // 指向其右边的下⼀个兄弟结点 int data; // 结点中的数据域 };


2.二叉树

2.1二叉树的概念与结构

二叉树是一种特殊的树型结构,一棵二叉树是结点的一个有限集合,该集合由⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空.

从上图可以看出⼆叉树具备以下特点: 1️⃣⼆叉树不存在度⼤于2的结点(结点的的孩子个数只能存在:0、1、2) 2️⃣⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树. 注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的.


2.2特殊的二叉树

2.2.1满二叉树

在⼀个⼆叉树中,如果所有分支结点都存在左子树和右子树,并且叶子结点都在同一层上,每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树.也就是说,如果⼀个⼆叉树的层数为 K ,且结点总数是2k − 1,则它就是满⼆叉树.


2.2.2完全二叉树

完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的.对于深度为 K 的,有 n 个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从1⾄n的结点⼀一对应时称之为完全⼆叉树.要注意的是满⼆叉树是⼀种特殊的完全⼆叉树.

通过观察可以发现: 1️⃣除了最后一层,每层结点的个数达到最大. 2️⃣最后一层结点个数不一定达到最大;没有达到最大–>完全二叉树,达到最大–>完全二叉树也是满二叉树. 3️⃣结点从左到右依次排列. ⼆叉树性质一 根据满⼆叉树的特点可知: 1️⃣若规定根结点的层数为1,则⼀棵⾮空⼆叉树的第i层上最多有 2i−1个结点. 2️⃣若规定根结点的层数为1,则深度为h的⼆叉树的最⼤结点数是2h−1. 3️⃣若规定根结点的层数为1,具有n个结点的满⼆叉树的深度h=log2(n+1)(log以2为底,n+1为对数) ⼆叉树性质二 对于具有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)/2=父结点.


2.3二叉树存储结构

2.3.1顺序结构

顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储.

现实中我们通常把堆(⼀种⼆叉树)使⽤顺序结构的数组来存储,需要注意的是这⾥的堆和操作系统虚拟进程地址空间中的堆是两回事,⼀个是数据结构,⼀个是操作系统中管理内存的⼀块区域分段.


2.3.2链式结构

⼆叉树的链式存储结构是指,⽤链表来表示⼀棵⼆叉树,即⽤链表来指示元素的逻辑关系.通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 .链式结构⼜分为⼆叉链和三叉链,目前我所介绍的⼀般都是⼆叉链.后续会在数据结构高阶中如红⿊树介绍三叉链.


3.实现顺序结构二叉树

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


3.1堆的概念和结构

如果有⼀个关键码的集合K = {k0,k1,k2 , …,kn−1 },把它的所有元素按完全⼆叉树的顺序存储⽅式存储,在⼀个⼀维数组中,并满⾜:Ki <= K2∗i+1(Ki >= K2∗i+1且Ki <= K2∗i+2),i = 0、1、2… ,则称为⼩堆(或⼤堆).将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆叫做最⼩堆或⼩根堆.

堆具有以下性质: 1️⃣堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值. 2️⃣堆总是⼀棵完全⼆叉树.


3.2堆的实现

Heap.c

代码语言:javascript
复制
#include"Heap.h"
//初始化堆
void HPInit(HP* php)
{
	php->arr = NULL;
	php->size = php->capacity = 0;
}
//堆的销毁
void HPDestroy(HP* php)
{
	if (php->arr)
		free(php->arr);
	php->arr = NULL;
	php->size = php->capacity = 0;
}
//堆的打印
void HPPrint(HP* php)
{
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->arr[i]);
	}
	printf("\n");
}
//孩子结点和父结点交换
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//大堆:>
		//小堆:<
		if (arr[child] > arr[parent])
		{
			//调整
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}
//堆的插入
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	//判断空间是否足够
	if (php->size == php->capacity)
	{
		int newCapcity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->arr, newCapcity*sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		php->arr = tmp;
		php->capacity = newCapcity;
	}
	php->arr[php->size] = x;
	AdjustUp(php->arr, php->size);
	++php->size;
}
// 判空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n)
{
	int child = parent * 2 + 1;//左孩子
	while (child < n)
	{
		//大堆:<
		//小堆:>
		if (child + 1 < 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;
		}
	}
}
//删除堆顶元素
void HPPop(HP* php)
{
	assert(!HPEmpty(php));
	// 0 php->size-1
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	--php->size;
	//向下调整
	AdjustDown(php->arr, 0, php->size);
}

//取堆顶数据
HPDataType HPTop(HP* php)
{
	assert(!HPEmpty(php));
	return php->arr[0];
}
//求size
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

Heap.h

代码语言:javascript
复制
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//堆的结构
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;     //有效数据个数
	int capacity; //空间大小
}HP;
void Swap(int* x, int* y);
void AdjustUp(HPDataType* arr, int child);
void AdjustDown(HPDataType* arr, int parent, int n);
void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPrint(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
//取堆顶数据
HPDataType HPTop(HP* php);
// 判空
bool HPEmpty(HP* php);
int HPSize(HP* php);

test.c

代码语言:javascript
复制
#include"Heap.h"
void arrPrint(int* arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
//堆排序
void HeapSort1(int* arr, int n)
{
	HP hp; //——————借助数据结构堆来实现堆排序
	HPInit(&hp);
	for (int i = 0; i < n; i++)
	{
		HPPush(&hp, arr[i]);
	}
	int i = 0;
	while (!HPEmpty(&hp))
	{
		int top = HPTop(&hp);
		arr[i++] = top;
		HPPop(&hp);
	}
    HPDestroy(&hp);
}
void HeapSort(int* arr, int n)
{
	//建堆——向下调整算法建堆
	for (int i = (n-1-1)/2; i>=0; i--)
	{
		AdjustDown(arr, i, n);
	}
	//堆排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, 0, end);
		end--;
	}
}
void test01()
{
	HP hp;
	HPInit(&hp);
	HPPush(&hp, 56);
	HPPush(&hp, 10);
	HPPush(&hp, 15);
	HPPush(&hp, 30);
	HPPush(&hp, 70);
	HPPush(&hp, 25);
	HPPrint(&hp);
	HPPop(&hp);
	HPPrint(&hp);
	HPPop(&hp);
	HPPrint(&hp);
	HPPop(&hp);
	HPPrint(&hp);
	HPPop(&hp);
	HPPrint(&hp);
    HPDestroy(&hp);
}
void test02()
{
	HP hp;
	HPInit(&hp);
    HPPush(&hp, 56);
	HPPush(&hp, 10);
	HPPush(&hp, 15);
	HPPush(&hp, 30);
	HPPrint(&hp);
    while (!HPEmpty(&hp))
	{
		int top = HPTop(&hp);
		printf("%d ", top);
		HPPop(&hp);
	}
    HPDestroy(&hp);
}
int main()
{
	test01();
	test02();
	int arr[6] = {19,15,20,17,13,10};
	printf("排序之前:");
	arrPrint(arr, 6);
    //堆排序
	HeapSort(arr, 6);
    printf("排序之后:");
	arrPrint(arr, 6);
	return 0;
}

3.2.1向上调整算法

堆的插⼊:将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆. 💡 向上调整算法 1️⃣先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后. 2️⃣插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可.

代码语言:javascript
复制
void AdjustUp(HPDataType* a, int child)
 {
 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 HPPush(HP* php, HPDataType x)
 {
 assert(php);
 if (php->size == php->capacity)
 {
 size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
 HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
 if (tmp == NULL)
 {
 perror("realloc fail");
 return;
 }
 php->a = tmp;
 php->capacity = newCapacity;
 }
 php->a[php->size] = x;
 php->size++;
 AdjustUp(php->a, php->size-1);
 }

接下来我们计算向上调整算法建堆时间复杂度 因为堆是完全⼆叉树,⽽满⼆叉树也是完全⼆叉树,此处为了简化使⽤满⼆叉树来证明(时间复杂度本来看的就是近似值,多⼏个结点不影响最终结果).

分析: 第1层,20 个结点,需要向上移动0层 第2层,21 个结点,需要向上移动1层 第3层,22 个结点,需要向上移动2层 第4层,23 个结点,需要向上移动3层 … 第h层,2h−1 个结点,需要向上移动h-1层 则需要移动结点总的移动步数为:每层结点个数*向上调整次数(第⼀层调整次数为0) T(h) = 21 ∗ 1 + 22 ∗ 2 + 23 ∗ 3 + … + 2h−2 ∗ (h − 2) + 2h−1 ∗ (h − 1) ① 2∗T(h) = 22 ∗ 1 + 23 ∗ 2 + 24 ∗ 3 + … + 2h−1 ∗ (h − 2) + 2h ∗ (h − 1) ② ② ⼀ ① 错位相减: T(h) = −21 ∗ 1 − (22 + 23 + … + 2h−2 + 2h−1) + 2h ∗ (h − 1) T(h) = −20 − 21 ∗ 1 − (22 + 23 + … + 2h−2 + 2h−1) + 2h ∗ (h − 1) + 20 T(h) = −(20 + 21 ∗ 1 + 22 + 23 + … + 2h−2 + 2h−1) + 2h ∗ (h − 1) + 20 T(h) = −(2h − 1) + 2h ∗ (h − 1) + 20 根据⼆叉树的性质: n = 2h − 1 和 h = log2 (n + 1) T (n) = −N + 2h ∗ (h − 1) + 20 F (h) = 2h(h − 2) + 2 F (n) = (n + 1)(log2 (n + 1) − 2) + 2 由此可得:💡 向上调整算法建堆时间复杂度为:O(n ∗ log2 n)


3.2.2向下调整算法

堆的删除:删除堆是删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏向下调整算法. 向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整. 💡 向下调整算法 1️⃣将堆顶元素与堆中最后⼀个元素进⾏交换 2️⃣删除堆中最后⼀个元素 3️⃣将堆顶元素向下调整到满⾜堆特性为⽌

代码语言:javascript
复制
void AdjustDown(HPDataType* arr, int parent, int n)
{
	int child = parent * 2 + 1;//左孩子
	while (child < n)
	{
		//大堆:<
		//小堆:>
		if (child + 1 < 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;
		}
	}
}
void HPPop(HP* php)
{
	assert(!HPEmpty(php));
	// 0 php->size-1
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	--php->size;
	//向下调整
	AdjustDown(php->arr, 0, php->size);
}

计算向下调整算法建堆时间复杂度

分析: 第1层,20 个结点,需要向下移动h-1层 第2层,21 个结点,需要向下移动h-2层 第3层,22 个结点,需要向下移动h-3层 第4层,23 个结点,需要向下移动h-4层 … 第h-1层,2h−2 个结点,需要向下移动1层 则需要移动结点总的移动步数为:每层结点个数*向下调整次数 T(h) = 20∗(h − 1) + 21∗(h − 2) + 22∗(h − 3) + 23∗(h − 4) + … + 2h−3∗2 + 2h−2 ∗1 ① 2∗T(h) = 21∗(h − 1) + 22∗(h − 2) + 23∗(h − 3) + 24∗(h − 4) + … + 2h−2∗2 + 2h−1∗1② ② ⼀ ① 错位相减: T(h) = 1 − h + 21 + 22 + 23 + 24 + … + 2h−2 + 2h−1 T(h) = 20 + 21 + 22 + 23 + 24 + …+ 2h−2 + 2h−1 − h T(h) = 2h − 1 − h 根据⼆叉树的性质:n = 2^h − 1^ 和 h = log2 (n + 1) T (n) = n − log2 (n + 1) ≈ n 💡 向下调整算法建堆时间复杂度为:O(n)


3.3堆的应用

3.3.1堆排序

版本⼀:基于已有数组建堆、取堆顶元素完成排序版本.该版本有⼀个前提,必须提供有现成的数据结构堆

代码语言:javascript
复制
// 1、需要堆的数据结构
// 2、空间复杂度 O(N)
void HeapSort(int* a, int n)
{
HP hp;
for(int i = 0; i < n; i++)
{
HPPush(&hp,a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
a[i++] = HPTop(&hp);
HPPop(&hp);
}
HPDestroy(&hp);
}

版本⼆:数组建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据

代码语言:javascript
复制
// 排升序,建⼤堆
// 排降序,建⼩堆
// O(N*logN)
void HeapSort(int* a, int n)
{
 // a数组直接建堆 O(N)
 for (int i = (n-1-1)/2; i >= 0; --i)
 {
 AdjustDown(a, n, i);
 }
 // O(N*logN)
 int end = n - 1;
 while (end > 0)
 {
 Swap(&a[0], &a[end]);
 AdjustDown(a, end, 0);
 --end;
 }
 }

堆排序时间复杂度计算

分析: 第1层,20个结点,交换到根结点后,需要向下移动0层 第2层,21个结点,交换到根结点后,需要向下移动1层 第3层,22个结点,交换到根结点后,需要向下移动2层 第4层,23个结点,交换到根结点后,需要向下移动3层 … 第h层,2h−1个结点,交换到根结点后,需要向下移动h-1层 通过分析发现,堆排序第⼆个循环中的向下调整与建堆中的向上调整算法时间复杂度计算⼀致,此处不再赘述.因此,堆排序的时间复杂度为O(n+n∗log n),即O(nlogn). 💡 堆排序时间复杂度为:O(n log n)


3.3.2TOP-K问题

TOP-K问题:即求数据集合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤. ⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等. 对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了 (可能数据都不能⼀下⼦全部加载到内存中).最佳的⽅式就是⽤堆来解决,基本思路如下: 先取前K个数据进行建堆,遍历剩下的 n-k 个数据跟堆顶进行比较 找最大的前K个数据,建小堆,比堆顶数据大就和堆顶交换 找最小的前K个数据,建大堆,比堆顶数据小就和堆顶交换 1️⃣⽤数据集合中前K个元素来建堆 前k个最⼤的元素,则建⼩堆. 前k个最⼩的元素,则建⼤堆. 2️⃣⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素 时间复杂度:O(n) = k+(n − k)log2k

代码语言:javascript
复制
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);
 }

敬请期待下一篇文章内容:遍历二叉树以及二叉树算法题!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.树
    • 1.1树的概念与结构
    • 1.2树的基本术语
    • 1.3树的存储结构表示
  • 2.二叉树
    • 2.1二叉树的概念与结构
    • 2.2特殊的二叉树
      • 2.2.1满二叉树
      • 2.2.2完全二叉树
    • 2.3二叉树存储结构
      • 2.3.1顺序结构
      • 2.3.2链式结构
  • 3.实现顺序结构二叉树
    • 3.1堆的概念和结构
    • 3.2堆的实现
      • 3.2.1向上调整算法
      • 3.2.2向下调整算法
    • 3.3堆的应用
      • 3.3.1堆排序
      • 3.3.2TOP-K问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档