二叉树的讲解《二》(二叉树实现堆)

  个人主页:欢迎大家光临——>沙漠下的胡杨

  各位大帅哥,大漂亮

 如果觉得文章对自己有帮助

 可以一键三连支持博主

 你的每一分关心都是我坚持的动力

 

 ☄: 本期重点:二叉树实现堆

  希望大家每天都心情愉悦的学习工作。 


目录

堆的定义:

堆的存储结构:

堆的代码实现:

头文件篇:

源文件篇:

堆的初始化:

堆的打印:

堆的销毁:

返回堆顶元素:

判断堆元素是否为空:

返回堆的元素个数:

堆的插入和向上调整:

堆的删除和向下调整:

实现代码正确性:

整体代码:

头文件:

源文件:


堆的定义:

 我们认为堆是一个完全二叉树,并且堆中的父子关系 有且只有一种,要么是父亲结点大于等于孩子结点,要么是父亲节点小于等于孩子结点。

我们根据根节点在堆中的大小,来确定是大堆还是小堆,如果根节点在堆中最大,则是大根堆;如果根节点在堆中最小,则是小根堆。

堆的存储结构:

我们知道一般的二叉树使用链式存储,但是完全二叉树可以使用数组进行存储。

一般的二叉树使用数组存储,会造成大量的空间浪费,而完全二叉树,就不会哦!

 又因为堆刚好是完全二叉树,所以我们使用数组存储。

上述示意图中,二叉树的部分是我们想象中的逻辑结构,下面数组部分是我们实际在数组中的存储结构 

堆的代码实现:

头文件篇:

首先我们和顺序表一样写出,头文件,存储结构,函数接口。

#pragma once
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capaticy;
}HP;

void Swap(HPDataType* p1, HPDataType* p2);//交换


void AdjustDwon(HPDataType* a, int size, int parent);//向下调整 (时间复杂度O(N))

void AdjustUp(HPDataType* a, int child);//向上调整(时间复杂度O(N))

void HeapPrint(HP* php);//打印

void HeapInit(HP* php);//初始化

void HeapDestroy(HP* php);//销毁

void HeapPush(HP* php, HPDataType x);//插入

void HeapPop(HP* php);//删除

HPDataType HeapTop(HP* php);//返回堆顶元素

bool HeapEmpty(HP* php);//判空

int HeapSize(HP* php);//返回堆的元素个数。

源文件篇:

上述的一些接口比较简单,我们就直接放代码,比较难的接口,我们详细说。

堆的初始化:

void HeapInit(HP* php)//初始化
{
	assert(php);
	php->a = NULL;
	php->size = php->capaticy = 0;
}

堆的打印:

void HeapPrint(HP* php)//打印
{
	assert(php);

	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");

}

堆的销毁:

void HeapDestroy(HP* php)//销毁
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capaticy = 0;
}

返回堆顶元素:

HPDataType HeapTop(HP* php)//返回堆顶元素
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

判断堆元素是否为空:

bool HeapEmpty(HP* php)//判空
{
	assert(php);

	return php->size == php->capaticy;
}

返回堆的元素个数:

int HeapSize(HP* php)//返回堆的元素个数。
{
	assert(php);

	return php->size;
}

下面我们要讲的是堆的最重要部分 插入 和 删除 功能,以及向上调整和向下调整算法。

堆的插入和向上调整:

我们建堆插入时,一般会插入在动态数组的最后一个元素处插入数据,然后在进行调整,这个调整叫向上调整算法。

 我们在调整时,满足父亲节点的下标和孩子节点的下标满足一个公式P = (C-1)/ 2

利用这一公式我们可以进一步找到父亲节点,然后调整,代码如下:

void AdjustUp(HPDataType* a, int child)//向上调整
{
	int parent = (child - 1) / 2;//找到父亲节点

	while (child > 0)//如果child等于0,表示调整到根,不可在调整返回。
	{
		//孩子大于父亲就交换(大根堆)
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x)//插入
{
	assert(php);
	if (php->size == php->capaticy)
	{
		int NewCapaticy = php->capaticy == 0 ? 4 : 2 * php->capaticy;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*NewCapaticy);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capaticy = NewCapaticy;
	}

	php->a[php->size] = x;
	php->size++;
	//最后要给的是孩子的下标,是size-1.
	AdjustUp(php->a, php->size - 1);
}

堆的删除和向下调整:

同样在堆删除数据时,会先交换最后一个数据和根,然后让根进行向下调整。

我们也使用了公式: C(左孩子)= P*2 +1

我们依据上图实现下代码吧:

void AdjustDwon(HPDataType* a, int size, int parent)//向下调整
{
	int child = parent * 2 + 1;//左孩子

	while (child < size)//孩子节点下,到size结束
	{   
		//要在确定有右孩子的情况下,右孩子大于左孩子,就使用右孩子(大堆)
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child = child + 1;
		}
		//如果孩子大就交换(大堆)
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)//删除
{
	assert(php);
	assert(!HeapEmpty(php));

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDwon(php->a, php->size, 0);
}

实现代码正确性:

我们使用一个小例子来验证下代码的正确性,我们使用一些随机值,然后把他们插入堆中,接着我们打印下,判断是否和我们想象中的逻辑结构相符合:

void HeapTest1()
{
	HP hp;//建堆
	HeapInit(&hp);//初始化
	int a[] = { 10, 15, 18, 41, 56, 89, 66, 23, 37, 75 };//随机值

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);//插入堆中
	}

	HeapPrint(&hp);//打印

	HeapPop(&hp);//删除根节点

	HeapPrint(&hp);//打印

	HeapDestroy(&hp);//销毁
}

int main()
{
	HeapTest1();//测试函数
	return 0;
}

 结果如上,我们验证下逻辑结构是否正确。

第一次打印,符合大根堆性质。

第二次打印,符合大根堆性质。

整体代码:

头文件:

#pragma once
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capaticy;
}HP;

void Swap(HPDataType* p1, HPDataType* p2);//交换


void AdjustDwon(HPDataType* a, int size, int parent);//向下调整 (时间复杂度O(N))

void AdjustUp(HPDataType* a, int child);//向上调整(时间复杂度O(N))

void HeapPrint(HP* php);//打印

void HeapInit(HP* php);//初始化

void HeapDestroy(HP* php);//销毁

void HeapPush(HP* php, HPDataType x);//插入

void HeapPop(HP* php);//删除

HPDataType HeapTop(HP* php);//返回堆顶元素

bool HeapEmpty(HP* php);//判空

int HeapSize(HP* php);//返回堆的元素个数。

源文件:

#include "Heap.h"

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


void HeapPrint(HP* php)//打印
{
	assert(php);

	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");

}

void HeapInit(HP* php)//初始化
{
	assert(php);
	php->a = NULL;
	php->size = php->capaticy = 0;
}

void HeapDestroy(HP* php)//销毁
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capaticy = 0;
}


HPDataType HeapTop(HP* php)//返回堆顶元素
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

bool HeapEmpty(HP* php)//判空
{
	assert(php);

	return php->size == php->capaticy;
}

int HeapSize(HP* php)//返回堆的元素个数。
{
	assert(php);

	return php->size;
}

void AdjustDwon(HPDataType* a, int size, int parent)//向下调整
{
	int child = parent * 2 + 1;//左孩子

	while (child < size)//孩子节点下,到size结束
	{   
		//要在确定有右孩子的情况下,右孩子大于左孩子,就使用右孩子(大堆)
		//想变为小根堆,大于变小于(child +1 <size不变)
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child = child + 1;
		}
		//如果孩子大就交换(大堆)
		if (a[child] > a[parent])//想变为小根堆,大于变小于
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void AdjustUp(HPDataType* a, int child)//向上调整
{
	int parent = (child - 1) / 2;//找到父亲节点

	while (child > 0)//如果child等于0,表示调整到根,不可在调整返回。
	{
		//孩子大于父亲就交换(大根堆)
		if (a[child] > a[parent])//想变为小根堆,大于变小于
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x)//插入
{
	assert(php);
	if (php->size == php->capaticy)
	{
		int NewCapaticy = php->capaticy == 0 ? 4 : 2 * php->capaticy;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*NewCapaticy);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capaticy = NewCapaticy;
	}

	php->a[php->size] = x;
	php->size++;
	//最后要给的是孩子的下标,是size-1.
	AdjustUp(php->a, php->size - 1);
}

void HeapPop(HP* php)//删除
{
	assert(php);
	assert(!HeapEmpty(php));

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDwon(php->a, php->size, 0);
}

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沙漠下的胡杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值