本次笔记内容:

3.3.1 先序中序后序遍历

3.3.2 中序非递归遍历

3.3.3 层序遍历

3.3.4 遍历应用例子

小白专场:题意理解及二叉树表示

小白专场:程序框架、建树及同构判别


文章目录


二叉树的三种基本遍历

先序遍历

遍历过程为:

  1. 访问根节点;
  2. 先序遍历其左子树;
  3. 先序遍历其右子树。
void PreOrderTraversal(BinTree BT)
{
if (BT)
{
printf("%d", BT->Data);
PreOrderTraversal(BT->Left) ;
PreOrderTraversal(BT->Right);
}
}

如上,使用递归实现。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_层序遍历

效果如上图:A(BDFE)(CGHI)。

中序遍历

遍历过程为:

  1. 中序遍历其左子树;
  2. 访问根节点;
  3. 中序遍历其右子树。
void InOrderTraversal(BinTree BT)
{
if (BT)
{
PreOrderTraversal(BT->Left) ;
printf("%d", BT->Data);
PreOrderTraversal(BT->Right);
}
}

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_非递归遍历算法_02

效果如上图。

后序遍历

遍历过程为:

  1. 后序遍历其左子树;
  2. 后序遍历其右子树;
  3. 访问根节点。
void PostOrderTraversal(BinTree BT)
{
if (BT)
{
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
printf("%d", BT->Data);
}
}

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_非递归遍历算法_03

效果如上图。

特点

三种遍历过程中路径一样,只是访问各结点的时机不同。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_树的同构_04

如上图,对于B结点,所谓先序,就是第一次碰到它就print,中序就是第二次碰到就print,后续是第三次碰到才print。

中序非递归遍历(使用堆栈)

中序堆栈遍历

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_先序遍历_05

如上图,沿着路径行进,第二次碰到在堆栈中的元素,则抛出堆栈中元素。

  • 碰到一个结点就把它压栈,并去遍历它的左子树;
  • 当左子树遍历结束后,从栈顶弹出这个结点并访问它;
  • 然后按其右指针再去中序遍历该结点的右子树。
void InOrderTraversal(BinTree BT)
{
BinTree T = BT;
Stack S = CreateStack(MaxSize); /* 创建并初始化堆栈S */
while (T || !IsEmpty(S))
{
while (T) /* 一直向左并将沿途节点压入堆栈 */
{
Push(S, T);
T = T->Left;
}
if (!IsEmpty(S))
{
T = Pop(S); /* 结点弹出堆栈 */
printf("%5d", T->Data); /* (访问)打印结点 */
T = T->Right; /* 转向右子树 */
}
}
}

先序也能非递归遍历

void PreOrderTraversal(BinTree BT)
{
BinTree T = BT;
Stack S = CreateStack(MaxSize); /* 创建并初始化堆栈S */
while (T || !IsEmpty(S))
{
while (T) /* 一直向左并将沿途节点压入堆栈 */
{
printf("%5d", T->Data); /* (访问)打印结点 */
Push(S, T);
T = T->Left;
}
if (!IsEmpty(S))
{
T = Pop(S); /* 结点弹出堆栈 */
T = T->Right; /* 转向右子树 */
}
}
}

如上,改变printf(访问)执行时机即可。

后序遍历也可以用堆栈实现。

层序遍历

二叉树遍历的核心问题:二维结构的线性化。

  • 从结点访问其左、右儿子结点;
  • 访问左儿子后,右儿子结点怎么办?
  • 需要一个存储结构保存暂时不访问的结点;
  • 存储结构:堆栈、队列。

队列实现

遍历从根结点开始,首先将根结点入队,然后开始执行循环:结点出队、访问该结点、其左右儿子入队。

根结点入队,然后:

  1. 从队列中取出一个元素;
  2. 访问该元素所指结点;
  3. 若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队。
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
if (!BT)
return;
Q = CreateQueue(MaxSize);
AddQ(Q, BT);
while (!IsEmptyQ(Q))
{
T = DeleteQ(Q);
printf("%d\n", T->Data);
if (T->Left)
AddQ(Q, T->Left);
if (T->Right)
AddQ(Q, T->Right);
}
}

遍历二叉树的应用

输出二叉树中的叶子结点

void PreOrderPrintLeaves(BinTree BT)
{
if (BT)
{
if (!BT->Left && !BT->Right)
printf("%d", BT->Data);
PreOrderPrintLeaves(BT->Left);
PreOrderPrintLeaves(BT->Right);
}
}

如上,在printf()之前加上一个if()判断是否为叶子结点。

二叉树的高度

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_二叉树_06

如上图,首先应明确左右子树高度,加上1,为树高度这条结论。

int PostOrderGetHeight(BinTree BT)
{
int HL, HR, MaxH;
if (BT)
{
HL = PostOrderGetHeight(BT->Left);
HR = PostOrderGetHeight(BT->Right);
MaxH = (HL > HR) ? HL : HR;
return (MaxH + 1);
}
else
{
return 0;
}
}

二元运算表达式树及其遍历

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_树的同构_07

如上图,叶结点是运算树;不同遍历方式得到不同缀表达式。​中缀表达式会受到运算优先级的影响(不准),其他表达式准。

中缀表达式解决办法:输出左子树时,先出个左括号,输出右子树后,出个右括号。

由两种遍历序列确定二叉树

必须有中序遍历才行!

没有中序的困扰,如

  • 先序遍历序列:A B;
  • 后续遍历序列:B A。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_二叉树_08

得到如上图,不能唯一确定二叉树。

先序和中序来确定一棵二叉树分析
  • 根据先序遍历序列第一个结点确定根节点;
  • 根据根节点在中序遍历序列中分割出左右两个子序列;
  • 对左子树和右子树分别递归使用相同的方法继续分解。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_非递归遍历算法_09

如上图,先序、中序遍历结果的结构不同。因此可以根据二者进行二叉树确定。

类似的,后序和中序遍历序列也可以确定一棵二叉树。

树的同构

题意理解

给定两棵树T1和T2,如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_树的同构_10

如上图,上面的两棵树同构,下面的不同构。

输入格式

后面的两个整数代表左右儿子是谁(编号,从0开始)。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_二叉树_11

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_二叉树_12

如上图,这种输入方式下,二叉树的输入不一定将根节点放在第一个。

二叉树表示

使用结构数组表示二叉树,用静态链表来表示(左右儿子用近似链表的方式表示)。

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_先序遍历_13

#define MaxTree 10
#define ElementType char
#define Tree int // 从0开始的索引(结点标号)
#define Null -1 // Null在std.io中定义值为0,

struct TreeNode
{
ElementType Element;
Tree Left;
Tree Right;
} T1[MaxTree], T2[MaxTree];

程序框架搭建

int main() {
建二叉树1
建二叉树2
判别是否同构并输出

return 0;
}

需要设计的函数:

  • 读取数据建二叉树;
  • 二叉树的同构判别。
int main() {
Tree R1, R2;

R1 = BuildTree(T1);
R2 = BuildTree(T2);
if (Isomorphic(R1, R2)) printf("Yes\n");
else printf("No\n");

return 0;
}

如何建立二叉树

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_先序遍历_14

如上图,建立二叉树过程中,先读入结点个数。函数的返回值为树根。

Tree BuildTree(struct TreeNode T[])
{
scanf("%d\n", N);
if (N)
{
for (i = 0; i < N; i++)
check[i] = 0;
for (i = 0; i < N; i++)
{
scanf("%c %c %c\n", &T[i].Element, &cl, &cr);
if (cl != '-')
{
T[i].Left = cl - '0'; // 字符串转换为int
check[T[i].Left] = 1;
}
else
T[i].Left = Null;
if (cr != '-')
{
T[i].Right = cr - '0';
check[T[i].Right] = 1;
}
else
T[i].Right = Null;
}
for (i = 0; i < N; i++)
if (!check[i])
break;
Root = i;
}
return Root;
}

判断同构

【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构_层序遍历_15

先考虑特殊情况,如是否都为空?是否一个空一个不为空?再考虑两树左子树根结点是否相同?是的话将左右子树递归;或者另一种情况,左子树根结点与右子树根结点是否相同?是的话左子树和右子树交叉比较递归。

int Isomorphic(Tree R1, Tree R2)
{
if ((R1 == Null) && (R2 == Null))
/* both empty */ retrun 1;
if (((R1 == Null) && (R2 != Null)) || (R1 != Null) && (R2 == Null))
/* one of them is empty */ return 0;
if (T1[R1].Element != T2[R2].Element)
/* roots are different */ return 0;
if ((T1[R1].Left == Null) && (T2[R2].Left == Null))
/* both have no left subtree */
return Isomorphic(T1[R1].Right, T2[R2].Right);
if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) &&
((T1[T1[R1].Left].Element) == (T2[T2[R2].Left].Element)))
/* no need to swap the left and the right */
return (Isomorphic(T1[R1].Left, T2[R2].Left) &&
Isomorphic(T1[R1].Right, T2[R2].Right));
else /* need to swap the left and the right */
return (Isomorphic(T1[R1].Left, T2[R2].Right) &&
Isomorphic(T1[R1].Right, T2[R2].Left));
}

实现如上。逻辑需要完整清楚。