二叉树——存储结构&遍历方式

说到二叉树,从逻辑结构上来说是非线性结构,从存储结构上来说有顺序存储&链式存储。

  • 为什么说它是非线性结构呢?
    因为它是一对多的关系

  • 那顺序存储&链式存储又是如何定义的呢?

  1. 顺序存储

当然,如果不是完全二叉树,那么就无法从结点编号反映出结点的逻辑关系
否则就会这样

故而一定要将二叉树的编号和完全二叉树对应起来(也就是空节点也要编号)
显然,如果是非完全二叉树会很浪费结点空间
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); /*右子树不空,则右子树根节点入队*/
	}
}

下一篇将补充说明这几种遍历方式的应用场景(附带代码)

posted @ 2021-12-15 19:35  天子笑!分你一坛  阅读(172)  评论(0编辑  收藏  举报