遍历是二叉树的一类重要操作,也是二叉树的其它操作和应用的算法基本框架
typedef struct BiTNode{
int data; // 数据域
struct BiTNode *lchild, *rchild; // 左右孩子
}BiTNode, BiTree;
typedef struct BiTNode{
int data; // 数据域
struct BiTNode *lchild, *rchild, *parent; // 左右孩子 ,及双亲
}BiTNode, BiTree;
遍历方式可分为:深度优先遍历,广度优先遍历。
而深度优先遍历又可分为:先序遍历、中序遍历、后序遍历。其中又可区分递归遍历跟非递归遍历。
如果L、D、R表示左子树、根、右子树,那么3种算法可表示(都是争对跟结点D而言的):
DLR:先序
LDR:中序
LRD:后序
这里只展示中序递归遍历:
// 中序递归遍历
void InOrderTraverse(BiTree T,Status (*visit)(int elem)){
if( T == NULL ) return ;
InOrderTraverse(T->lchild,visit);
visit(T->data);
InOrderTraverse(T->rchild,visit);
}
这里又可分为使用栈和不使用栈的情况
// 从T结点出发,沿左分支不断深入,直到左分支为空的结点,沿途结点入栈S
BiTNode *GoFastLeft(BiTree T,LStack &S){
if( NULL == T ) return NULL;
while( T->lchild != NULL ){
Push(S,T);
T = T->lchild;
}
return T;
}
void InOrderTraverse(BiTree T, Status (*visit)(int elel)){
LStack s; InitStack(s);
BiTree p;
p = GoFastLeft(T,s); // 找到最左下的结点
while(p!=NULL){
visit(p->data);
if( p->rchild != NULL ){ // 令p指向其右孩子为根的子树的最左下结点
p = GoFastLeft(p->rchild,s);
}else if( !StackEmpty(s) ){
Pop(s,p);
}else{
p = NULL;
}
}
}
// 从T结点出发,沿左分支不断深入,直到左分支为空的结点,沿途结点入栈S
BiTNode *GoFastLeft(BiTree T,LStack &S,Status (*visit)(int elem)){
if( NULL == T ) return NULL;
while( T->lchild != NULL ){
visit(T->data); // ...
Push(S,T);
T = T->lchild;
}
visit(T->data);// ...
return T;
}
void InOrderTraverse(BiTree T, Status (*visit)(int elem)){
LStack s; InitStack(s);
BiTree p;
p = GoFastLeft(T,s); // 找到最左下的结点
while(p!=NULL){
if( p->rchild != NULL ){ // 令p指向其右孩子为根的子树的最左下结点
p = GoFastLeft(p->rchild,s,visit);
}else if( !StackEmpty(s) ){
Pop(s,p);
}else{
p = NULL;
}
}
}
二叉树的非递归后序遍历算法:
前序、中序、后序的非递归遍历中,要数后序最为麻烦,如果只在栈中保留指向结点的指针,那是不够的,必须有一些额外的信息存放在栈中。方法有很多,这里只举一种,先定义栈结点的数据结构。
typedef struct{
Node * p;
int rvisited;
}SNode //Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过。
lastOrderTraverse(BiTree bt){
//首先,从根节点开始,往左下方走,一直走到头,将路径上的每一个结点入栈。
p = bt;
while(bt){
push(bt, 0); //push到栈中两个信息,一是结点指针,一是其右结点是否被访问过
bt = bt.lchild;
}
//然后进入循环体
while(!Stack.empty()){ //只要栈非空
sn = Stack.getTop(); // sn是栈顶结点
//注意,任意一个结点N,只要他有左孩子,则在N入栈之后,N的左孩子必然也跟着入栈了(这个体现在算法的后半部分),所以当我们拿到栈顶元素的时候,可以确信这个元素要么没有左孩子,要么其左孩子已经被访问过,所以此时我们就不关心它的左孩子了,我们只关心其右孩子。
//若其右孩子已经被访问过,或是该元素没有右孩子,则由后序遍历的定义,此时可以visit这个结点了。
if(!sn.p.rchild || sn.rvisited){
p = pop();
visit(p);
}
else //若它的右孩子存在且rvisited为0,说明以前还没有动过它的右孩子,于是就去处理一下其右孩子。
{
//此时我们要从其右孩子结点开始一直往左下方走,直至走到尽头,将这条路径上的所有结点都入栈。
//当然,入栈之前要先将该结点的rvisited设成1,因为其右孩子的入栈意味着它的右孩子必将先于它被访问(这很好理解,因为我们总是从栈顶取出元素来进行visit)。由此可知,下一次该元素再处于栈顶时,其右孩子必然已被visit过了,所以此处可以将rvisited设置为1。
sn.rvisited = 1;
//往左下方走到尽头,将路径上所有元素入栈
p = sn.p.rchild;
while(p != 0){
push(p, 0);
p = p.lchild;
}
}//这一轮循环已结束,刚刚入栈的那些结点我们不必管它了,下一轮循环会将这些结点照顾的很好。
}
}
而我自己写的算法则是不断访问左边子树后将其切断,虽然能实现,但是不是很好,这里就不展示。
void PreOrderTraverse(TriTree T, Status (*visit)(int elem)){
TriTree p, pr;
if( T == NULl ) return ;
p = T;
while(p!=NULL){
visit(p->data);
if(p->lchild != NULL){
p = p->lchild;
}else if( p->rchild != NULL ){
p = p->rchild;
}else{
// ★★往回查找,找到第一个有右孩子的p结点,并且该右子树没被访问过(由pr标记),找不到程序结束
while(p!=NULL && (p->rchild==pr||p->rchild==NULL)){
pr = p;
p = p->parent;
}
if( p!=NULL ) p = p->rchild;
}
}
}
TriTree GoFastLeft(TriTree T){
if( T == NULL ) return NULL;
while(T->lchild != NULL ){
T = T->lchild;
}
return T;
}
void InOrder(TriTree PT, void (*visit)(TElemType)){
TriTree t;
if( PT == NULL ) return ;
t = GoFastLeft(PT);
while(t != NULL){
visit(t->data);
if( t->rchild != NULL){
t = GoFastLeft(t->rchild);
}else{
// 右元素为NULL,返回上一层
if( t->parent != NULL ){
// 如果是其双亲的左孩子,说明双亲还没visit操作
if( t->parent->lchild == t ){
t = t->parent;
}else{
// 如果是其双亲的右孩子,说明双亲已经被visit操作,继续向上
while( t->parent != NULL && t->parent->rchild == t ){
t = t->parent;
}
if( t->parent == NULL ){
t = NULL;
}else{
t = t->parent;
}
}
}else{
t = NULL;
}
}
}
}
typedef struct TriTNode {
TElemType data;
struct TriTNode *lchild, *rchild, *parent;
int mark; // 标志域(在三叉链表的结点中增设一个标志域
// (mark取值0,1或2)以区分在遍历过程中到达该结点
// 时应继续向左或向右或访问该结点。)
} TriTNode, *TriTree;
void PostOrder(TriTree T, void (*visit)(TElemType)){
TriTree t;
if( T == NULL ) return ;
t = T;
while( t!= NULL ){
if( t->mark == 0 ){ // 向左(左边是否为空)
if( t->lchild == NULL && t->rchild != NULL ){ // 左边为空,直接标记为1,并访问操作,
t->mark = 1; // 表示待visit操作,并向上
t = t->rchild;
}else if( t->lchild == NULL && t->rchild == NULL ){ // 左右为空,直接访问并向上
t->mark = 1;
visit(t->data);
t = t->parent;
}else{
t->mark = 2; // 进入左边,标记表示待向右
t = t->lchild;
}
} else if( t->mark == 2 ){ // 向右操作 (可能为空)
if( t->rchild == NULL ){
t->mark = 1; // 表示待visit操作,并向上
visit(t->data);
t = t->parent;
}else{
t->mark = 1;
t = t->rchild;
}
} else if( t->mark == 1 ){ //visit操作,并向上
visit(t->data);
t = t->parent;
}
}
}
void LevelOrderTraverse(BiTree T, Status (*visit)(int elem)){
if( T == NULL ) return ;
LQueue Q; InitQueue(Q);
BiTree p = T;
visit(T->data);
EnQueue(Q,p);
while( !QueueEmpty(Q) ){
DeQueue(Q,p);
if(p->lchild != NULL){ // 处理左孩子
visit(T->lchild->data);
EnQueue(Q,p->lchild);
}
if(p->rchild != NULL){ // 处理右孩子
visit(T->rchild->data);
EnQueue(Q,p->rchild);
}
}
}
最后,如果有哪里错误或者不足,希望能够指出,谢谢!