1.二叉树的先中后序遍历
1.1.先中后序遍历的基本概念
- 先序遍历:根→左→右:ABDECFG
- 中序遍历:左→根→右:DBEAFCG
- 后序遍历:左→右→根:DEBFGCA
- 可以先按遍历的顺序写出每次递归的子树的根左右结点,然后依次按结点添加下一次递归的根左右结点,直到访问全部结点(逐层展开)
1.先序遍历:根左右→根(根左右)右→ 根(根左右)(根左右)
2.中序遍历:左根右→(左根右)根→(左根右)根(左根右)
3.后序遍历:左右根→(左右根)右根→(左右根)(左右根)根

1.2.先中后序遍历的代码实现
1.2.1.递归
typedef struct BiTNode{
elemType value;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
//先序遍历
void preOrder(BiTree T){
if(T != NULL){
visit(T); //访问根节点
preOrder(T->lchild); //递归遍历左子树
preOrder(T->rchild); //递归遍历右子树
}
}
//中序遍历
void inOrder(BiTree T){
if(T != NULL){
inOrder(T->lchild); //递归遍历左子树
visit(T); //访问根节点
inOrder(T->rchild); //递归遍历右子树
}
}
//后序遍历
void postOrder(BiTree T){
if(T != NULL){
postOrder(T->lchild); //递归遍历左子树
postOrder(T->rchild); //递归遍历右子树
visit(T); //访问根节点
}
}

- #126为第126行代码,#127为第127行代码
- preOrder(A)→访问A→preOrder(A)入栈#126,进入preOrder(B)
- 访问B→preOrder(B)入栈#126,进入preOrder(D)
- 访问D→左子树为空→preOrder(D)入栈#127,进入preOrder(G)
- 访问G→左子树空→右子树空→返回preOrder(D)#127→返回preOrder(B)#126→preOrder(B)#127入栈,进入preOrder(E)
- 访问E→左子树为空→右子树为空→返回preOrder(B)#127→返回preOrder(A)#126→preOrder(A)#127入栈,进入preOrder(C)
- 访问C→preOrder(C)#126入栈,进入preOrder(F)
- 访问F→左子树为空→右子树为空→返回preOrder(C)#126→右子树为空→返回preOrder(A)#127→结束遍历
1.2.2.非递归
后序遍历非递归:栈中剩余元素都是栈顶元素的双亲结点,此特性可求根到结点的路径和两个节点的最近公共结点
#define MAXSIZE 100
typedef struct BiTNode{
struct BiTNode *lchild, *rchild;
Elemtype value;
}BiTNode, *BiTree;
typedef struct Stack{
BiTNode data[MAXSIZE];
int top;
}Stack;
//先序遍历
void PreOrder(BiTree T){
Stack S;
InitStack(S);
BiTNode *p = T;
//当p不存在并且栈空时结束循环
while(p || !(IsEmpty(S)){
if (p) {
//先序遍历先访问p再入队
visit(p);
push(S, p);
p = p->lchild;
}
else{
pop(S, p);
p = p->rchild);
}
}//while
}
//中序遍历
void InOrder(BiTree T){
Stack S;
InitStack(S);
BiTNode *p = T;
while(p || !(IsEmpty(S)){
if (p){
push(S, p);
p = p->lchild;
}
else{
//中序遍历出栈后访问p
pop(S, p);
visit(p);
p = p->rchild;
}
}//while
}
//后序遍历
void PostOrder(BiTree T){
Stack S;
InitStack(S);
BiTNode *p = T, *r = NULL;
while(p || !(IsEmpty(S)){
//p存在,则入栈p,进入p的左子树
if (p){
push(S, p);
p = p->lchild;
}
//p不存在
else{
//查看栈顶元素
GetTop(S, p);
//栈顶元素的右孩子未访问过,则将右孩子入栈,并且进入其左子树
if (p->rchild && p->rchild != r) {
p = p->rchild;
push(S, p);
p = p->lchild;
}
//栈顶元素的右孩子访问过,则出栈并访问栈顶元素,并用r进行标记
//访问后p置空再次进入else循环
else {
pop(S, p);
visit(p);
r = p;
p = NULL;
}
}//else
}//while
}
2.二叉树的层次遍历
2.1.算法思想
- 初始化一个队列
- 将根节点入队
- 若队不空,则队头结点出队,访问该节点,并且将其左右孩子入队
- 重复3直至队列为空
2.2.代码实现
typedef struct BiTNode{
elemType value;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
typedef struct linkNode{
//存指针
BiTNode *data;
struct linkNode *next;
}linkNode;
typedeft struct linkQueue{
linkNode *front, *rear;
}linkQueue;
//层次遍历
void levelOrder(BiTree T){
linkQueue Q;
initQueue(Q); //初始化队列
BiTree p;
enQueue(Q, T); //T入队
while(!isEmpty(Q)){
deQueue(Q, p); //p出队
visit(p); //访问p
if (p->lchild) enQueue(Q, p->lchild); //p的左孩子入队
if (p->rchild) enQueue(Q, p->rchild); //p的右孩子入队
}
}
3.由遍历序列构造二叉树
中序+前序/后序/层序可以确定一个二叉树

3.1.前序+中序确定一个二叉树
- 前序序列:D A E F B C H G I
- 中序序列:E A F D H C B G I
- 前序序列第一个访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
- 前序序列中EAF第一个访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
- 前序序列中HCBGI第一个访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)
- 前序序列中HC第一个访问的是根节点→C是HC的根节点→左(H)根(C)
- 前序序列中GI第一个访问的是根节点→G是GI的根节点→根(G)右(I)
3.2.后序+中序确定一个二叉树
- 后序序列:E F A H C I G B D
- 中序序列:E A F D H C B G I
- 后序序列中最后访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
- 后序序列中EAF最后访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
- 后序序列中HCBGI最后访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)
6.后序序列中HC最后访问的是根节点→C是HC根节点→左(H)根(C)
7.后序序列中GI最后访问的是根节点→G是GI根节点→根(I)右(G)
3.3.层序+中序确定一个二叉树
- 层次序列:D A B E F C G H I
- 中序序列:E A F D H C B G I
- 层次序列中第一个访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
- 层次序列中EAF第一个访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
- 层次序列中HCBGI第一个访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)
- 层次序列中HC第一个访问的是根节点→C是HC的根节点→左(H)根(C)
- 层次序列中GI第一个访问的是根节点→G是GI的根节点→根(G)右(I)
4.1.线索二叉树的概念
二叉树使用链式存储时会有n + 1个空指针未使用,可以将其指向该结点的前驱和后继,使得二叉树具有线性特性,从而方便遍历
规定线索二叉树中,相比传统二叉树,多两个标志位——ltag和rtag。tag用来标记指针指向的是左右孩子还是前驱后继

typedef struct threadNode{
elemType data;
struct threadNode *lchild, *rchild;
int ltag, rtag;
}threadNode, *threarTree;

4.2.中序线索化
typedef struct threadNode{
elemType data;
struct threadNode *lchild, *rchild;
int ltag, rtag;
}threadNode, *threadTree;
//全局变量pre,指向当前当前结点的前驱结点
threadNode *pre = NULL;
void inThread(threadTree &T){
if (T){
inThread(T->lchild);
visit(T);
inThread(T->rchild);
}
}
void visit(threadNode *q){
//如果p节点左孩子不存在,则左孩子指向该节点的前驱结点
if (!q->lchild) {
q->lchild = pre;
q->ltag = 1;
}
//如果pre结点存在并且它的右子树为空
if (pre && !pre.rchild){
pre->rchild = q;
pre->rtag = 1;
}
pre = q;
}
void createInThread(threadTree T){
pre = NULL;
if(T) {
inThread(T);
//如果最后一个结点的右孩子为空,则改标记位
if (pre->rchild == NULL) pre->rtag = 1;
}
}

4.3.先序线索化
需要注意,如果不对ltag进行判断,则会进入死循环
typedef struct threadNode{
struct threadNode *lchild, *rchild;
elemType data;
int ltag, rtag;
}threadNode, *threadTree;
threadNode pre = NULL;
void preThread(threadTree T){
visit(T);
//防止进入死循环
if (ltag == 0) preThread(T->lchild);
preThread(T->rchild);
}
void visit(threadTree *p){
if (p->lchild == NULL) {
p->lchild = pre;
p->ltag = 1;
}
if (pre && pre->rchild == NULL) {
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
void createPreThread(threadTree T){
pre = NULL;
if (T){
preThread(T);
if (pre->rchild == NULL) pre->rtag = 1;
}
}
4.4.后序线索化
typedef struct threadNode{
struct threadNode *lchild, *rchild;
elemType data;
int ltag, rtag;
}threadNode, *threadTree;
threadNode *pre = NULL;
void postThread(threadTree T){
postThread(T->lchild);
postThread(T->rchild);
visit(T);
}
void visit(threadNode *p){
if (p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
if (pre && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
void createPostThread(threadTree T){
pre = NULL;
if (T) {
postThread(T);
if (pre->rchild == NULL) pre->rtag = 1;
}
}
4.5.找前驱/后继
4.5.1.中序线索树找后继
- p->rtag = 1,next = p->rchild
- p->rtag = 0,next = 右子树的最左节点
//找到中序遍历的第一个节点
threadNode *firstNode(threadNode *p){
//找到最左下的结点,并非一定是叶子结点
//有可能是左空右不空
while(p->ltag == 0) p = p->lchild;
return p;
}
//找当前节点的后继结点
threadNode *nextNode(threadNode *p){
//rtag = 0说明有右子树,继续寻找右子树中的最左节点
if (p->rtag == 0) return firstNode(p->rchild);
//rtag = 1说明右子树为指向的是后继结点
else return p->rchild;
}
//中序遍历
void InOrder(ThreadNode *T){
for (threadNode *p = firstNode(T); p != NULL; p = nextNode(p)) visit(p);
}
4.5.2.中序线索树找前驱
- p->ltag = 1,pre = p->lchild
- p->ltag = 0,pre = 左子树的最右结点
//找到最后一个结点
ThreadNode *LastNode(ThreadNode *p){
while(p->rtag == 0) p = p->rchild;
return p;
}
//找前驱结点
ThreadNode *PreNode(ThreadNode *p){
if (p->rtag == 0) return LastNode(p->lchild);
else return p->rchild;
}
//中序遍历
void InOrder(ThreadTree T){
for (ThreadNode *p = LastNode(T); p != NULL; p = preNode(p)) visit(p);
}
4.5.3.先序搜索树找后继
- p->ltag = 1,next = p->rchild
- p->ltag = 0
- 左子树不空,next = 左孩子
- 左子树空,右子树不空,next = 右孩子
4.5.4.先序搜索树找前驱
- p->ltag = 1,pre = p->lchild
- p->ltag = 0
- 如果没有parent指针,则无法找到前驱
- 有parent指针:
1.p是左孩子:pre = p->parent
2.p是右孩子,但左兄弟为空:pre = p->parent
3.p是右孩子,且有左兄弟:pre = 左兄弟子树的最后一个遍历的结点
4.p是根节点:pre = NULL
4.5.5.后序搜索树找后继
- p->rtag = 1,next = p->rchild
- p->rtag = 0
- 如果没有parent指针,则无法找到后继
- 有parent指针:
1.p是右孩子:next = p->parent
2.p是左孩子,且没有右兄弟:next = p->parent
3.p是左孩子,且有右兄弟:next = 右兄弟子树中第一个被后序遍历的结点
4.p是根节点,p没有后继
4.5.6.后序搜索树找前驱
- p->ltag = 1,pre = p->lchild
- p->ltag = 0,p必有左子树
- p有右子树,pre = p->rchild
- p没有右子树,pre = p->lchild