二叉树——存储结构&遍历方式
说到二叉树,从逻辑结构上来说是非线性结构,从存储结构上来说有顺序存储&链式存储。
-
为什么说它是非线性结构呢?
因为它是一对多的关系
-
那顺序存储&链式存储又是如何定义的呢?
- 顺序存储
当然,如果不是完全二叉树,那么就无法从结点编号反映出结点的逻辑关系
否则就会这样
故而一定要将二叉树的编号和完全二叉树对应起来(也就是空节点也要编号)
显然,如果是非完全二叉树会很浪费结点空间
2. 链式存储
点击查看代码
typedef struct BiTNode{
int data;
struct BiTNode* lchild;
struct BiTNode* 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);
}
先序遍历非递归算法
点击查看代码
void PreOrder(BiTree T){
InitStack(S); BiTree p = T; /*初始化栈,p是遍历指针*/
while(p||!IsEmpty(S)){ /*栈不空或者p不空时循环*/
if(p){ /*一路向左*/
visit(p);Push(S,p); /*访问当前节点并入栈*/
p = p->lchild; /*左孩子不空,一直向左走*/
}
else{ /*出栈,并转向出栈节点的右子树*/
Pop(S,p); /*栈顶元素出栈*/
p = p->rchild; /*向右子树走,p赋值为当前节点的右孩子*/
}
}
}
中序遍历非递归算法
点击查看代码
void InOrder(BiTree T){
InitStack(S); BiTree p = T; /*初始化栈,p是遍历指针*/
while(p||!IsEmpty(S)){ /*栈不空或者p不空时循环*/
if(p){ /*一路向左*/
Push(S,p); /*当前节点入栈*/
p = p->lchild; /*左孩子不空,一直向左走*/
}
else{ /*出栈,并转向出栈节点的右子树*/
Pop(S,p); visit(p); /*栈顶元素出栈,访问出栈节点*/
p = p->rchild; /*向右子树走,p赋值为当前节点的右孩子*/
}
}
}
后序非递归算法
算法思想:
后序非递归遍历二叉树是先访问左子树,再访问右子树,最后访问根节点。
结合下图分析:
沿着根的左孩子,依次入栈,直到左孩子为空。此时栈内元素依次为A B D。
读栈顶元素:若其右孩子不空且未被访问,将右子树转执行第一步,否则栈顶元素出栈并访问
注:在第二步中,必须分清返回时是从左子树返回的还是从右子树返回的,因此设定一个辅助指针r,指向最近访问过的结点(或者在结点中增加标号域)
点击查看代码
void PostOrder(BiTree T){ /*利用辅助指针r来实现*/
InitStack(S); p=T; /*初始化栈和遍历指针p*/
r = NULL; /*辅助指针r,用来指向最近访问过的结点*/
while(p||!IsEmpty(S)){
if(p){ Push(S,p);p = p->lchild;/*走到最左边*/ }
else{ /*向右*/
GetTop(S,p); /*读栈顶结点*/
if(p->rchild&&p->rchild!=r){ /*若右子树存在且未被访问过*/
p = p->rchild; /*转向右*/
Push(S,p); /*入栈*/
p = p->lchild; /*再走到最左*/
}
else{ /*否则弹出结点并访问*/
Pop(S,p);visit(p->data);
r = p; /*记录最近访问过的结点*/
p = NULL; /*每次出栈访问完一个结点就相当于遍历完以该结点为根的子树,重置p指针*/
}
}
}
后序非递归遍历特点:访问一个结点p时,栈中结点恰好是p结点的所有祖先结点,从栈底到栈顶结点再加上p结点,刚好构成从根节点到p结点的一条路径。
应用:
求根到某结点的路径
求两个结点的最近公共祖先
除此之外,二叉树还有一种层次遍历
点击查看代码
void LevelOrder(BiTree T){
InitQueue(Q); /*初始化辅助队列*/
BiTree p;
EnQueue(Q,T); /*根节点入队*/
while(!IsEmpty(Q)){ /*队列不空则循环*/
DeQueue(Q,p); /*队头结点出队*/
visit(p);
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); /*左子树不空,则左子树根节点入队*/
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); /*右子树不空,则右子树根节点入队*/
}
}
下一篇将补充说明这几种遍历方式的应用场景(附带代码)