二叉树与堆基本操作

二叉树

二叉树定义
每个节点的度小于等于 2 ,即每个节点的子树最多有两颗,且两个字数有左右顺序之分

在这里插入图片描述

满二叉树

二叉树的每一层节点数都达到了最大:

在这里插入图片描述

完全二叉树

完全二叉树中所存在的节点的位置与满二叉树中一一对应,但在最后一层依次从左往右存储时并未存满:

在这里插入图片描述

二叉树的基本性质

性质一:二叉树中第 i 层最多有 2^(i-1) 个节点

求解最多 ===》每一层的节点数都达到存储的最大值(每个节点都保证度为二)
在这里插入图片描述
因此,第 i 层最多有节点 : 2^(i-1) 个

性质二:高度为 h 的二叉树最多有 2^h - 1 个节点

求解最多 ===》每一层的节点数都达到存储的最大值(每个节点都保证度为二)

第一层节点最多有 : 2^(1-1) 个
第二层节点最多有 : 2^(2-1) 个
第三层节点最多有 : 2^(3-1) 个

第 h 层节点最多有 : 2^(h-1) 个

故:
共有节点数:
2^0 + 2^1+ 2^2 + … +2^(h-1)
=1*(1-2^h) / (1-2)
=2^h -1

性质三:在一棵二叉树中,假如度为零的节点个数为 n0,度为二的节点个数为 n2 ,则有关系式 n0=n2+1

假设一棵二叉树中节点总数为 N ,度为零的节点有n0个,度为二的节点有n2个,度为一的节点有n1个,则有等式  N=n0+n1+n2成立;

因为二叉树中除根节点外,其余节点都有一个双亲,故有 N 个节点的二叉树有 N-1 条边;
度为一的节点有一个孩子(即有一条向下的边),度为零的节点没有孩子(没有向下的边),度为二的节点有两个孩子(有两条向下的边)
因此有等式:N-1=0*n0+1*n1+2*n2

解两个方程式可以得到 n0=n2+1

性质四:具有 N 个总节点的二叉树高度为 [log2(n+1)](向上取整)

假设为满二叉树:
共有 N 个 节点,设高度为 h ,则有:
N=2^0+2^1+2^2+..+2^(h-1)
 =2^h - 1
可得 h=log2(N+1)

若不是满二叉树,则高度值计算得到的是一个小数值,故向上取整去接近这个小数值的整数为高度值

性质五:对一颗具有 N 个节点的完全二叉树,对它从上往下、从左往右进行从 1 编号:
(1)1 号节点为根节点;
(2)第 i 个节点的左孩子节点为 2i,2i<n 则为左孩子,否则左孩子不存在;
(3)第 i 个节点的右孩子节点为 2i+1,2i+1<n 则为左孩子,否则左孩子不存在;

在这里插入图片描述

注意:

若根节点标记为 0 时,则它的左右孩子节点分别为 2i+1 、2i+2 (2*i+2 < n时,即左右孩子存在时)
节点为 i 的双亲节点为 (i-1)/2

堆是一种完全二叉树结构------更适合使用顺序存储方式来存储数据

定义堆结构为:

typedef int DataType;

typedef struct Heap {
	DataType* arr;
	int capacity;
	int size;
}Heap;

小根堆的定义

如果任意节点的值都小于其孩子节点的值,则称之为小根堆(小堆)
在这里插入图片描述

大根堆的定义

如果任意节点的值都大于其孩子节点的值,则称之为大根堆(大堆)

在这里插入图片描述

建小堆

已知一个数组,可以对照来建立相应的堆结构:

int arr[]={27,15,19,18,28,34,65,49,25,37};

根据数组画堆结构:

在这里插入图片描述
由图可以看出来 ,以 27 为根节点的二叉树的左右子树都为小堆 ,因此需要将 27 向下调整为小堆形式
在这里插入图片描述
继续将 27 向下调整:
在这里插入图片描述
此时 27 为根的二叉树依旧不满足小根堆,继续向下调整:
在这里插入图片描述
此时将 27 调整到了最下方,堆已经调整为小堆形式。

由此可见,调整小堆形式的过程是一个循环向下调整的过程,因此相应代码形式为:

//将以 parent 为根的树调整为小根堆
void Swap(int* a,int* b)
{
	int tmp=*a;
	*a=*b;
	*b=tmp;
}
//向下调整以 parent 为根节点的树
void AdjustDown(int arr[],int size,int parent){
int child=2*parent+1;       //记录左孩子节点
while(child  < size) {  //有两个节点时
 //当右孩子节点存在时可以进行调整
		if(child + 1 < size && arr[child+1]<arr[child]){
		//存在右孩子时,选取较小的孩子
			child=child+1; 
		}
	//将小孩子节点与双亲进行比较
		if(arr[parent]>arr[child]){
			Swap(&arr[parent],&arr[child]);     //交换
	//大的元素向下调整,将小节点向上调整
	
	//调整之后导致下一层节点为根的树不满足小堆特性,需要继续调整
			parent=child;
			child=2*parent+1;      //根节点从 0 开始标号
		}
		else{
			return ;
		}
	}
}

因此,如需要将一个不是小根堆的数组结构调整为小堆结构,则需要从最后一个非叶子节点开始进行调整:

最后一个叶子节点 n-1
最后一个叶子节点的双亲节点(最后一个非叶子节点) ((n-1)-1) / 2

Heap* hp;

//核心代码:
//从最后一个非叶子节点开始进行调整小根堆
for(int root=(n-2)/2;root>=0;--root){
	AdjustDown(hp->arr,hp->size,root);
}

建大堆

给定一个数组形式:

int arr[]={18,27,28,15,19,34, 25,37,65,49};

据此数组画相应的堆结构:
在这里插入图片描述
建大堆,需要从最后一个非叶子节点开始依次向前调整,假如此时需要对某一个 parent (节点值为 15)为根节点的树进行调整大堆:

在这里插入图片描述
将 15 向下调整:
在这里插入图片描述

此时 15 为根的树依旧不满足大堆特性,继续调整:
在这里插入图片描述

对以 parent 为根的树进行调整为大根堆相应代码为:

//将以 parent 节点为根的树调整为大堆
void AdjustDownBig(int* arr, int size, int parent)
{
	//将 parent 树调整为大根堆
	int child = 2 * parent + 1;

	while (child < size)
	{
		//找较大的孩子节点
		if (child + 1 < size && arr[child + 1] > arr[child])
			child += 1;

		//较大值与双亲进行比较,双亲小则向下调整
		if (arr[child] > arr[parent]) {
			Swap(&arr[child], &arr[parent]);

			parent = child;    //继续向下进行判断
			child = 2 * parent + 1;
		}
		else
			return;
	}
}

如需要将整个非大根堆数组进行调整为大堆:

Heap* hp;

//核心代码:
//从最后一个非叶子节点开始进行调整为大堆
for(int root=(n-2)/2;root>=0;--root){
	AdjustDownBig(hp->arr,hp->size;root);
}

堆的基本操作(以小堆为例)

堆的创建

给定一个数组结构,需要按要求创建堆结构:

void HeadCreate(Heap* hp, DataType* a, int n)
{
//首先需要给堆空间
	hp->arr = (DataType*)malloc(sizeof(DataType)*n);
	if (NULL == hp->arr)
		return;

//假如创建的堆中有 n 个元素,定义堆容量为 n
	hp->capacity = n;

//将给定数组中元素拷贝到堆空间中
	memcpy(hp->arr, a, sizeof(DataType)*n);
	hp->size = n;

//调整为小堆结构
	//从倒数第一个非叶子节点进行调整
	//倒数第一个叶子节点的双亲,倒数第一个叶子节点 n-1, 它的双亲 ((n-1)-1)/2
	
	for (int root = (n - 2) / 2; root >= 0; root--) {
		AdjustDown(hp->arr, hp->size, root);   
	}
}

堆销毁

void HeapDestroy(Heap* hp)
{
	assert(hp);
	free(hp->arr);   //释放堆空间
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

堆判空

int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}

求堆中元素个数

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

取堆顶元素

DataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->arr[0];    //0号位置为堆顶元素
}

堆中元素的删除

堆元素的删除一般是指删除堆顶元素。
将堆中最后一个元素与堆顶元素进行交换,则堆中元素减一(size–),然后将堆中 size 个元素进行重新调整为小堆元素:

void HeadPop(Heap* hp)
{
	//删除堆顶元素
	if (HeapEmpty(hp))
		return;

	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);  //将堆顶元素与最后一个元素交换,然后调整
	
	hp->size--;
	AdjustDown(hp->arr, hp->size , 0);
}

在这里插入图片描述

堆的插入(重点*****)

首先需要判断容量是否足够:不够需扩容,够直接存;
其次,插入元素是在堆底进行的插入,因此需要判断插入的元素是否为最小(即是否需要向上调整);
最后,调整为小堆之后,size++(元素有效个数加一)

//判断堆容量
void CheckCapacity(Heap* hp)
{
	if(hp->capacity==hp->size)
	{
		int newcapacity=2*hp->capacity;
		DataType* tmp=(DataType*)malloc(sizeof(DataType)*newcapacity);
		if(NULL==tmp)
			return;
	
		memcpy(tmp,hp->arr,sizeof(DataType)*hp->size);
		free(hp->arr);  //释放旧空间
		hp->arr=tmp;
		hp->capacity=newcapacity;
	}
}
//进行元素插入时,是将元素插入到堆底

void HeapPush(Heap* hp,DataType x)
{
	assert(hp);
	CheckCapacity(hp);    //检查容量是否足够

	//新元素插入堆底
	hp->arr[hp->size++]=x;

	//元素插入之后需要检查是否满足大堆/小堆的基本条件,进行相应的向上调整
	AdjustUp(hp->arr,hp->size,hp->size-1);	
}
//向上调整函数-------小堆
void AdjustUp(DataType arr[],int size,int child)
{
	int parent=(child-1)/2;   //找需要调整的节点的父母节点
	while(chile>=0){
		if(arr[child]<arr[parent]){           //(新插入的)孩子节点小于双亲,需要将孩子上移
				Swap(&arr[child],&arr[parent]);
				child=parent;       //继续上一层的调整
				parent=(child - 1) / 2;
		}
		else{
				return;
		}
	}
}

至此,建堆(小堆)的基本操作已经完成,小堆是需要将小元素往上移,大元素向下移;对应大堆的基本操作时,需要将大元素向上小元素向下移。

但是若需求不是很明确,即一段代码想要既能够实现小堆又能够实现大堆时,我们可以使用一个函数指针的方法来实现该操作。

(有任何问题欢迎评论哦~)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值