数据结构初:二叉树的顺序存储——堆

目录

一、堆的定义

1.概念

2.性质

3.时间复杂度

二、堆的实现

1.向下/向上调整算法

 2.创建堆(原理图与上方相同)

3.插入堆

4.删除

5.取堆顶元素

6.堆的数据个数

7.判空 判满

8.堆排序

三、堆的应用

❤TopK问题


前言:二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。

一、堆的定义

1.概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

2.性质

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

3.时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明 ( 时间复杂度本来看的 就是近似值,多几个节点不影响最终结果)

二、堆的实现

1.向下/向上调整算法

对于堆的操作,最频繁使用的便是排序,即将堆整理成为有序的大堆或小堆,小堆使用向下调整,大堆则使用向上调整

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};

void Adjustup(Heap* php,int start) {
	assert(php);
	int i = start;
	int j = (i - 1) / 2;
	while (i > 0) {
		if (php->heap[i] < php->heap[j]) {
			Swap(&php->heap[i], &php->heap[j]);  //此处调用函数,会使算法效率降低
			i = j;
			j = (i - 1) / 2;
		}
		else
			break;
	}

}
void Adjustdown(Heap* php,int start) {
	assert(php);
	int i = start;
	int j = 2 * i + 1;
	while (j < php->size) {
		if (j+1<php->size&&php->heap[j + 1] < php->heap[j]) {
			j++;
		}
		if (php->heap[i] > php->heap[j]) {
			Swap(&php->heap[i], &php->heap[j]);
			i = j;
			j = 2 * i + 1;
		}
		else
			break;
	}
}

 2.创建堆(原理图与上方相同)

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
int a[] = {1,5,3,8,7,6}; 

在建堆之前我们要先对堆进行初始化

void HeapInit(Heap* php) {
	assert(php);
	php->heap = (HeapElem_Type*)malloc(sizeof(HeapElem_Type));
	assert(php->heap);
	php->capacity = HEAP_DEFAULT_SIZE;
	php->size = 0;
}
void HeapCreate(Heap* php, HeapElem_Type ar[], int n) {
	assert(php);
	php->heap = (HeapElem_Type*)malloc(sizeof(HeapElem_Type));
	assert(php->heap);
	memcpy(php->heap, ar, sizeof(HeapElem_Type) * n);
	int cur = n / 2 - 1;
	while (cur >= 0) {
		Adjustdown(php, cur);
		cur--;
	}
}

此处的建堆是引用的前面建好的数组数据,若有灵活使用的需求,可以在函数里面设置一个输入项。

3.插入堆

先插入一个数 到数组的尾上,再进行向上调整算法,直到满足堆。
void HeapInsert(Heap* php,HeapElem_Type v) {
	assert(php);
	if (HeapFull(php)) {
		return;
	}
	php->heap[php->size] = v;
	Adjustup(php, php->size);
	php->size++;
}

4.删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调 整算法。
void HeapErase(Heap* php) {
	assert(php);
	if (HeapEmpty(php)) {
		return; 
	}
	php->size--;
	php->heap[0] = php->heap[php->size];
	Adjustdown(php, 0);
}

5.取堆顶元素

HeapElem_Type HeapTop(Heap* php){
	assert(php&&!HeapEmpty(php);
	return php->heap[0];
}

6.堆的数据个数

由于定义堆结构时加入了容量size,该功能实现起来非常容易。

size_t Heap(Heap* php) {   
	assert(php);
	return php->size;

}

7.判空 判满

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

8.堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

void HeapSort(Heap* php, HeapElem_Type ar[], int n) {
	assert(php);
	php->heap = (HeapElem_Type*)malloc(sizeof(HeapElem_Type));
	assert(php->heap);
	memcpy(php->heap, ar, sizeof(HeapElem_Type) * n);
	int cur = n / 2 - 1;
	while (cur >= 0) {
		Adjustdown(php, cur);
		cur--;
	}

	while (!HeapEmpty(php)) {
		php->size--;
		Swap(&php->heap[0], &php->heap[php->size]);
		Adjustdown(php, 0);
	}
}

以上各段代码用到的宏定义、结构体:

#define HEAP_DEFAULT_SIZE 8
#define HeapElem_Type int

typedef struct Heap {
	HeapElem_Type* heap;
	size_t capacity;
	size_t size;
}Heap;

三、堆的应用

❤TopK问题

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个元素建堆
 // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	Heap hp;
    //建立含有K个元素的堆
	HeapInit(&hp, a, k);
	for (int i = k - 1; i < n; i++) {
		if (php->heap[0] < a[i]) {
			php->heap[0] = a[i];
			Adjustdown(php, 0);
		}
		i++;
	}
	for (int j = 9; j >= 0; j--) {
		printf("%d \t", php->heap[j]);
	}
}
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(int* a, n, 10);
}

(ps:小菜鸟一枚,如有错误之处,恳请各位斧正)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值