二叉树非递归遍历(前序、中序、后序
1. 二叉树结点非递归遍历流程分析
结点遍历流程分析(因为前序中序后序遍历流程一样,故而很重要)
- T 指向 【1】
- if 【1】 非空
- then 【1】 入栈 and T 指向 【1】 的左儿子(即 【2】) S:1
- T 指向 【2】
- if 【2】 非空
- then 【2】 入栈 and T 指向 【2】 的左儿子(即【4】) S:1、2
- T 指向 【4】
- if 【4】 非空
- then 【4】 入栈 and T 指向 【4】 的左儿子(没有左儿子,因此为空,即 T 指向空) S:1、2、4
- T 指向空(下一步应该是遍历【4】的右子树)
- 栈 S 出栈元素 【4】,T 指向出栈元素 【4】 的右儿子(因为 【4】 没有右儿子,因此 T 指向空)S:1、2
- T 指向空(下一步应该是遍历【2】的右子树)
- 栈 S 出栈元素 【2】,T 指向出栈元素 【2】 的右儿子(即【5】)S:1
- T 指向【5】
- if 【5】 非空
- then 【5】 入栈 and T 指向 【5】 的左儿子(即 【8】)S:1、5
- T 指向 【8】
- if 【8】 非空
- then 【8】 入栈 and T 指向 【8】 的左儿子(即指向空)S:1、5、8
- T 指向空(下一步应该是遍历【8】的右子树)
- 栈 S 出栈元素 【8】,T 指向出栈元素 【8】 的右儿子(即指向空)S:1、5
- T 指向空(下一步应该是遍历【5】的右子树)
- 栈 S 出栈元素 【5】,T 指向出栈元素 【5】 的右儿子(即指向空)S:1
- T 指向空(下一步应该是遍历 【1】 的右子树)
- 栈 S 出栈元素 【1】,T 指向出栈元素 【1】 的右儿子(即指向【3】) S:NULL
- T 指向 【3】(剩下的就略写了)
==》【3】 入栈,T 指向左儿子【6】 S:3- T 指向 【6】
==》【6】 入栈,T 指向左儿子空 S:3、6- T 指向空
==》【6】 出栈,T 指向右儿子空 S:3- T 指向空
==》【3】 出栈,T 指向右儿子 【7】 S:NULL- T 指向 【7】
==》【7】 入栈,T 指向左儿子空 S:7- T 指向空
==》【7】 出栈,T 指向右儿子 【9】 S:NULL- T 指向 【9】
==》【9】入栈,T 指向左儿子空 S:9- T 指向空
==》【9】出栈,T 指向右儿子空 S:NULL- T 指向空
==》没有元素可以出栈,遍历结束
小结 1
T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
终止条件 =》 T 指向空 且 栈空
小结 2
由于三种遍历方式经过结点的路线相同,因此我们可以通过调整结点访问位置,从而实现三种遍历
上述过程的代码实现
void PreOrderTraversalWithStack(BinTree BT)
{
BinTree T = BT;
Stack S = CreateStack(1000);
while (T || !IsEmpty(S)) // 终止条件 =》 T 指向空 且 栈空
{
while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
{
Push(S, T);
T = T->Left;
}
if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
{
BinTree *v;
Pop(S, v);
T = *v;
T = T->Right;
}
}
}
2. 中序遍历的非递归实现
如上图,我们进行非递归算法实现的思路整理(堆栈实现)
- 手算易得,中序遍历结果为 4、2、8、5、1、6、3、7、9
由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历
- 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,每次出栈的值,刚好就是中序遍历的结果,那么我们的改进方法就是“在结点出栈后加上输出操作”,代码见下,
if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
{
BinTree *v;
Pop(S, v);
T = *v;
printf("%d\t", T->data); // 加入语句
T = T->Right;
}
3. 前序遍历的非递归实现
如上图,我们进行非递归算法实现的思路整理(堆栈实现)
- 手算易得,前序遍历结果为 1、2、4、5、8、3、6、7、9
由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历
- 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,每次入栈的值,刚好就是前序遍历的结果,那么我们的改进方法就是“在结点入栈前or后加上输出操作”,代码见下,
while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
{
// printf("%d\t", T->data); // 加入语句(位置2)
Push(S, T);
printf("%d\t", T->data); // 加入语句
T = T->Left;
}
4. 后序遍历的非递归实现
如上图,我们进行非递归算法实现的思路整理(堆栈实现)
- 手算易得,后序遍历结果为 4、8、5、2、6、9、7、3、1
由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历
- 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,!!!竟然没有对的上的值!!!那该怎么解决呢?
别急,一个相应的解决方式是
如果我们规定根节点的右儿子优先,那么根据我们的分析,前序遍历就成了 “根右左的顺序”,我们可以倒过来看,即“左右根”,这不就是我们想要的后序遍历吗!!因此我们基于右儿子优先进行前序遍历的输出结果进行逆序,那就是我们想要的后序遍历了!!!- 验证一下,基于右儿子优先的前序遍历结果为 “1、3、7、9、6、2、5、8、4” 发现,其逆序和我们计算的 4、8、5、2、6、9、7、3、1 后序遍历结果相同,因此上述思路没错!
代码改进思路
基于右儿子优先实现前序遍历,在前序遍历中,将输出的结果存储到 另一个栈中,则最后这个栈元素出栈顺序,就是前序遍历元素顺序的逆序!!!
void PostOrderTraversalWithStack(BinTree BT)
{
BinTree T = BT;
Stack S = CreateStack(1000);
Stack post = CreateStack(1000);
while (T || !IsEmpty(S)) // 终止条件 =》 T 指向空 且 栈空
{
while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的右儿子
{
Push(S, T);
Push(post, T);
T = T->Right;
}
if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的左儿子
{
BinTree *v;
Pop(S, v);
T = *v;
T = T->Left;
}
}
// 输出 post 元素,就是我们需要的后序遍历的结果(也是二叉树基于右儿子优先实现的前序遍历元素的逆序)
while(!IsEmpty(post)) // post 不为空
{
BinTree *v;
Pop(post, v);
printf("%d\t", (*v)->data);
}
}
5. 小结
- 前序、中序遍历 基于 左儿子优先的结点遍历(见第一段代码)进行修改可以得到
- 后序遍历 基于 右儿子优先的结点遍历下前序遍历结果的逆序得到
6. 代码补全(二叉树数据类型定义、栈数据类型的定义)
/* 二叉树数据类型的定义 */
/* 一个结点有一个数据域及两个指针域,分别指向左右子树 */
typedef int DataType;
typedef struct TreeNode
{ /* 树结点定义 */
DataType Data; /* 结点数据 */
struct TreeNode *Left; /* 指向左子树 */
struct TreeNode *Right; /* 指向右子树 */
} TreeNode;
typedef TreeNode *BinTree; /* 二叉树类型 */
typedef BinTree Position;
/* Stack 数据类型的定义(顺序存储结构) */
// typedef int ElementType; // 堆栈中存储元素的类型
typedef BinTree ElementType; // 堆栈的元素类型是二叉树结点类型的指针
typedef struct
{
ElementType *data; // 使用数组存储堆栈元素,数组中的元素,是一个指向二叉树结点的指针
int top; // 栈顶元素对应在数组中的位置,空栈则 top = -1
int MaxSize; // 堆栈容量
} SqNode; // SqNode 是一种数据类型,即顺序堆栈
typedef SqNode *Stack; // 栈数据类型
/* Stack 操作的声明及定义 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 建栈
Stack CreateStack(int MaxSize)
{
Stack S = (Stack)malloc(sizeof(SqNode)); // 给栈指针分配空间
S->data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
S->top = -1;
S->MaxSize = MaxSize;
return S;
}
// 栈满判断
bool IsFull(Stack S)
{
return S->top + 1 == S->MaxSize;
}
// 栈空判断
bool IsEmpty(Stack S)
{
return S->top == -1;
}
// 出栈操作
bool Pop(Stack S, ElementType *Value) // Value 类型是 *BinTree
{
if (IsEmpty(S))
return false;
*Value = S->data[(S->top)--];
return true;
}
// 入栈操作
bool Push(Stack S, ElementType Value)
{
if (IsFull(S))
return false;
S->data[++(S->top)] = Value;
return true;
}