树的概念与堆的模拟实现

本文介绍了树的基本概念,包括节点的度、叶子节点、父节点等,并讲解了二叉树的特性,如满二叉树和完全二叉树。接着,文章详细阐述了堆的定义,特别是大根堆,以及如何使用数组模拟实现堆,包括插入、删除元素的关键操作向上调整和向下调整。
摘要由CSDN通过智能技术生成

1、树的基本概念 

2、二叉树的基本概念

3、堆的基本概念与模拟实现


1、树的基本概念

树是由n个节点所组成的非线性结构,它是一个特殊的节点根节点和其余n-1个互不相交的节点相组成,如下图:

 几个基本的概念:

节点的度:一个节点含有的子树的个数,如A的度为3。

叶子结点或者终端节点:没有子树的节点,如E、F、G、H、I。

父节点:一个节点若有子树则成为父节点,它的子树称为它的子节点,如B是E的父节点。

兄弟节点:有相同父节点的子节点称为兄弟节点,如E和F是兄弟节点。

树的度:树内节点最大的度为树的度,如上图的树的度为3。

节点的层次:根节点为第1层,往后层次+1。

树的高度或深度:树的高度和深度相同,不过高度是从下向上,深度是从上向下。


2、二叉树的基本概念

二叉树是一种特殊的树,它的每个子树都只有至多2个节点,如下图:

满二叉树和完全二叉树:

满二叉树是二叉树的每一层的节点都达到最大值:

而完全二叉树则是要求除最后一层外节点达最大值,而最后一层节点要依次从左向右排列:

 关于二叉树的一些有用的性质:

二叉树度为0的节点永远比度为2的节点多1个,即n0 = n2 + 1。

高度为h的完全二叉树节点数为[2^(h - 1), 2^h - 1]。

满二叉树:n = 2^h - 1,h = log2(n + 1)。

用数组实现二叉树

数组可以模拟实现二叉树的存储结构,但仅适用于满二叉树或完全二叉树。

存在数组中的二叉树的值通过其所在下标位置可以表示一颗满二叉树或完全二叉树中父与子的关系:

parent = (child - 1) / 2

lchild = parent * 2 + 1

rchild = parent * 2 + 2


 3、堆的基本概念与模拟实现

堆就是在满二叉树或完全二叉树的基础上对其数据进行一些规则限定:

小根堆:树中所有父亲都小于或等于孩子

大根堆:树中所有父亲都大于或等于孩子

如图就是一个大根堆。

堆的模拟实现(大堆):

模拟一个堆需要包含以下函数:

typedef int hpDataType;

typedef struct Heap {
	hpDataType* a;
	int size;
	int capacity;
}hp;

hpDataType HeapTop(hp* h);

bool HeapEmpty(hp* h);

void HeapInit(hp* h);

void HeapPush(hp* h, hpDataType x);

void AdjustUp(hpDataType* a, int child); //向上调整

void HeapPop(hp* h); //删除堆顶元素

void AdjustDown(hpDataType* a, int n, int parent); //向下调整

void Swap(hpDataType* a, hpDataType* b);

 取堆顶元素和判空比较简单,这里的堆顶元素就是这颗二叉树的根节点:

hpDataType HeapTop(hp* h)
{
	assert(h);
	return h->a[0];
}

bool HeapEmpty(hp* h)
{
	assert(h);
	return h->size == 0;
}

堆的初始化和Swap函数:

void HeapInit(hp* h)
{
	h->a = (hpDataType*)malloc(sizeof(hpDataType) * 4);
	h->size = 0;
	h->capacity = 4;
}

void Swap(hpDataType* a, hpDataType* b)
{
	hpDataType x = *a;
	*a = *b;
	*b = x;
}

接下来两个函数是模拟实现堆的难点,堆的插入和删除。

先从插入来看,已知有了一个大堆:

 此时插入一个数据:

要想让它调整为大堆,那么就要对这个新插入的节点进行处理,根据大堆的规则,这里就要用到向上调整的方法,就是让它依次与它的父节点相比较,如果大于父节点则交换,直到它不大于父节点或调整到根节点为止。

看代码:

void AdjustUp(hpDataType* a, int child) //大堆
{
	int parent = (child - 1) / 2;
	while (child > 0) //child等于0时已经调整到根位置
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(hp* h, hpDataType x)
{
	assert(h);
	if (h->size == h->capacity) //扩容操作
	{
		h->capacity = 2 * h->capacity;
		hpDataType* tmp = (hpDataType*)realloc(h->a, sizeof(hpDataType) * h->capacity);
		h->a = tmp;
	}
	h->a[h->size] = x; //插入数据
	h->size++;

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

 而删除操作删除的是堆顶的元素,此时若对整棵树再进行调整就比较困难,所以选择让堆顶元素与最后一个元素互换,然后让这个元素从堆顶开始向下调整,这个过程和向上调整类似,直接看代码:

void HeapPop(hp* h) //删除堆顶元素
{
	assert(h);
	assert(!HeapEmpty(h));
	Swap(&h->a[0], &h->a[h->size - 1]); //将最后一个元素与堆顶交换
	h->size--;

	AdjustDown(h->a, h->size, 0);
}

void AdjustDown(hpDataType* a, int n, int parent) //向下调整
{
	int child = parent * 2 + 1; //默认孩子是左孩子
	while (child < n)
	{
		//选出左右孩子中最大的一个
		if (child + 1 < n && a[child] < a[child + 1]) //注意越界,child+1如果等与n就越界
		{
			child = child + 1;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

完整代码如下:

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

typedef int hpDataType;

typedef struct Heap {
	hpDataType* a;
	int size;
	int capacity;
}hp;

hpDataType HeapTop(hp* h)
{
	assert(h);
	return h->a[0];
}

bool HeapEmpty(hp* h)
{
	assert(h);
	return h->size == 0;
}

void HeapInit(hp* h)
{
	h->a = (hpDataType*)malloc(sizeof(hpDataType) * 4);
	h->size = 0;
	h->capacity = 4;
}

void Swap(hpDataType* a, hpDataType* b)
{
	hpDataType x = *a;
	*a = *b;
	*b = x;
}

void AdjustUp(hpDataType* a, int child) //大堆
{
	int parent = (child - 1) / 2;
	while (child > 0) //child等于0时已经调整到根位置
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(hp* h, hpDataType x)
{
	assert(h);
	if (h->size == h->capacity) //扩容操作
	{
		h->capacity = 2 * h->capacity;
		hpDataType* tmp = (hpDataType*)realloc(h->a, sizeof(hpDataType) * h->capacity);
		h->a = tmp;
	}
	h->a[h->size] = x; //插入数据
	h->size++;

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

void HeapPop(hp* h) //删除堆顶元素
{
	assert(h);
	assert(!HeapEmpty(h));
	Swap(&h->a[0], &h->a[h->size - 1]); //将最后一个元素与堆顶交换
	h->size--;

	AdjustDown(h->a, h->size, 0);
}

void AdjustDown(hpDataType* a, int n, int parent) //向下调整
{
	int child = parent * 2 + 1; //默认孩子是左孩子
	while (child < n)
	{
		//选出左右孩子中最大的一个
		if (child + 1 < n && a[child] < a[child + 1]) //注意越界,child+1如果等与n就越界
		{
			child = child + 1;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

双葉Souyou

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

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

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

打赏作者

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

抵扣说明:

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

余额充值