二叉树与堆学习笔记

一、树的概念

1.png

  • 树的度——这个节点直接链接的子节点数量
  • 树的深度——从根节点到最远的叶子节点距离
  • 叶子节点——无子节点的节点
  • 父节点——节点的直接所被链接的节点为父节点
  • 祖先节点——该节点的父节点及父节点的父节点等等
  • 兄弟节点——与该节点共有一个父节点的节点为该节点的兄弟节点

二、二叉树的概念与性质

二叉树具有树的性质,但在树的基础上做了限制

  • 二叉树——每个节点最多两个子节点

这里还要介绍两个特殊的二叉树
1.完全二叉树-除了倒数第二层以外,其他节点都是满的,最后一层必须是连续的,也就是说不能只存在右子树
2.满二叉树,最后一层均为叶子节点,其他节点都是满的
11.jpg
因为这两个树比较特殊,所以也存在一些规律,比如可以比较方便的计算树的深度,节点数量范围等等
1.深度为n的满二叉树的节点数量为(2^n)-1
2.深度为n的完全二叉树的节点数量范围是2(n-1)到(2n)-1之间
可以根据以上公式到推出通过节点数量计算深度的公式

链式二叉树的结构

 typedef int DataType;
 struct Node
 {
     struct Node* firstChild1;    // 第一个孩子结点
     struct Node* pNextBrother;   // 指向其下一个兄弟结点
     DataType data;               // 结点中的数据域
 };

三、堆的概念

堆其实就是使用完全二叉树的存储结构,子结点的值总是不大于或者不小于父节点,叫做大堆或者小堆
24.jpg
如果把一个数组看出一个堆,那么下标位置为N的节点的父节点为(N-1)/2,下标为N的左孩子为N2+1,其右孩子下标为N2+2。

堆的实现

堆的结构

typedef int HPDataType;//数据类型

typedef struct Heap
{
	HPDataType* _a;//
	int _size;//当前数据个数
	int _capacity;//数组容量
}Heap;

堆的初始化

// 构建一个容量大小为n的堆
void HeapCreate(Heap* hp, int n)
{
	HPDataType* a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	hp->_a = a;
	hp->_capacity = n;
	hp->_size = 0;
}

在堆中有两个主要的大小,向上/向下调整

图为数字9的向上调整

通过对每个数据进行调整,我们可以吧一个数调整成一个堆

但是向上调整和向下调整要求堆调整的时候调整目标方向的结构也为堆,我们面对一个无序的数组的话,我们可以从第二层开始向上调整或者倒数第二层开始向下调整来避免这个问题。

               
// 堆的销毁
void HeapDestory(Heap* hp)
{
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
	free(hp);
	hp = NULL;
}

//交换
void Swap(HPDataType* p, int a, int b)
{
	int tmp = p[a];
	p[a] = p[b];
	p[b] = tmp;
}

//向上调整//小堆
void AdjustUp(HPDataType*p,int child,int n)
{
	int parents = (child - 1) / 2;
	while (p[child] > p[parents])
	{
		Swap(p, child, parents);

		child = parents;
		parents = (child - 1) / 2;
	}
}

//向下调整//小堆
void AdjustDown(HPDataType* p, int parents,int n)
{
	int child = parents * 2 + 1;
	if (child<n && parents * 2 + 2&& p[child] < p[parents * 2 + 2])
	{
		child = parents * 2 + 2;
	}
	while(child < n && p[parents] < p[child])
	{
		Swap(p, parents, child);
		parents = child;
		child = parents * 2 + 1;
		if (child < n && parents * 2 + 2<n && p[child] < p[parents * 2 + 2])
		{
			child = parents * 2 + 2;
		}
	}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	if (hp->_capacity == hp->_size)
	{
		hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * (hp->_capacity * 2));
		hp->_capacity *= 2;
	}

	hp->_a[hp->_size++] = x;

	AdjustUp(hp->_a, hp->_size - 1,hp->_size);
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->_size);
	int tmp = hp->_a[0];
	hp->_a[0] = hp->_a[hp->_size - 1];
	hp->_a[hp->_size - 1] = tmp;

	AdjustDown(hp->_a, 0,hp->_size-1);
	hp->_size--;
}

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->_size);
	return hp->_a[0];
}

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size==NULL;
}

对于堆的删除,我们选择删除堆顶的数据,因为堆顶的数据总是最大或者最小的,我们通过交换堆顶与最后一位的数据,再把堆顶交换过去的数据进行向下排序来完成数据的删除。

堆排序

堆的排序不能直接选择排升序就建小堆这种的形式,因为不能保证一个小堆在数组中的顺序的完全升序的
image.png
所以我们选择和删除一样的思路,选择反其道而行之,排升序建立大堆
image.png
这样我们每次都可以把最大的选出来放到最后
image.png
然后再调整为堆,第二次就能选出次大的,依次循环就能完成排序
代码实现

// 对数组进行堆排序
void HeapSort(int* a, int n)
{
	//建大堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i, n);
	}

	//向下调整
	int end = n - 1;
	while (end > 0)
	{
		Swap(a, 0, end);
		AdjustDown(a, 0, end);
		end--;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值