非递归的具体代码实现参见: 👉这里
先序
先序压栈:先输出,再压左子树,后pop左子树,pop根,压右子树。
输出 | m | A | C | n | NULL | B | D | NULL | E | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
栈 | m | A m | C A m | A m | m | n m | m | NULL | B | D B | B | NULL | E | end |
对于任何一个子树来说:先压根,再压左子树,先弹出所有左子树,再弹出根,栈空,最后压右子树,弹出所有右子树(其中栈会有几次空)。
pop | 左子树 | 根 | 右子树 | |||
---|---|---|---|---|---|---|
stack | 根 | 左子树 根 | 根 | 右子树 |
与中序的不同在于,其在压入前输出,实现了与压入顺序相同的输出顺序LNR。
中序
中序:先压左子树,先pop后输出,再压右子
输出 | C | A | n | m | D | B | E | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
栈 | C A m | A m | m | n m | m | NULL | D B | B | NULL | E | end |
对于任何一个子树来说:先压根再压左子树,先弹出所有左子树,再弹出根,栈空,最后压右子树,弹所有右子树(其中栈会有几次空)。
pop | 左子树 | 根 | 右子树 | |||
---|---|---|---|---|---|---|
stack | 根 | 左子树 根 | 根 | 右子树 |
与先序的不同在于,其在pop后输出,实现了与压入顺序相反的输出顺序NLR
后序
输出 | C | n | A | D | E | B | m | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
栈 | C A m | A m | n A m | A m | m | D B m | B m | E B m | B m | m | end |
后序的栈中,任何一个时刻,都包含了栈顶及其到根的所有祖先!
从根压到叶,再从叶pop到根。每次左pop了,就去压右,即总是左子出栈再压右子树。
pop | 左子树 | 右子树 | 根 | |||
---|---|---|---|---|---|---|
stack | 根 | 左子树 根 | 根 | 右子树 根 | 根 |
根据上面分析:
- 压栈顺序相同:根、左子树、右子树。
- 出栈顺序不同:先/中:左子树,根,右子树。
\qquad\qquad\qquad 后: \quad 左子树,右子树,根。 - 兄弟结点不会同时在栈中,因为总是左子树pop了,再push右子树。
- 后序的栈中,任何时候都包含了栈顶结点到根的所有祖先(即栈顶到根的路径)
- 先/中 压栈、出栈方式相同,先:先输出后push,中:先pop后输出。
- 先/中都会在左子树与根全出栈后,出现栈空的情况!而后序在处理 最后一个结点(根)之前都不会空栈。
- 承上,
while(p||!isEmpty(S))
为什么需要p ?
(1)S一开始是空的(根纳入while里面处理了)
(2) 对于先/中都会在之后再次出现若干次S空的状态。
附:层序
层序使用队列,逻辑清晰简单,层序可以用来实现很多东西,比如求树高,树宽,某层结点数,每层结点数,是否完全二叉树…
front rear | 头 ↔ \leftrightarrow ↔尾 | 输出 | 层数 |
---|---|---|---|
T # 0 1 | m | 第一层 | |
T A B # 1 3 | A、B | m | 第二层 |
T A B C D # 2 5 | B、C、n | A | |
T A B C D E F# 3 7 | C、n、D、E | B | 第三层 |
T A B C D E F# 4 7 | n、D、E | C | |
T A B C D E F# 5 7 | D、E | n | |
T A B C D E F # 6 7 | E | D | |
E |
我们可以发现,若将结点编号,根为1,每次front==最右边结点时,说明层数加一。
以此可以计算总层数。