| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 3490 人关注过本帖
标题:无空间复杂度(无栈)的非递归二叉树中序遍历
只看楼主 加入收藏
stnlcd
Rank: 1
等 级:新手上路
帖 子:177
专家分:1
注 册:2004-11-21
收藏
 问题点数:0 回复次数:3 
无空间复杂度(无栈)的非递归二叉树中序遍历

常见的二叉树非递归算法都是用栈保存访问路径上的结点,这样使空间复杂为o(n),其中n为树最大深度。近日偶然翻书发现一空间复杂度为o(1)的算法,与大家分享。这个算法并没有以牺牲时间复杂度为代价,它只是巧妙的运用叶子结点左右孩子指针为空这一事实,将所有的叶子组成一链栈用于保存回退信息,其中叶子结点的lchild域相当于链表的data域,rchild相当于链表的next域,是一种“废物利用”的思想,本质上还是用了栈,只是没用分配栈空间而已。该算法的设计十分巧妙,需要仔细理解才会明白。
事实上,像二叉树这种递归定义的非线性结构,在设计相应的非递归程序时最难考虑的是从当前结点向上回退的情况,向下走的情况很简单:先向左走,左面走不下去了就向右。回退的时候有两个要点:1,找到其父结点的指针以便回退。2,判断出它是父结点的左孩子还是右孩子。常见的用栈保存信息的算法,栈中保存了从根结点到当前结点的路径信息,当然可以正确的回退。
该算法的大概思想是:p作为当前结点,q作为它的父结点(满足要点1),如此深入下去,如果p是q的左孩子,就把q的左指针指向q的父亲,这样q作指针所指向的结点就不在是p了,而是p的爷爷结点了,这样的做法是为了在回退时找到父结点。为了满足要点2:判断回退时它是父结点的左孩子还是右孩子,有三种情况要考虑:1,回退时如果q的lchild为空,表示q只有右子树p,这样就判断出来了p只可能是q的右孩子,剩下的问题就是回退和恢复q的lchild了。2,如果q的rchild为空,这个情况和1相似......3,如果q的lchild和rchild都不为空,这样回退时候就比较难判断p到底是q的左还是右儿子了?该算法的解决方法为:用av保存当前叶子结点以作为为栈分配的元素,这个栈用于记录遇到的双子树结点。用lr表示当前遇到的双子树结点,也就是p从q的lchild域回退时才判断出来q有两个子树(怎知道是lchild回退?这是程序技巧问题,看程序!),这时就把q记录在lr内,这时lr并不急于进av栈,只有当lr的右子树中还有双子树结点时,lr才进栈!如果lr的右子树中没有双子树结点,那么当p回退的时候如果碰到了lr就表示p为q的右儿子,具体算法还要看程序说明,我很难说的清楚。至于作为栈的叶子结点是完全够用的,这是由中序遍历的性质决定的,因为只有访问了一个叶子结点才有可能访问一个双子树结点。有兴趣的话,大家仔细思考吧。祝大家学习愉快!


/*程序默认的先序建树序列为:“abdg e c f ”*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char tree[30]; /*tree数组为先序建树序列*/
int ti=0;

typedef struct BiTNode {
char data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

/*先序建树函数!*/
void CreateBiTree(BiTree *T) {
char ch;
ch=tree[ti++];
if(!ch) return;
if(ch==' ') *T=NULL;
else {
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data=ch;
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
/*非递归无栈二叉树中序遍历算法*/
void InOrder2(BiTree T) {
BiTree top=NULL,lr=NULL,r,av,
p=T,q=T;
/*top为栈顶指针,lr记录当前2子树结点,r为临时变量,
av为当前可用叶子结点,p为当前访问结点,q为p的父亲结点*/
if(T==NULL) return ;
while(1) { /*向下访问搜索*/
while(1) {
if(p->lchild==NULL&&p->rchild==NULL) {/*如果访问到底,就输出该结点*/
printf("%c ",p->data);
break;
}
else if(p->lchild==NULL) {/*沿左子树访问到底,转向访问右子树*/
printf("%c ",p->data);
r=p->rchild;
p->rchild=q;
q=p; p=r;
}
else {/*一直沿左子树访问下去*/
r=p->lchild;
p->lchild=q;
q=p; p=r;
}
}
av=p;/*p为叶子结点,av记录当前叶子结点*/
while(1) {
if(p==T) return;/*如果回退到根,返回*/
else if(q->lchild==NULL) {/*如果父结点q的lchild为空,
表示p为q的右儿子*/
r=q->rchild; /*向父结点的右儿子方向回退*/
q->rchild=p; /*重新连接父指针*/
p=q; q=r;
}
else if(q->rchild==NULL) {/*类似上面*/
r=q->lchild;
q->lchild=p;
p=q; q=r;
printf("%c ",p->data);
}
else {/*如果p的父亲q有两个子树,用以下方法判断p是q的左儿子还是右儿子*/
if(q==lr) {/*p是q的右儿子*/
r=top; /*退栈*/
lr=r->lchild; /*lchild相当于链表的data域,rchild相当next域*/
top=r->rchild;
r->lchild=r->rchild=NULL;/*退栈结束*/
r=q->rchild;
q->rchild=p;
p=q; q=r;
}
else { /*p是q的左儿子*/
printf("%c ",q->data);
av->lchild=lr;/*入栈加回退操作*/
av->rchild=top;
top=av; lr=q;
r=q->lchild;
q->lchild=p;
p=q->rchild;
q->rchild=r;
break;
}
}
}
}

}

main() {
BiTree T=NULL;
/*该树的先序序列为:abdgecf
中序序列为:gdbeacf*/
strcpy(tree,"abdg e c f ");
CreateBiTree(&T);
printf("\nzhongxu2:");
InOrder2(T);

}



[此贴子已经被作者于2006-10-27 8:26:00编辑过]

搜索更多相关主题的帖子: 二叉树 遍历 递归 空间 
2006-10-23 14:35
SunShining
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:31
帖 子:2215
专家分:0
注 册:2006-2-17
收藏
得分:0 
嗯.这个算法值得仔细研究.现在没有时间.晚上再来详细研究

谢谢您的共项.如有不理解的地方.再来请教!

[glow=255,violet,2]闭关修炼ing...[/glow] [FLASH=360,180]http://www./chinaren.swf[/FLASH]
2006-10-23 15:32
haroldi
Rank: 1
等 级:新手上路
帖 子:158
专家分:0
注 册:2006-7-22
收藏
得分:0 
以下是引用stnlcd在2006-10-23 14:35:51的发言:

常见的二叉树非递归算法都是用栈保存访问路径上的结点,这样使空间复杂为o(n),其中n为树最大深度。近日偶然翻书发现一空间复杂度为o(1)的算法,与大家分享。这个算法并没有以牺牲时间复杂度为代价,它只是巧妙的运用叶子结点左右孩子指针为空这一事实,将所有的叶子组成一链栈用于保存回退信息,其中叶子结点的lchild域相当于链表的data域,rchild相当于链表的next域,是一种“废物利用”的思想,本质上还是用了栈,只是没用分配栈空间而已。该算法的设计十分巧妙,需要仔细理解才会明白。
事实上,像二叉树这种递归定义的非线性结构,在设计相应的非递归程序时最难考虑的是从当前结点向上回退的情况,向下走的情况很简单:先向左走,左面走不下去了就向右。回退的时候有两个要点:1,找到其父结点的指针以便回退。2,判断出它是父结点的左孩子还是右孩子。常见的用栈保存信息的算法,栈中保存了从根结点到当前结点的路径信息,当然可以正确的回退。
该算法的大概思想是:p作为当前结点,q作为它的父结点(满足要点1),如此深入下去,如果p是q的左孩子,就把q的左指针指向q的父亲,这样q作指针所指向的结点就不在是p了,而是p的爷爷结点了,这样的做法是为了在回退时找到父结点。为了满足要点2:判断回退时它是父结点的左孩子还是右孩子,有三种情况要考虑:1,回退时如果q的lchild为空,表示q只有右子树p,这样就判断出来了p只可能是q的右孩子,剩下的问题就是回退和恢复q的lchild了。2,如果q的rchild为空,这个情况和1相似......3,如果q的lchild和rchild都不为空,这样回退时候就比较难判断p到底是q的左还是右儿子了?该算法的解决方法为:用av保存当前叶子结点以作为为栈分配的元素,这个栈用于记录遇到的双子树结点。用lr表示当前遇到的双子树结点,也就是p从q的lchild域回退时才判断出来q有两个子树(怎知道是lchild回退?这是程序技巧问题,看程序!),这时就把q记录在lr内,这时lr并不急于进av栈,只有当lr的右子树中还有双子树结点时,lr才进栈!如果lr的右子树中没有双子树结点,那么当p回退的时候如果碰到了lr就表示p为q的右儿子,具体算法还要看程序说明,我很难说的清楚。至于作为栈的叶子结点是完全够用的,这是由中序遍历的性质决定的,因为只有访问了一个叶子结点才有可能访问一个双子树结点。有兴趣的话,大家仔细思考吧。祝大家学习愉快!


/*程序默认的先序建树序列为:“abdg e c f ”*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char tree[30]; /*tree数组为先序建树序列*/
int ti=0;

typedef struct BiTNode {
char data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

/*先序建树函数!*/
void CreateBiTree(BiTree *T) {
char ch;
ch=tree[ti++];
if(!ch) return;
if(ch==' ') *T=NULL;
else {
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data=ch;
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
/*非递归无栈二叉树中序遍历算法*/
void InOrder2(BiTree T) {
BiTree top=NULL,lr=NULL,r,av,
p=T,q=T;
/*top为栈顶指针,lr记录当前2子树结点,r为临时变量,
av为当前可用叶子结点,p为当前访问结点,q为p的父亲结点*/
if(T==NULL) return ;
while(1) { /*向下访问搜索*/
while(1) {
if(p->lchild==NULL&&p->rchild==NULL) {/*如果访问到底,就输出该结点*/
printf("%c ",p->data);
break;
}
else if(p->lchild==NULL) {/*沿左子树访问到底,转向访问右子树*/
printf("%c ",p->data);
r=p->rchild;
p->rchild=q;
q=p; p=r;
}
else {/*一直沿左子树访问下去*/
r=p->lchild;
p->lchild=q;
q=p; p=r;
}
}
av=p;/*p为叶子结点,av记录当前叶子结点*/
while(1) {
if(p==T) return;/*如果回退到根,返回*/
else if(q->lchild==NULL) {/*如果父结点q的lchild为空,
表示p为q的右儿子*/
r=q->rchild; /*向父结点的右儿子方向回退*/
q->rchild=p; /*重新连接父指针*/
p=q; q=r;
}
else if(q->rchild==NULL) {/*类似上面*/
r=q->lchild;
q->lchild=p;
p=q; q=r;
printf("%c ",p->data);
}
else {/*如果p的父亲q有两个子树,用以下方法判断p是q的左儿子还是右儿子*/
if(q==lr) {/*p是q的右儿子*/
r=top; /*退栈*/
lr=r->lchild; /*lchild相当于链表的data域,rchild相当next域*/
top=r->rchild;
r->lchild=r->rchild=NULL;/*退栈结束*/
r=q->rchild;
q->rchild=q; //改为 q->rchild=p; 否则将破坏原结构.
p=q; q=r;
}
else { /*p是q的左儿子*/
printf("%c ",q->data);
av->lchild=lr;/*入栈加回退操作*/
av->rchild=top;
top=av; lr=q;
r=q->lchild;
q->lchild=p;
p=q->rchild;
q->rchild=r;
break;
}
}
}
}

}

main() {
BiTree T=NULL;
/*该树的先序序列为:abdgecf
中序序列为:gdbeacf*/
strcpy(tree,"abdg e c f ");
CreateBiTree(&T);
printf("\nzhongxu2:");
InOrder2(T);

}




Do people want thick road ...
2006-10-27 00:41
stnlcd
Rank: 1
等 级:新手上路
帖 子:177
专家分:1
注 册:2004-11-21
收藏
得分:0 
是的是的,
打程序时笔误

要让一个男人破产,请给他一架相机,要让一个男人倾家荡产,请给他一架望远镜。
2006-10-27 08:25
快速回复:无空间复杂度(无栈)的非递归二叉树中序遍历
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.017752 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved