一,先序创建节点
- 首先,按先序遍历创建节点:先序遍历列表如下,( ϕ \phi ϕ 代表空格)
a b c ϕ \phi ϕ ϕ \phi ϕ d e ϕ \phi ϕ g ϕ \phi ϕ ϕ \phi ϕ f ϕ \phi ϕ ϕ \phi ϕ |
---|
-
其次由先序序列,对应的二叉树状图如下,
-
注意点:
1,scanf 的时候要注意冲洗缓冲区
2,递归建立节点 -
代码
bool CreateBiTree(BiTNode *(&T)){ // 按先序次序输入二叉树的节点的值,空格字符表示空树
elemType2 ch = 'c';
printf("输入元素\n");
scanf("%c", &ch);
fflush(stdin);
if(ch == ' '){
T = NULL;
}
else{
T = (BiTNode *)malloc(sizeof(BiTNode));
if(!T){
return false;
}
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return true;
}
二,递归遍历(根据根的先后分为 CLR, LCR, LRC)
访问函数
- 进行遍历前,先设置访问函数
- 代码
bool Visit(elemType2 e){
printf("%c\t", e);
return true;
}
先序遍历(CLR)
- 代码
// 先序
bool PreOrderTraverse(BiTNode *T, bool (*v)(elemType2)){ // 使用回调函数还挺有意思的
if(T != NULL){
v(T->data);
PreOrderTraverse(T->lchild, v);
PreOrderTraverse(T->rchild, v);
}
return true;
}
中序遍历(LCR)
- 代码
bool InOrderTraverse(BiTNode *T, bool (*v)(elemType2)){ // 中序
if(T != NULL){
InOrderTraverse(T->lchild, v);
v(T->data);
InOrderTraverse(T->rchild, v);
}
return true;
}
后序遍历(LRC)
- 代码
bool PostOrderTraverse(BiTNode *T, bool (*v)(elemType2)){ // 后序
if(T != NULL){
PostOrderTraverse(T->lchild, v);
PostOrderTraverse(T->rchild, v);
v(T->data);
}
return true;
}
三,非递归遍历
进行非递归遍历要用到自己的栈
先序遍历(CLR)
- 首先根节点入栈,然后沿着一个方向入栈,不妨设一直沿左孩子入栈,当遇到虚拟节点为空时候,停止入栈,准备出栈操作。即入栈顺序为 A->B->C(注意虚拟节点是否入栈,决定了不同的算法,本文中假设虚拟节点不入栈)
- 由 根->左->右 的访问次序,来进行访问,即入栈时候,就可以当成访问了 根,
此时,访问顺序为 A->B->C - 虚拟节点不入栈,那么,退回到上次入栈的元素,即 C,此时 C 因为已经访问,可以出栈,并将其右孩子入栈,这里要理解的是,右孩子也应该当成根处理,入栈时候即可访问它,然后沿着右孩子的左孩子方向入栈,依次类推。
- 代码:(结合代码很容易理解)
bool PreOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){ // 非递归前序遍历,要用到自己的栈
// 每次都是当成根节点访问的
// 虚拟节点
BiTNode *p=NULL;
SqStack S;
p = T;
InitStack(S);
//printf("%p\n", p);
// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
while(p || !(S.top == S.base)){
// 左边一直入栈
if(p){
v(p->data);
Push(S, p);
p = p->lchild;
}
else{ // 访问栈顶节点,并出栈,继续压入栈
Pop(S, p);
p = p->rchild;
}
}
return true;
}
中序遍历
中序遍历不同的是,由于要优先访问左孩子,所以沿根的左孩子入栈时并未访问它。
而弹栈的时候,即上面的 C 弹栈,C 是 B 的左孩子,所以这时候要访问 C。总结为,弹的是上个节点的左孩子,所以弹的时候再访问,同时这里要注意弹的也是本节点的根。要站在上一个节点的角度看问题。
- 代码:(结合代码很容易理解)
bool InOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){ // 非递归中序遍历,要用到自己的栈
// 每次都是当成根节点访问的
// 虚拟节点
BiTNode *p=NULL;
SqStack S;
p = T;
InitStack(S);
//printf("%p\n", p);
// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
while(p || !(S.top == S.base)){
// 左边一直入栈
if(p){
Push(S, p);
p = p->lchild;
}
else{ // 访问栈顶节点,并出栈,继续压入栈
Pop(S, p);
v(p->data);
p = p->rchild; // 继续访问右边
}
}
return true;
}
后序遍历
后序遍历稍微复杂一些,要记录每个节点的访问次数。
- 依然从根开始,沿着一个方向访问,本文以左孩子方向访问。
- 从上一个节点来看,左孩子遍历不能退栈,右孩子遍历了才可以退栈。
- 第一次左孩子入栈的时候坐上标记,表示第一次访问。当左孩子由于访问到了虚拟节点,要退回来时,即退到根节点,第二次访问做上标记;接着再去访问右孩子,右边完毕会退到根节点,第三次访问根节点时,根节点再弹出栈(注意这里弹栈后根节点要为空)。引用如下段落可以说明更清楚操作过程:
- 探讨一下为什么要进行标记?因为左边要退回,不能访问左边,右边也要退回,不能访问右边,只有根节点弹栈的时候,根节点要访问。而要弹栈的根节点先后三次访问,很难区分是是否再右边退回的时候进行的访问,为了简便区分,于是做上了标记。如果不做标记也是可以的,谨记,要做到只有当根节点的右节点访问了,才能弹出栈,这里还是要判断右节点到底访问没有。
- 代码
bool PostOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){ // 非递归后序遍历,要用到自己的栈
// 每次都是当成根节点访问的
// 虚拟节点
BiTNode *p=NULL;
BiTNode *topTemp=NULL;
SqStack S;
p = T;
InitStack(S);
//printf("%p\n", p);
// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
while(p || !(S.top == S.base)){
// 左边一直入栈
if(p){
Push(S, p);
p->flag = 1;
p = p->lchild;
}
else{ // 进来的时候 p 为空,pop 后 p 非空
GetTop(S, topTemp);
if(topTemp->flag == 1){ // 左边退栈
topTemp->flag = 2;
p = topTemp->rchild;
}
else{ // 右边退栈
Pop(S, p);
v(p->data);
p = NULL; // 此时无需访问左右
}
}
}
return true;
}
补充,层序遍历
- 要利用队列来实现层序遍历,如下入队顺序是 A->B->C->D->E->F->G,
- 即根入队,实现第一层入队,然后第一层出队,然后如果根有左孩子,那么左孩子入队;接着如果有右孩子那么右孩子入队。
- 队列头部出队,左孩子来到头部,再进行把左孩子当成根,回到第 2 步。
- 其实根据上面的步骤,自然而然原来的对头出队,新的元素每到队头,又从队尾增加队列元素,那么就实现了层序遍历(有点儿神奇)
- 代码
bool LevelOrderTraverse(BiTNode *T, bool (*v)(elemType2)){
LinkQueue lkT;
InitQueue_Link(lkT);
EnQueue_Link(lkT, T); // 根节点先入队
while(lkT.front != lkT.rear){
if(T->lchild != NULL){ // 如果有左孩子,那么左孩子入队;
EnQueue_Link(lkT, T->lchild);
}
if(T->rchild != NULL){ // 如果有右孩子,那么右孩子入队;
EnQueue_Link(lkT, T->rchild);
}
DeQueue_Link(lkT, T); // 根节点出队,那么头部就是左孩子,相当于第二层开始了
v(T->data);
GetHead(lkT, T); // 更新了 T
}
return true;
}