树的四种遍历案例和AVL树的旋转的四种情况

树与二叉树

一, 树的基本术语

在这里插入图片描述

  1. 树中一个结点的子结点个数称为该结点的度, 树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3,树的度为3.
  2. 度大于0的结点称为分支结点(又称非终端结点),度为0(没有子女结点)的结点称为叶子结点(又称终端结点)。在分支结点中,每个结点的分支数就是该结点的度。
  3. 结点的深度,高度和层次。

二, 树的性质

  1. 树中的结点数等于所有结点的度数加1。
  2. 度为m的树中第i层上至多有m^(i-1)个结点(i>=0)。
  3. 高度为h的m叉树至多有(m^h-1)/(m-1)个结点。
  4. 具有n个结点的m叉树的最小高度为[log m(n(m-1)+1)]。

三, 二叉树的概念

1, 几种特殊的二叉树

在这里插入图片描述

  1. 满二叉树。一颗高度为h。且含有2^h - 1个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点,上图所示。满二叉树的叶子结点都集中在二叉树的最下一层,并且除叶子结点之外的每个结点度数均为2。

可以对满二叉树按层续编号:约定编号从跟结点(跟结点编号为1)起,自上而下,自左而右。这样,每个结点对应一个编号,对于编号为i的结点,若有双亲,则其双亲为[i/2],若有左孩子,则左孩子为2i; 若有右孩子,则右孩子为2i+1。

在这里插入图片描述

  1. 完全二叉树。设一个高度为h,有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1到n的结点一一对应时,称为完全二叉树,如上图。这种树的特点如下:

①. 如i<[i/2],则结点i为分支结点,否则为叶子结点。

②. 叶子节点只可能在层次最大的两层上出现。对于最大层次中的叶子结点,都依赖排列在该层最左边的位置上。

③. 若有度为1的结点,则只可能有一个,且该结点只有左孩子而无右孩子。

④. 按层序编号后,一旦出现某个结点(编号为i)为叶子结点或只有左孩子, 则编号大于i的结点均为叶子结点。

⑤. 若n为奇数,则每个分支结点都有左子女和右子女; 若n为二数,则编号最大的分支结点(编号为n/2)只有左子女,没有右子女,其中分支结点左,右子女都有。

2, 二叉树的性质

在这里插入图片描述

  1. 非空二叉树上的叶子结点数等于度为2的结点数加1,即n0 = n2 + 1。
  2. 非空二叉树上第k层上至多有2^k-1结点(k>=1)。
  3. 高度为h的二叉树至多有2^h-1个结点(h>=1)。
  4. 对于完全二叉树按从上到下
  5. 具有n个(n>0)结点的完全二叉树的高度为[log2(n+1)]或者[|log2n|+1]。

四, 二叉树的四种遍历和线索二叉树

1, 二叉树的四种遍历

在这里插入图片描述

所谓二叉树的遍历,是指按某条搜索路径访问树中的每个结点,使得每个结点均被服务一次,而且仅被访问一次。

由二叉树的递归定义可知,遍历一颗二叉树便要决定根结点N,左子树L和右子树R的访问顺序。按照先遍历左子树再遍历右子树的原则,常见的遍历次序有先序(NLR),中序(LNR),和后序(LRN)三种遍历算法,其中"序"指的是根结点再何时被访问。

① 先序遍历

先序遍历(preorder)的操作过程如下:

若二叉树为空,则什么也不做,否则,

  1. 访问根结点;
  2. 先序遍历左子树;
  3. 先序遍历右子树;

对应的递归算法如下:

void preorder(BiTree T)
{
	if (T)
	{
		//访问根结点
		show(T); 
		//递归遍历左子树
		preorder(T->lchild);
		//递归遍历右子树
		preorder(T->rchild);
	}
}

如上图所示, 先序遍历所得的结点序列为 1, 2, 4, 8, 9, 5, 10, 11, 3, 6, 12, 13, 7, 14, 15

② 中序遍历

中序遍历(inorder)的操作过程如下:
若二叉树为空,则什么也不做,否则:

  1. 中序遍历左子树,
  2. 访问根结点
  3. 中序遍历右子树

对应的递归算法如下:

void inorder(BiTree T)
{
	if (T)
	{
		// 递归遍历左子树
		inorder(T->lchild);
		//访问根结点
		show(T);
		// 递归遍历右子树
		inorder(T->rchild);
	}
}

对应上图二叉树, 中序遍历所得到的结点序列为 8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 15, 7

③ 后序遍历

后序遍历(postorder)的操作过程如下
若二叉树为空,则什么也不做;否则

  1. 后序遍历左子树
  2. 后序遍历右子树
  3. 访问根结点

对应的递归算法如下

void postorder(BiTree T)
{
	if (T)
	{
		//递归遍历左子树
		postorder(T->lchild);
		// 递归遍历右子树
		postorder(T->rchild);
		//访问根结点
		show(T);
	}
}

对应上图二叉树 :后序遍历得到的结点序列为 8, 9, 4, 10, 11, 5, 2, 12, 13, 6, 14, 15, 7, 3, 1

总结三种遍历算法中,递归遍历左,右子树的顺序都是固定的, 只是访问根结点的顺序不同。不管采用哪种遍历算法,每个结点都访问一次且仅访问一次,故时间复杂度都是O(n)。在递归遍历中,递归工作栈的栈深恰好为树的深度,所以在再坏情况下,二叉树是有n个结点且深度为n的单支树,遍历算法的空间复杂度为O(n)。

④ 递归算法和非递归算法的转换

借助栈,可以将二叉树的递归遍历算法转换为非递归算法。下面以中序遍历为列子给出中序遍历的非递归算法。先扫描(并非访问)根结点的所有左结点并将它们一一进栈,然后出栈一个结点p(显然结点p没有左孩子结点或者左孩子结点已经被访问过),访问它。然后扫描该结点的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一进栈,如此继续,直到栈空为止。

中序遍历的非递归算法如下:

void inorder2(BiTree T)
{
	//初始化栈
	initstatck(s);
	//p是遍历指针
	TiTree p = T;
	// 栈不空或者p不空时循环
	while (p || !s.emptr())
	{
		
		if (p)
		{
			//根指针进栈,遍历左子树
			s.push(p);
			//每遇到非空二叉树先向左走
			p = p->lchild;
		}
		else 
		{
			// 根指针退栈, 访问根结点,遍历右子树
			//退栈, 访问根结点
			s.pop(p);
			show(p);
			// 再向右子树走
			p = p->rchild;
		}
	}
}

后序遍历算法如下

void postorder2(BiTree T)
{
	if (T)
	{
		s.push(T);
		BiTree p = NULL;
		while (!s.empty())
		{
			p = s.top();
			// 判断当前lcheild和rchild是否已经再栈中 
			if (p->lchild && p->lchild != T && p->rchild != T)
			{
				s.push(p);
			}
			else if (p->rchild && p->rchild != T)
			{
				s.push(p);
			}
			else 
			{
				s.pop(p);
				show(p);
				T = p;
			}
		}
	}
}

再写非递归遍历时要有递归压栈的模型 来使用栈的数据结构

⑤ 层次遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EHHdrRzx-1574611452390)(https://github.com/chensongpoixs/cstructdata/raw/master/%E4%B8%89%2C%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E6%A0%91/img/binary_tree_order_level.png?raw=true)]

如上图所示为二叉树的层次遍历,即按照箭头所指方向,按照1, 2, 3, 4 层次顺序,对二叉树中的各个结点进行访问。

要进行层次遍历,需要借助一个队列。先将二叉树根结点入队,然后出队,访问该结点,若它有左子树,则将左子树根结点入队,如它有右子树,则将右子树根结点入队。然后出队,对出队结点访问,如此反复,直到队列为空。

二叉树的层次遍历算法如下

void level_order(BiTree T)
{
	initqueue(q);
	BiTree p;
	//将根结点入队列中
	q.endqueue(T);
	//队列不空循环
	while (!q.empty())
	{
		//队头元素出队
		q.dequeue(p);
		show(p);
		
		if (p->lchild)
		{
			//左子树不空,则左子树入队列
			q.endqueue(p->lchild);
		}
		if (p->rchild)
		{
			//右子树不空, 则右子树入队列
			q.endqueue(p->rchild);
		}
	}
}

五, 树与二叉树的应用

1, 平衡二叉树
① 平衡二叉树的定义

在这里插入图片描述

为避免树的高度增长过快,降低二叉排序树的性能, 我们规定在插入和删除二叉树的结点的时, 要保证任意结点的左, 右子树高度差的绝对不超过1, 将这样的二叉树称为平衡二叉树(Balanced Binary Tree), 简称平衡树(AVL)。定义结点左子树和右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1, 0或1。
因此,平衡二叉树可定义的为或者是一颗空树, 或者是具有下列性质的二叉树; 它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。如上图a所示 ,是平衡二叉树,如上图b所示是不平衡二叉树。结点种的值为该结点的平衡因子。

② 平衡二叉树的插入

二叉排序树保证平衡的基本思想如下:每当在二叉排序树种插入(或删除)一个结点时,首先检查其插入路径上的结点因为此次操作而导致了不平衡。若导致了不平衡,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。

在这里插入图片描述

注意:每次调整的对象都是最小的不平衡子树,即以插入路径上离插入结点最近的平衡因子的绝对值大于1的结点作为根的子树。如上图所示框的为最小不平衡子树。

平衡二叉树的插入过程的前半部分与二叉排序树相同,但在新结点插入后,若造成查找路径上的某个结点不再平衡,则需要做出相应的调整。一般可将失去平衡后进行调整的规律归纳为下列4种情况:

1, LL平衡旋转(右单旋转)

由于在结点A的左孩子(L)的左子树(L)上插入了新的结点,A的平衡因子由1曾至2, 导致以A为根的子树失去平衡, 需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

在这里插入图片描述

2, RR平衡旋转(左单旋转)

由于在结点A的右孩子®的右子树®上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转替代A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原右子树则作为A结点的右子树,

在这里插入图片描述

3, LR平衡旋转(先左后右双旋转)

由于在A的左孩子(L)的右子树®上插入新的结点,A的平衡因子由1曾至2,导致以A为根的子树失去平衡,需要进行二次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。

在这里插入图片描述

4, RL平衡旋转(先右后左双旋转)

由于在A的右孩子®的左子树(L)上插入新的结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行二次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该结点向左上旋转提升到A结点的位置。

在这里插入图片描述

个人博客地址:https://chensongpoixs.github.io/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二叉搜索: 二叉搜索是一种特殊的二叉,它的左子中所有节点的值都小于它的根节点的值,而右子中所有节点的值都大于它的根节点的值。二叉搜索支持快速的插入、查找和删除操作,时间复杂度为O(logn)。 插入和删除: 插入操作是将一个新节点插入到二叉搜索中,找到对应的位置并插入即可。删除操作比插入操作复杂一些,需要考虑到删除节点后二叉搜索的结构是否仍然满足二叉搜索的性质。如果需要删除的节点是叶子节点,直接删除即可。如果需要删除的节点只有一个子节点,可以将其子节点上移,删除该节点。如果需要删除的节点有两个子节点,可以找到该节点右子中最小的节点,将其值复制到需要删除的节点中,然后删除该最小节点。 : 的遍是指按照某种顺序依次访问中的所有节点。常用的方式有前序遍、中序遍和后序遍。前序遍是先访问根节点,然后访问左子和右子;中序遍是先访问左子,然后访问根节点和右子;后序遍是先访问左子和右子,然后访问根节点。 AVL: AVL是一种自平衡二叉搜索,它保证了的任意节点的左右子高度差不超过1。当插入或删除节点时,AVL会通过旋转操作来保持的平衡。AVL的查找、插入、删除操作的时间复杂度均为O(logn)。 红黑: 红黑也是一种自平衡二叉搜索,它的插入和删除操作比AVL更快,但查找操作稍慢一些。红黑通过染色和旋转操作来保持的平衡,它要求中任意一条从根到叶子的路径上,红色节点和黑色节点的个数相同。红黑的时间复杂度也为O(logn)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值