本次笔记内容:
3.3.1 先序中序后序遍历
3.3.2 中序非递归遍历
3.3.3 层序遍历
3.3.4 遍历应用例子
小白专场:题意理解及二叉树表示
小白专场:程序框架、建树及同构判别
文章目录
- 层序遍历
- 队列实现
- 树的同构
二叉树的三种基本遍历
先序遍历
遍历过程为:
- 访问根节点;
- 先序遍历其左子树;
- 先序遍历其右子树。
void PreOrderTraversal(BinTree BT)
{
if (BT)
{
printf("%d", BT->Data);
PreOrderTraversal(BT->Left) ;
PreOrderTraversal(BT->Right);
}
}
如上,使用递归实现。
效果如上图:A(BDFE)(CGHI)。
中序遍历
遍历过程为:
- 中序遍历其左子树;
- 访问根节点;
- 中序遍历其右子树。
void InOrderTraversal(BinTree BT)
{
if (BT)
{
PreOrderTraversal(BT->Left) ;
printf("%d", BT->Data);
PreOrderTraversal(BT->Right);
}
}
效果如上图。
后序遍历
遍历过程为:
- 后序遍历其左子树;
- 后序遍历其右子树;
- 访问根节点。
void PostOrderTraversal(BinTree BT)
{
if (BT)
{
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
printf("%d", BT->Data);
}
}
效果如上图。
特点
三种遍历过程中路径一样,只是访问各结点的时机不同。
如上图,对于B结点,所谓先序,就是第一次碰到它就print,中序就是第二次碰到就print,后续是第三次碰到才print。
中序非递归遍历(使用堆栈)
中序堆栈遍历
如上图,沿着路径行进,第二次碰到在堆栈中的元素,则抛出堆栈中元素。
- 碰到一个结点就把它压栈,并去遍历它的左子树;
- 当左子树遍历结束后,从栈顶弹出这个结点并访问它;
- 然后按其右指针再去中序遍历该结点的右子树。
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(访问)执行时机即可。
后序遍历也可以用堆栈实现。
层序遍历
二叉树遍历的核心问题:二维结构的线性化。
- 从结点访问其左、右儿子结点;
- 访问左儿子后,右儿子结点怎么办?
- 需要一个存储结构保存暂时不访问的结点;
- 存储结构:堆栈、队列。
队列实现
遍历从根结点开始,首先将根结点入队,然后开始执行循环:结点出队、访问该结点、其左右儿子入队。
根结点入队,然后:
- 从队列中取出一个元素;
- 访问该元素所指结点;
- 若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队。
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()判断是否为叶子结点。
二叉树的高度
如上图,首先应明确左右子树高度,加上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;
}
}
二元运算表达式树及其遍历
如上图,叶结点是运算树;不同遍历方式得到不同缀表达式。中缀表达式会受到运算优先级的影响(不准),其他表达式准。
中缀表达式解决办法:输出左子树时,先出个左括号,输出右子树后,出个右括号。
由两种遍历序列确定二叉树
必须有中序遍历才行!
没有中序的困扰,如
- 先序遍历序列:A B;
- 后续遍历序列:B A。
得到如上图,不能唯一确定二叉树。
先序和中序来确定一棵二叉树分析
- 根据先序遍历序列第一个结点确定根节点;
- 根据根节点在中序遍历序列中分割出左右两个子序列;
- 对左子树和右子树分别递归使用相同的方法继续分解。
如上图,先序、中序遍历结果的结构不同。因此可以根据二者进行二叉树确定。
类似的,后序和中序遍历序列也可以确定一棵二叉树。
树的同构
题意理解
给定两棵树T1和T2,如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。
如上图,上面的两棵树同构,下面的不同构。
输入格式
后面的两个整数代表左右儿子是谁(编号,从0开始)。
如上图,这种输入方式下,二叉树的输入不一定将根节点放在第一个。
二叉树表示
使用结构数组表示二叉树,用静态链表来表示(左右儿子用近似链表的方式表示)。
// 从0开始的索引(结点标号)
// 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;
}
如何建立二叉树
如上图,建立二叉树过程中,先读入结点个数。函数的返回值为树根。
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;
}
判断同构
先考虑特殊情况,如是否都为空?是否一个空一个不为空?再考虑两树左子树根结点是否相同?是的话将左右子树递归;或者另一种情况,左子树根结点与右子树根结点是否相同?是的话左子树和右子树交叉比较递归。
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));
}
实现如上。逻辑需要完整清楚。