【数据结构】第六章 树与二叉树

1.树的概念

树的定义是递归的,是一种递归的数据结构。
树中的某个结点(除根结点外)最多只和上一层的一个结点(即其父结点)有直接关系,根结点没有直接上层结点,因此在 n n n个结点的树中有 n − 1 n-1 n1条边。
路径是从上向下的,同一双亲结点的两个孩子结点之间不存在路径。
路径长度是路径上所经过的边的个数。
树的基本性质:

  1. 树中的结点数等于所有结点的度数加1。
  2. 度为 m m m的树中第 i i i层上至多有 m i − 1 m^{i-1} mi1个结点( i > = 1 i>=1 i>=1)。
  3. 高度为 h h h m m m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/(m-1) (mh1)/(m1个结点。
  4. 具有 n n n个结点的 m m m又树的最小高度为 ⌈ l o g m ( n ( m − 1 ) + 1 ) ⌉ \lceil log_m(n(m-1)+1)\rceil logm(n(m1)+1)
    注:
  5. 树的路径长度是所有路径长度的总和,树根到每个结点的路径的最大值应是树的高度减1。
  6. 树中结点总数为 n n n,则 n = n= n=分支数 + 1 +1 +1,而分支数又等于树中各结点的度之和,即 n = 1 + n 1 + 2 n 2 + 3 n 3 + 4 n 4 = n 0 + n 1 + n 2 + n 3 + n 4 n=1+n_1+2n_2+3n_3+4n_4=n_0+n_1+n_2+n_3+n_4 n=1+n1+2n2+3n3+4n4=n0+n1+n2+n3+n4

2.二叉树

2.1. 基本概念与性质

  1. 二叉树与度为2的有序树的区别:
  1. 度为2的树至少有3个结点,而二叉树可以为空
  2. 度为2的有序树的孩子结点的左右次序是相对于另一孩子结点而言的,若某个结点只有一个孩子结点,则这个孩子结点就无须区分其左右次序,而二又树无论其孩子数是否为2,均需确定其左右次序
  1. 满二叉树:
  1. 高度为 h h h,且含有 2 h − 1 2^h-1 2h1个结点的二叉树
  2. 叶子结点都集中在二叉树的最下一层,除叶子结点外的每个结点度数均为2.
  3. 对于编号为 i i i的结点,若有双亲,则其双亲为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2。若有左孩子,则左孩子为 2 i 2i 2i;若有右孩子,则右孩子为 2 i + 1 2i+1 2i+1
  1. 完全二叉树:
    高度为 h h h、有 n n n个结点的二叉树,当且仅当其每个结点都与高度为 h h h的满二叉树中编号为 1 ~ n 1~n 1n的结点一一对应
  1. i < = ⌊ n / 2 ⌋ i<=\lfloor n/2\rfloor i<=n/2,则结点 i i i为分支结点,否则为叶子结点。
  2. 度为1的结点,只可能有一个,且只有左孩子.
  3. 出现编号为 i i i的结点为叶子结点或只有左孩子,则编号大于 i i i的结点均为叶子结点。
  4. n n n为奇数,则每个分支结点都有左孩子和右孩子;若 n n n为偶数,则编号最大的分支结点(编号为 n / 2 n/2 n/2)只有左孩子,没有右孩子,其余分支结点左、右孩子都有。
  1. 二叉排序树:
    左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字。左子树和右子树又各是一棵二又排序树。
  2. 平衡二叉树:
    树上任一结点的左子树和右子树的深度之差不超过1。
  3. 二叉树的性质:
  1. 非空二叉树上的叶子结点数等于度为2的结点数加1,即 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  2. 非空二叉树上第 k k k层上至多有 2 k − 1 2^{k-1} 2k1个结点( k > = 1 k>=1 k>=1)。
  3. 高度为 h h h的二叉树至多有 2 h − 1 2^h-1 2h1个结点( h > = 1 h>=1 h>=1)。
  4. 完全二叉树:
    1) i > 1 i>1 i>1时,双亲结点为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2 i i i为偶数,其双亲结点为 i / 2 i/2 i/2,为左孩子; i i i为奇数,其双亲结点为 ( i − 1 ) / 2 (i-1)/2 (i1)/2,为右孩子。
    2)当 2 i < = n 2i<=n 2i<=n时, 2 i 2i 2i为左孩子,当 2 i + 1 < = n 2i+1<=n 2i+1<=n时, 2 i + 1 2i+1 2i+1为右孩子
    3)结点 i i i所在层次(深度)为 ⌊ l o g 2 i ⌋ + 1 \lfloor log_2i\rfloor+1 log2i+1
  5. 具有 n n n ( n > 0 ) (n>0) (n>0)结点的完全二叉树的高度为 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1)\rceil log2(n+1) ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n\rfloor+1 log2n+1

2.2 二叉树的存储结构

  1. 顺序存储:
  1. 用一组地址连续的存储单元,将完全二叉树上编号为 i i i的结点元素存储在某个数组下标为 i − 1 i-1 i1的分量中
  2. 完全二叉树和满二叉树采用顺序存储比较合适。
  3. 在最坏情况下,一个高度为 h h h且只有 h h h个结点的单支树却需要占据近 2 h − 1 2^h-1 2h1个存储单元。

注:

  1. 这种存储结构要从数组下标1开始存储树中的结点,若从数组下标0开始存储,则不满足计算出其孩子结点在数组中的位置
  2. 树的顺序存储结构中,数组下标代表结点的编号,下标上所存的内容指示了结点之间的关系。二叉树的顺序存储结构中下标既代表了结点的编号,又指示了树中各结点之间的关系
  3. 二叉树都可以用树的存储结构来存储,但树却不都能用二叉树的存储结构来存储。
  1. 链式存储:
    类型描述:
typedef struct BiTNode{
     
	Elemtype data;
	struct BiTNode *lchild, *rchild;
}BiTNode,*BiTree;

注:

  1. 顺序存储空间利用率低,二叉树一般采用链式存储结构
  2. 含有 n n n个结点的二叉链表中,含有 n + 1 n+1 n+1个空链域

2.3 二叉树的遍历

u

2.4 线索二叉树

2.二叉树的遍历与线索化
<1>.二叉树的遍历

二叉树的遍历是指按一定规律对二叉树中的每个结点进行访问且仅访问一次。遍历操作就是将二叉树中结点按一定规律线性化的操作,目的在于将非线性化结构变成线性化的访问序列。
我们用L、D、R分别表示遍历左子树、访问根结点、遍历右子树,那么对二又树的遍历顺序就可以有六种方式:
(1)访问根,遍历左子树,遍历右子树(记做DLR)。
(2)访问根,遍历右子树,遍历左子树(记做DRL)。
(3)遍历左子树,访问根,遍历右子树(记做LDR)。
(4)遍历左子树,遍历右子树,访问根(记做LRD)。
(5)遍历右子树,访问根,遍历左子树(记做RDL)。
(6)遍历右子树,遍历左子树,访问根(记做RLD)。

  1. 先序遍历(DLR)操作过程:
    若二叉树为空,则空操作,否则依次执行如下3个操作:
    (1)访问根结点;
    (2)按先序遍历左子树;
    (3)按先序遍历右子树。
  2. 中序遍历(LDR)操作过程:
    若二叉树为空,则空操作,否则依次执行如下3个操作:
    (1)按中序遍历左子树;
    (2)访问根结点;
    (3)按中序遍历右子树。
  3. 后序遍历(LRD)操作过程:
    若二叉树为空,则空操作,否则依次执行如下3个操作:
    (1)按后序遍历左子树;
    (2)按后序遍历右子树;
    (3)访问根结点。
    显然,这种遍历是一个递归过程。
<2>线索二叉树

二叉树的遍历运算是将二叉树中结点按一定规律线性化的过程。当以二叉链表作为存储结构时,只能找到结点的左、右孩于信息,而不能直接得到结点在遍历序列中的前驱和后继信息。要得到这些信息可采用以下两种方法:第一种方法是将二叉树遍历一遍,第二种方法是充分利用二又链表中的空链域,将遍历过程中结点的前驱、后继信息保存下来。
在有n个结点的二又链表中共有2n个链域,但只有n-1个有用的非空链域,其余n+1个链域是空的。可以利用剩下的n+1个空链域来存放遍历过程中结点的前驱和后继信息。
【数据结构】第六章 树与二叉树_第1张图片
指向前驱和后继结点的指针叫做线索。对二义树以某种次序进行遍历并且加上线索的过程叫做线索化。
1.二叉树的线索化:线索化实质上是将二叉链表中的空指针域填上相应结点的遍历前驱或后继结点的地址,而前驱和后继的地址只能在动态的遍历过程中才能得到。因此线索化的过程即为在遍历过程中修改空指针域的过程。
【数据结构】第六章 树与二叉树_第2张图片
2.在线索二叉树中找前驱、后继结点
(1)找结点的中序前驱结点:对于结点p,当p->Ltag=1时,p->LChild 指向p的前驱。当p->Ltag=0时.p->LChild指向p的左孩子。由中序遍历的规律可知,作为根p的前驱结点,它是中序遍历p的左子树时访问的最后一个结点,即左子树的“最右下端”结点。
(2)找结点的中序后继结点:对于结点p,若要找其后继结点,当p->Rtag=1时,p->RChild即为p的后继结点;当p->Rtag=0时,说明p有右子树,此时p的中序后继结点即为其右子树的“最左下端”的结点。
(3)先序查找结点的后继:若p 存在左子树,则p的左孩子结点为p的后继,若无左子树,有右子树,则p的右孩子结点即为p的后继。若无左右孩子,则p->RChild中所指的结点为p 的后继。
(4)先序查找结点的前驱:比较困难,若p是根,前驱为空,若p是左孩子或者双亲结点无左孩子p为右孩子,p的前驱为双亲结点。若p为右孩子,双亲结点有左孩子,则p的前驱是双亲结点左子树中按先序遍历时最后访问的结点。
3.由遍历序列确定二叉树:
先序+中序可以确定一个二叉树
分析:先序中第一个结点为二叉树的根结点,中序遍历中,根结点将中序序列分为两部分,根结点之前为左子树,根结点之后为右子树。同时,左子树和右子树的根结点又可以分别把左子树和右子树划分为两个子序列,如此递归下去可得到二叉树。
后序+中序可以确定一个二叉树

3.树、森林和二叉树
<1>树的存储结构
  1. 双亲表示法
    这种方法用一组连续的空间来存储树中的结点,在保存每个结点的同时附设一个指示器来指示其双亲结点在表中的位置,其结点的结构如下:
    【数据结构】第六章 树与二叉树_第3张图片
    在这种存储结构中,求某个结点的孩子时需要遍历整个向量。
  2. 孩子表示法
    这种方法通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n个结点共有n个孩子链表(叶子结点的孩子链表为空表),而n个结点的数据和n个孩子链表的头指针又组成一个顺序表。
    【数据结构】第六章 树与二叉树_第4张图片
  3. 孩子兄弟表示法
    这种表示法又称为树的二叉表示法,或者二叉链表表示法,即以二叉链
    表作为树的存储结构。链表中每个结点设有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟(右兄弟)结点。
<2>树、森林与二叉树的互相转换

树的孩子兄弟链表结构与二叉树的二叉链表结构在物理结构上是完全相同的,只是它们的逻辑含义不同。

  1. 树转换为二叉树:
    (1)树中所有相邻兄弟之间加一条连线。
    (2)对树中的每个结点,只保留其与第一个孩子结点之间的连线,删去其与其它孩子结点之间的连线。
    (3)以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。
    在二又树中,左分支上的各结点在原来的树中是父子关系,而右分支上的各结点在原来的树中是兄弟关系。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必然为空。
    树做这样的转换所构成的二又树是唯一的。
    【数据结构】第六章 树与二叉树_第5张图片
  2. 森林转换为二叉树
    (1)将森林中的每棵树转换成相应的二叉树。
    (2)第一棵二又树不动,从第二棵二叉树开始,依次把后一棵二又树的根结点作为前一棵二又树根结点的右孩子,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。

【数据结构】第六章 树与二叉树_第6张图片

  1. 二叉树还原为树或森林
    (1)若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子……都与该结点的双亲结点用线连起来。
    (2)删掉原二又树中所有双亲结点与右孩子结点的连线。
    (3)整理由(1)、(2)两步所得到的树或森林,使之结构层次分明
<3>树与森林的遍历

树的先根遍历<=>二叉树的前序遍历
树的后根遍历<=>二叉树的中序遍历
森林的先序遍历、中序历和后序遍历与其相应二又树的先序遍历、中序遍历和后序遍历是对应相同的。

4.哈夫曼树

1.路径:指从一个结点到另一个结点之间的分支序列
2.路径长度:指从一个结点到另一个结点所经过的分支数目。
3.结点的权:树的每个结点赋予一个具有某种实际意义的实数
4.结点的带权路径长度:从树根到某一结点的路径长度与该结点的权的乘积
5.树的带权路径长度为树中所有叶子结点的带权路径长度之和,通常记为:
WPL=∑Wi * li (1<=i<=n)
n为叶子结点的个数,Wi为第i个叶子结点的权值,li为第i个叶子结点的路径长度。
6.结点n对应的路径长度为log2n(向下取整),所以前n项之和为∑log2k(向下取整,1<=k<=n)。完全二叉树的路径长度为∑log2k(树的深度)完全二叉树具有最小路径长度的性质,但不具有唯一性。有些树并不是完全二叉树,但也可以具有最小路径长度。
7.哈夫曼树:将给定结点构成一棵(外部通路)带权树的路径长度最小的二又树。

  1. 构造哈夫曼树
    哈夫曼树(最优二叉树):它是由n个带权叶子结点构成的所有二叉树中带权路径长度WPL最短的二叉树。
    构造哈夫曼算法的步骤如下:
    (1)初始化:用给定的n个权值{w1,w2,…,wn}对应的n个结点构成n棵二叉树的森林F={T1,T2,…,Tn),其中每一棵二叉树Ti(1<=i<=n)都只有一个权值为wi的根结点,其左、右子树为空。
    (2)找最小树:在森林F中选择两棵根结点权值最小的二叉树,作为一棵新二叉树的左、右子树,标记新二叉树的根结点权值为其左右子树的根结点权值之和。
    (3)删除加入:从F中删除被选中的那两棵二叉树,同时把新构成的二又树加入到森林F中。
    (4)判断:重复(2)、(3)操作,直到森林中只含有一棵二叉树为止,此时得到的这棵二叉树就是哈夫曼树。
    在哈夫曼树中权越大的叶子离根越近,则其具有最小带权路径长度。

  2. 哈夫曼编码
    前缀编码:任意一个编码不能成为其它任意编码的前缀(最左子串)
    哈夫曼编码:一棵具有n个子叶的哈夫曼树,对其左分支赋0,右分支赋1,则从根到每个叶子的通路上,各分支的赋值分别构成一个二进制串,此二进制串称为哈夫曼树。
    哈夫曼编码是前缀编码。是最优前缀编码。
    利用哈夫曼树可以得到平均长度最短的编码。

2.算法

1.二叉树

二叉链表结构:

typedef struct Node 
{
     
	DataType data;
	struct Node * LChild;
	struct Node * RChild;
}BiTNode, *BiTree;
<1>.二叉树的遍历(先中后)

(1) 二叉树的遍历算法

'''先序遍历二叉树'''
void PreOrder(BiTree root)
{
     
	if(root!=NULL)
	{
     
		Visit(root->data);
		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}
'''中序遍历二叉树'''
void InOrder(BiTree root)
{
     
	if(root!=NULL)
	{
     
		InOrder(root->LChild);
		Visit(root->data);
		InOrder(root->RChild);
	}
}
'''后序遍历二叉树'''
void PostOrder(BiTree root)
{
     
	if(root!=NULL)
	{
     
		PostOrder(root->LChild);
		PostOrder(root->RChild);
		Visit(root->data);
	}
}

(2)遍历算法应用

  1. 输出二叉树中的结点
    算法思想:输出二叉树中的结点并无次序要求,可用三种遍历算法中的一种。
'''先序遍历输出二叉树中的结点'''
void PreOrder(BiTree root)
{
     
	if(root!=NULL)
	{
     
		printf(root->data);
		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}
  1. 输出二叉树中的叶子结点
    算法思想:输出二又树中的叶子结点与输出二叉树中的结点相比,它是一个有条件的输出问题, 即在遍历过程中走到每一个结点时需进行测试,看是否有满足叶子结点的条件。故只需将
printf(root->data);

改为:

if(root->LChild==NULL && root->RChild==NULL)
	printf(root->data);
  1. 统计叶子结点数目
    a.算法思想:统计二叉树中叶子结点数目并无次序要求,只需将访问操作具体变为判断是否为叶子结点及统计操作即可
printf(root->data);

改为:

if(root->LChild==NULL && root->RChild==NULL)
	LeafCount++;

b. 算法思想:采用分治算法,如果是空树,返回0;如果只有一个结点,返回1;否则为左右子树的叶子结点数之和

int leaf(BiTree root)
{
     
	int LeafCount;
	if(root==NULL) LeafCount= 0;
	else if(root->LChild==NULL && root->RChild==NULL) LeafCount=1;
	else{
     
		LeafCount=leaf(root->LChild)+leaf(root->RChild);
	}
	return LeafCount;
}
  1. 建立二叉链表方式存储的二叉树
    给定一棵二叉树,我们可以得到它的遍历序列;反过来,给定一棵二又树的遍历序列,我们也可以创建相应的二叉链表。
    例如,二叉树的“扩展先序遍历序列”为:AB.DF…G…C.E.H…其中用小圆点表示空子树。
    算法思想:采用类似先序遍历的递归算法,首先读入当前根结点,如果是‘ .’ 将当前树根置空,否则申请新结点,存入当前根结点,创建根结点的左子树右子树。
void CreateBiTree(BiTree *bt)
{
     
	char ch;
	ch=getchar();
	if(ch=='.') *bt=NULL;
	else	
		{
     
			*bt=(BiTree)malloc(sizeof(BiTNode));
			(*bt)->data=ch;
			CreateBiTree(&((*bt)->LChild));
			CreateBiTree(&((*bt)->LChild));
		}
}
  1. 求二叉树的高度
    a. 后序遍历求二叉树的高度
    算法思想:二叉树bt高度的递归定义如下:(1)若bt为空,则高度为0;(2)若bt非空,其高度应为其左、右子树高度的最大值加1.
int PostTreeDepth(BiTree bt)
{
     
	int hl,hr,max;
	if(bt==NULL) return 0;
	else{
     
		hl=PostTreeDepth(bt->LChild);
		hr=PostTreeDepth(bt->RChild);
		max=hl>hr?hl:hr;
	}
	return max+1;
}

b .前序遍历求二叉树高度
算法思想:二叉树的高度为二叉树中结点层次的最大值,遍历计算二叉树中的每个结点的层次,其中最大值即为二叉树的高度

void PreTreeDepth(BiTree bt ,int h)
{
     
//先序遍历求二叉树bt高度的递归算法,h为bt指向结点所在层次,初值为1
//depth 为当前求得的最大层次,为全局变量,调用前初值为0
	if(bt!=NULL)
	{
     
		if(depth<h) depth=h;
		PreTreeDepth(bt->LChild,h+1);
		PreTreeDepth(bt->RChild,h+1);
	}
}
  1. 按树状打印二叉树
    假设以二叉链表存储的二叉树中,每个结点所含数据元素为单字母,要求实现二叉树的横向显示问题
    【数据结构】第六章 树与二叉树_第7张图片
    算法思想:这种树形打印为逆中序,先右子树,再根结点,再左子树。算法中设置一个表示当前根结点层深的参数,以控制输出结点的左右位置,每当递归进层时层深参数加1。
void PrintTree(BiTree bt, int nLayer)
{
     
	if(bt!=NULL) 
	{
     
		PrintTree(bt->RChild, nLayer+1);
		for(int i=0;i<nLayer;i++)
			printf(' ');
		printf("%c\n",bt->data);
		PrintTree(bt->LChild, nLayer+1);
	}
}

(3)基于栈的递归消除
在大量复杂的情况下,递归的问题无法直接转换成循环,所以需要采用工作栈消除递归。工作栈提供一种控制结构,当递归算法进层时需要将信息保留;当递归算法出层时需要从栈区退出上层信息。

  1. 中序遍历二叉树的非递归算法
    算法思路:从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作,1.如果当前结点存在,则进栈并遍历左子树,2.否则推栈并访问,然后遍历右子树
'''直接实现栈操作'''
//s[m]表示栈,top表示栈顶指针
void inorder(BiTree root)
{
     
	top=0;
	do{
     
		while(root!=NULL)
		{
     
			if(top>m) return -1;
			top++;
			s[top]=root;
			root=root->LChild;
		}
		if(top!=0)
		{
     
			root=s[top];
			top--;
			visit(root->data);
			root->RChild;
		}
	}while(root!=NULL||top!=0);
}
''' 调用栈操作的函数'''
void InOrder(BiTree root)
{
     
	InitStack(&S);
	p=root;
	while(p!=NULL||!IsEmpty(S))
	{
     
		if(p!=NULL)
		{
     
			Push(&S,p);
			p=p->LChild;
		}
		else{
     
			Pop(&S, &p);
			visit(p->data);
			p=p->RChild;
		}
	}
}

递归算法的时间复杂度:n个结点二叉树,指控的次数为n+1,n为结点个数,故循环次数为n+(n+1)=2n+1,时间复杂度为O(n)
递归算法的空间复杂度:所需栈的空间最多等于二叉树深度K乘以每个结点所需空间数O(k),表面上看递归算法没有使用栈,每调用一次系统内部都有系统运行栈区支持,需要保留本层参数、临时变量与返回地址,因此递归算法比非递归算法占用的空间更多。

  1. 后序遍历二叉树的非递归算法
    访问完左子树右子树后最后访问根结点,判断刚访问过的结点q是不是当前栈顶结点p的有孩子。判别是否应该访问当前栈顶结点p时,有两种情况1.p无有孩子,直接访问根结点,2.p的右孩子是刚被访问过的结点q,访问根结点。
    算法思想:从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:1.从当前结点开始,进栈遍历左子树,直到左子树为空,2.如果栈顶结点右子树为空,或者栈顶结点右子树刚被访问过,则退栈并访问,然后将当前结点指针置空,3.否则遍历右子树。
void PostOrder(BiTree root)
{
     
	BiTNode *p ,*q;
	Stack S;
	InitStack(&S);
	p=root;
	q=NULL;
	while(p!=NULL||!IsEmpty(S))
	{
     
		if(p!=NULL) {
     Push(&S ,P); p=p->LChlid; }
		else{
     
			GetTop(&S ,&p);
			if(p->RChlid=NULL||q==p->RChild)
			{
     
				visit(p->data); 
				q=p;
				Pop(&S, &p);
				p=NULL;
			}
			else p=p->RChild;
		}
	}
	
}
<2>线索二叉树
  1. 建立中序线索树
    算法思想:加线索操作就是访问结点操作,加线索操作需要利用刚访问过结点与当前结点的关系,设置一个指针pre,始终记录刚访问过的结点,操作如下:1.如果当前遍历结点root的左子域为空,则左子域指向pre,2.如果前驱pre的右子域为空,则让右子域指向当前遍历结点root,3.为下一次做准备,当前访问结点root作为下一个访问结点的前驱pre.
void Inthread(BiTree root)
{
     
	if(root!=NULL)
	{
     
		Inthread(root->LChild);
		if(root->LChild==NULL) 
		{
     root->Ltag=1;root->LChild=pre;}
		if(pre!=NULL&&pre->RChild==NULL)
		{
     pre->RChild=root;pre->Rtag=1;}
		pre=root;
		Inthread(root->RChild);
	}
}
  1. 在中序中找结点的前驱、后继
'''  找前驱结点'''
BiTNode * InPre(BiTNode*p)
{
     
	if(p->Ltag==1) pre=p->LChild;
	else{
     
		for(q=p->LChild;q->Rtag==0;q=q->RChild)
		;
		pre=q;
		}
	return (pre);
}
'''  找后继结点'''
BiTNode * InNext(BiTNode*p)
{
     
	if(p->Rtag==1) next=p->RChild;
	else{
     
		for(q=p->RChild;q->Ltag==0;q=q->LChild)
		;
		next=q;
		}
	return (next);
}
  1. 遍历中序线索树
    算法思路:1. 求出某种遍历次序下第一个被访问的结点,2.连续求出刚访问结点的后继结点,直到所有结点均访问完毕。
BiTNode * InFirst(BiTree Bt)
{
     
	BiTNode *p=Bt;
	if(!p) return NULL;
	while(p->Ltag==0) p=p->LChild;
	return p;
}
void TInOrder(BiTree Bt)
{
     
	BiTNode *p;
	p=InFirst(Bt);
	while(p)
	{
     
		visit(p);
		p=InNext(p);
	}
}
2.树、森林和二叉树

孩子兄弟表示法的类型定义:

typedef struct CSNode
{
     
	DataType data;
	Struct CSNode * FirstChild;
	Struct CSNode * Nextsibling;
}CSNode,*CSTree;

树的遍历算法

'''  先序遍历'''
void RootFirst(CSTree root)
{
     
	if(root!=NULL)
	{
     
		visit(root->data);
		p=->root->FirstChild;
		while(p!=NULL)
		{
     
			RootFirst(p);
			p=p->Nextsibling;
		}
	}
}
void RootFirst(CSTree root)
{
     
	if(root!=NULL)
	{
     
		visit(root->data);
		RootFirst(root->FirstChild);
		RootFitst(root->FirstChild);
	}
}

你可能感兴趣的:(笔记)