数据结构:从零开始掌握二叉树(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是数据个数,比下标大1
	int 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;
}

posted on 2025-11-05 13:00  slgkaifa  阅读(25)  评论(0)    收藏  举报

导航