首页 > 其他分享 >二叉树(四)

二叉树(四)

时间:2023-07-22 18:07:18浏览次数:23  
标签:结点 parent 小堆 二叉树 child 节点

二叉树的顺序结构及堆的实现

二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费,而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。

需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,这里的堆指的是数据结构,而操作系统虚拟进程地址空间中的堆是操作系统中管理内存的一块区域分段。

image-20230710081914589

堆的概念及结构

如果有一个关键码的集合K = {k<sub>0</sub>,k<sub>1</sub>,k<sub>2</sub>……k<sub>n-1</sub>},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:K<sub>i</sub> <= K<sub>2 * i + 1</sub>且K<sub>i</sub> <= K<sub>2 * i + 2 </sub>(K<sub>i</sub> >= K<sub>2 * i + 1</sub>且K<sub>i</sub> >= K<sub>2 * i + 2 </sub>)i = 0,1,2……,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树

image-20230710084153392

堆的向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。(若想将其调整为小堆,那么根结点的左右子树必须都为小堆;若想将其调整为大堆,那么根结点的左右子树必须都为大堆。)

向下调整法的基本思想(以小堆为例):

从根结点处开始,选出左右孩子中值较小的孩子,让值较小的孩子与其父亲进行比较:

如果孩子比父亲节点值小,则该孩子与父亲节点进行交换,并将原来孩子节点的位置当作父结点继续向下进行调整,直到调整完成;

如果孩子比父亲节点值大,就不需要处理了,说明此时调整完成,该树已经是小堆了。

以小堆为例:

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

image-20230710090619263

向下调整法的代码如下(以小堆为例):

//交换函数
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}
//堆的向下调整(小堆)
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++;
		}
		//当父结点大小孩子节点时,交换位置并更新父结点和子节点
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);//交换
			parent = child;
			child = (parent * 2) + 1;
		}
        //堆已经形成
		else
		{
			break;
		}
	}
}

使用堆向下调整算法,最坏情况下(一直需要交换节点),假设树的高度为h,那么需要交换的次数为h - 1;假设该树的节点个数为N,那么h = log<sub>2</sub>(N+1)(按照满二叉树计算),所以可以得出堆的向下调整算法的时间复杂度为:O(log<sub>2</sub>N)。

使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆,那么如何将一个树调整为堆呢?

可以从倒数第一个非叶子节点开始进行向下调整,并且从该节点开始向前依次进行向下调整:第一个非叶子节点也就是最后一个叶子节点的父结点,假设节点个数为n,则最后一个叶子节点下标为n-1,由child = (parent * 2) + 1,可得其父结点的下标为(n-1 -1)/2;

image-20230710103417401

代码(以小堆为例):

//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
    AdjustDown(php->a, php->size, i);
}

建堆的时间复杂度:

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果) :

image-20230710104410420

由上图计算建堆过程中总的调整次数:T(n) = 1 * (h - 1) + 2 * (h - 2) + ……+2^h-2^ * 1;再通过错位相减法最后求得:T(n) = 1- h + 2^1^ + 2^2^ +

…… + 2^h-1^,等比数列求和得:T(n) = 2^h^ - h - 1,设N是满二叉树的节点个数,由 N = 2^h^ - 1和h = log<sub>2</sub>(N + 1)可以求出T(n) = N - log<sub>2</sub>(N+1),则建堆的时间复杂度为O(N)。

总结:

堆的向下调整法的时间复杂度:O(logN)

建堆的时间复杂度:O(N)

堆的向上调整算法

当我们在一个堆的末尾插入一个数据后,如果要继续保持这是个堆,就需要对堆进行调整,需要用到堆的向上调整法。

向上调整法的基本思想(以建小堆为例):将插入结点作为目标节点,和其父结点比较,如果目标结点的值比父结点的值小,则将目标结点与父结点进行交换,并将目标结点的父结点当作新的目标结点继续进行向上调整;如果目标结点的值比父结点的值大,则停止向上调整,说明该树已经是小堆。

image-20230710110204714

代码(以小堆为例):

//交换函数
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}

//堆的向上调整(小堆)
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;//求父结点位置
	while (child > 0)//调整到根结点的位置截止
	{
		if (a[child] < a[parent])//孩子结点的值小于父结点的值
		{
			//将父结点与孩子结点交换
			Swap(&a[child], &a[parent]);
			//继续向上进行调整
			child = parent;
			parent = (child - 1) / 2;
		}
		else//已成堆
		{
			break;
		}
	}
}

标签:结点,parent,小堆,二叉树,child,节点
From: https://blog.51cto.com/u_15562309/6817613

相关文章

  • 二叉树叶子结点的个数Python
    实现二叉树叶子节点个数的Python代码概述在本文中,我将向你展示如何使用Python来计算二叉树的叶子节点个数。我将向你解释这个过程的每一步,并提供相应的代码示例。步骤下面是计算二叉树叶子节点个数的步骤:步骤描述1创建一个二叉树2定义一个函数来计算叶子节点个......
  • 二叉树典型例题
    输入两棵二叉树的序列AB,判断B是否是A的子结构(NULL不是任何树的子结构) 创建了判断两个节点是否相等的以来函数,在判断是否是子结构的函数里用递归实现。......
  • leetcode104二叉树搜索
    深度优先搜索,递归maxDepth(TreeNode*root){if(!root)return0;returnmax(maxDepth(root->left),maxDepth(root->right))+1;} 广度优先搜索,队列queue<TreeNode*>q;q.push(root);while(!q.empty()){intsize=q.size();while(size>0){Tree......
  • BST(二叉搜索树)、AVL(平衡二叉树)、RBT(红黑树)的区别
    一、二叉搜索树(BST:BinarySortTree)二叉查找树就是左结点小于根节点,右结点大于根节点的一种排序树,也叫二叉搜索树。二叉查找树比普通树查找更快,查找、插入、删除的时间复杂度为O(logN)。但是二叉查找树有一种极端的情况,就是会变成一种线性链表似的结构。此时时间复......
  • 优先队列(基于二叉树的堆)
    代码出处GoSDKcontainer/heap/heap.goInterface接口定义typeInterfaceinterface{sort.InterfacePush(xinterface{})//addxaselementLen()Pop()interface{}//removeandreturnelementLen()-1.}sort.Interface是自定义排序时需要实现的接......
  • 2023.7.14 在二叉树中分配硬币
    借用灵神的图:所以一个直观的想法就是,计算这个子树的硬币个数和节点个数的差的绝对值。这个就是需要传递给父结点的硬币数量。如果这个子树中的子结点也全部执行了这个操作,那么就会把硬币全部集中到当前结点,因此不用考虑子结点中的移动。所以这是个递归算法。(因为要先完成子结......
  • 算法学习day14二叉树part01-94、144、145
    packageSecondBrush.Tree;importjava.util.ArrayList;importjava.util.List;/***94.二叉树的中序遍历*给定一个二叉树的根节点root,返回它的中序遍历。**/publicclassBinaryTreeInorderTraversal_94{publicList<Integer>inorderTraversal(Tre......
  • LeetCode 剑指 Offer 08. 二叉树的下一个节点
    题目:二叉树的下一个节点给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。(树的后继)注意:如果给定的节点是中序遍历序列的最后一个,则返回空节点;二叉树一定不为空,且给定的节点一定不是空节点;解题思路二叉树的中序遍历:{[左子树],根节点,[右子树]}如图所示......
  • 【算法】根据二叉树的级别返回排序后的元素列表
    根据给定的Node树节点,返回包含按级别排序的树中元素的列表,这意味着根元素位于第一位,然后根子元素(从左到右)位于第二位和第三位,依此类推。1publicclassNode2{3publicNodeLeft;4publicNodeRight;5publicintValue;67publicNode(No......
  • 1644 题「二叉树的最近公共祖先 II
    对于这道题来说,p和q不一定存在于树中,所以你不能遇到一个目标值就直接返回,而应该对二叉树进行完全搜索(遍历每一个节点),如果发现p或q不存在于树中,那么是不存在LCA的。  ......