二叉树(2)——堆的实现

本文介绍了如何在C++中使用小堆数据结构,包括堆的初始化、销毁、插入元素(HeapPush)、删除元素(HeapPop)以及维护堆性质的方法,如调整节点确保堆性。重点讲解了调整函数(AdjustUp和AdjustDown)的操作过程和时间复杂度。
摘要由CSDN通过智能技术生成
  •   堆表面是数组,内核是完全二叉树/满二叉树
  •   在插入删除的时候要注意操作过后堆是否还是一个堆,要进行交换等操作。(向上调整
  •   逻辑上控制二叉树,物理上控制数组!!!

接下来我们用【小堆】来介绍堆的代码实现。

Heap.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;

//定义堆结构体
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
int HeapSize(HP* php);
bool HeapEmpty(HP* php);

Heap.c

HeapInit初始化

void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

HeapDestroy销毁

void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

HeapPush插入数据

  • 这里要注意在一个堆后插入一个数,这个堆是否还是堆的问题。所以要将插入的数和他的父亲进行比较。
  • 同时要考虑,如何使一个随机的数组排列成堆?这个问题在test.c中就可解决。

Swap函数实现

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

AdjustUp函数实现

  • 理解判断条件:child > 0
  • 首先比较插入的数和它的父亲,然后进行判断。
  • 若满足判断条件,就交换两个数的位置,并让child向上调整到parent的位置,让parent向上调整到它的父亲的位置,依此类推。
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0) 不能这样写,因为parent不可能<0
	while (child > 0) //用child判断
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//往上走
			child = parent;
			parent = (child - 1) / 2;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

HeapPush函数实现

  • 时间复杂度为O(logN),也就是高度次。
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
			php->a = tmp;
			php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

HeapPop删除数据 

  • 删除根
  • 不能用挪动覆盖法,堆会彻底乱掉,且时间复杂度是O(N)。
  • 方法:首尾交换再尾删,然后向下调整。 这样左右子树依旧是小堆。时间复杂度为O(logN)。
  • 如何判断叶子? 叶子*2+1>size

Swap函数实现

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

AdjustDown函数实现

  • 越界问题:这里要注意在while里一定要加上【child+1<size】这个判断条件,因为如果child是完全二叉树叶子的左孩子,而且没有右孩子的情况,那么【if (a[child + 1] < a[child])】这个child+1就会越界。
void AdjustDown(int* a, int size, int parent)
{
	//假设左孩子小,如果事实是右孩子小,那么就更新child
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child+1 < size && a[child + 1] < a[child])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
	
}

HeapPop函数实现

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	//首尾交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}

 HeapTop取堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

HeapSize堆元素个数

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

 HeapEmpty判断是否为空

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

Test.c

首先测试一下插入。

  • ⭐要注意这里使用了for循环,将数组中的数一个一个插入,然后排列成堆。这样就能保证这个数组是一个堆啦!
int main()
{
	int a[] = { 4,6,2,1,5,8,2,9};
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		HeapPush(&hp, a[i]);
	}

	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

测试取数组中前k个小的元素。

  • 这里先将数组里的数据排列成一个小堆,然后用while循环, 取一个堆顶元素删一个。
int main()
{
	int a[] = { 4,6,2,1,5,8,2,9};
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		HeapPush(&hp, a[i]);
	}

	int k = 3;
	while (k--)
	{
		printf("%d\n", HeapTop(&hp));
		HeapPop(&hp);
	}

	return 0;
}

输出小堆

int main()
{
	int a[] = { 4,6,2,1,5,8,2,9};
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		HeapPush(&hp, a[i]);
	}

	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值