二叉树的遍历


二叉树的遍历都是 先左后右,实际是一路遍历左结点,直到孩子为空,返回的时候访问其他结点(如右结点),继续以右节点为基础一路访问左结点。
总体是 一路向左,没有了返回访问其他结点顺便换路线,再向左。

NLR中的N—node,其实也就是自己,而对于那个子树来说,他是根。
L—left subtree左子树 ,R—rigth subtree右子树,均是递归进入,遍历完了才返回。
访问到自己的时候,直接输出自己(先序),或者先递归完左子树L再输出自己(中序),或者递归完左子树L、右子树R再输出自己(后序)。

1.前序遍历—NLR

void PreOrder(BiTree T){
	if(T!=NULL){    	//每一次递归都会进入一个新子树,T为子树的根
		cout<<T->data;			//每访问一个子树先访问根结点
		PreOrder(T->lchild);   //一路向左
		PreOrder(T->rchild);   //左走完了,访问右+换路线,下一次继续一路向左
	}
}

在这里插入图片描述
基本过程是:输出根,向左(进入左子树)、输出根,向左、输出根,向左…
直到没了左子,返回,有右就向右(进入右子树),再输出根,向左,…

先序的特点:根是第一个访问到的,并且先访问完左子树,再访问右子树(无法区分分界点)


2.中序遍历—LNR

void InOrder(BiTree T){
	if(T!=NULL){
		InOrder(T->lchild);    //一路向左,直到进入最后一个子树向下执行
		cout<<T->data;		   //只有到最后一个左子树(叶)输出,然后返回的时候会输出子树根
		InOrder(T->rchild);  //进入右结点,继续一路向左,在其子树中输出左,根
	}
}

基本过程:一路向左,触底输出,再返回不断输出根,是否有右——调换路线,
有:右向右(进入右子树),再一路向左,直到叶子输出,返回输出根…
没有:就再返回输出根
在这里插入图片描述
如图也可知,中序序列可以用投影法快速求得。
在这里插入图片描述
中序的特点:第一个访问的是最左端的结点。先访问完根的左子树,再访问根,再访问根的右子树。所以知道子树的根就可以划分左右子树。


3.后序遍历—LRN

void PostOrder(BiTree T){
	if(T!=NULL){
		PostOrder(T->lchild);   //一路向左
		PostOrder(T->rchild);	//再一路向右
		cout<<T->data;		//直到最左节点输出,然后输出最右节点,返回时输出根
	}
}

后序有点奇怪,根节点是最后返回的时候才输出的,每次输出处在最底端的左右结点,返回输出根结点,有点像下图的摘葡萄.
适合用来销毁二叉树,从底部慢慢销毁,最后销毁根。
在这里插入图片描述

后序的特点:根是最后访问的。第一个访问最靠左的叶(可能是最左端的叶,也可能是最左端结点的右子树上最左端的叶)

我们遍历中不断调用Order(T->left)Oreder(T->rigth)就是在不断地进入左子树,没有子再进入右子树,同时也代表了上一个T的左子结点、右子结点。
每次调用后,我们的T就是根结点。返回也会返回到根结点,这也就是为什么根的位置是cout的位置。

Order(T->left)是一路向左。
如果在前面输出T(先序),就是每向左一个进入了新的子树,就把子树的根输出。
如果在其后面输出T,就直接一路向左走到了最后的子树(叶结点),再输出叶子。

Oreder(T->rigth)后面没有输出,每次调用进入函数还是向左/输出,这个主要是做调换路线用的。
那怎么输出右结点呢?当进入了右结点的路线,右结点也就成了子树的根,然后继续递归输出。


Ex.从单个结点看待遍历

对于每个结点,以他自己为参数的函数在整个递归过程中会"路过"三次

  1. 第一次是以其为参数T进入这个函数,这个时候要是输出,就是先序遍历。
  2. 接着进入他的左子树Order(T->left),然后一路递归,遍历完左子树最终返回到这个结点,这是第二次来到这个结点的函数,这个时候如果输出,就是中序遍历。
  3. 接着进入他的右子树Oreder(T->rigth),然后一路递归,遍历完右子树最终返回到这个结点,这是第三次来到这个结点的函数,这个时候如果输出,就是后序遍历。

这三次结束,这个结点的函数就会彻底结束,也就是直接出栈了。返回到上一个结点继续进行。
在这里插入图片描述
对于B来说
第一次是A调用 Order(T->left),进入了B为参数的函数——然后调用Order(T->left)
第二次是B的Order(T->left)运行结束返回的时候——然后调用Order(T->right)
第三次是B的Order(T->right)运行结束返回的时候。

对于一个结点,会先将其左子树遍历,再遍历其右子树,遍历的方法也是先左子树再右子树(毕竟递归,我这话讲的也递归了…)
所以,NLR中的N是node,其实也就是自己,而对于那个子树来说,他是根。LR分别是 left subtree , rigth subtree左子树和右子树
先序:进入左子树前输出自己。
中序:进入左子树后,进入右子树前输出自己。
后序:进入右子树后输出自己。


4.层次遍历(队列)

需要用队列来辅助进行,先将根入队,队头出队,再将队头的左子入队,右子入队,然后继续队头出队…

辅助队列如下:

typedef struct LiNode {
	BiNode* node;   //存结点的指针
	LiNode* next;
}LiNode,*Linklist;

typedef struct LinkQueue {
	Linklist front, rear;
}LinkQueue;

void InitQueue(LinkQueue& Q) {  //初始化队列,带头结点
	Q.front = new LiNode;
	Q.front->next = NULL;
	Q.rear = Q.front;
}
void EnQueue(LinkQueue& Q,BiNode* x) {
	LiNode* p = new LiNode;
	p->node = x;
	p->next = Q.rear->next;
	Q.rear->next = p;
	Q.rear = p;
}
bool DeQueue(LinkQueue& Q, BiNode* &x) {
	if (Q.rear == Q.front)  //队空
		return false;
	x = Q.front->next->node;
	LiNode* p = Q.front->next;
	if (p == Q.rear)   //只有一个结点
		Q.rear = Q.front;
	else
		Q.front->next = p->next;
	delete(p);
}

层序遍历

void LevelOrder(BiTree T, LinkQueue& Q) {
	InitQueue(Q);
	EnQueue(Q, T);    //根进队
	BiNode* p; 	   //工作指针
	while (Q.rear != Q.front) {  //队非空
		DeQueue(Q, p);       //队头出队
		cout << p->data;
		if (p->lchild != NULL)   //左子入队
			EnQueue(Q, p->lchild);
		if (p->rchild != NULL)	 //右子入队
			EnQueue(Q, p->rchild);
	}
}

用顺序啊,BiNode *data[Maxsize],就是个可以存结点的指针的数组!!!
我居然没想到数组可以存储各种结构!!!!
居然用链栈,链队!
难怪王道基本上都不介绍链栈链队。。。
记住!栈、队列,基本上都可以用顺序(顺序栈、循环队列)

//顺序队列:

typedef struct SqQueue {
	BiNode* val[Maxsize];
	int front, rear;
}SqQueue;
void InitQueue(SqQueue& Q) {    //rear指向队尾的下一个
	Q.front = Q.rear = 0;
}
bool isEmpty(SqQueue Q) {
	return Q.front == Q.rear ? 1 : 0;
}
bool EnQueue(SqQueue &Q, BiNode* x) {
	if ((Q.rear - Q.front) % Maxsize == Maxsize - 1) //队满
		return 0;
	Q.val[Q.rear] = x;
	Q.rear = (Q.rear + 1) % Maxsize;
	return 1;
}
bool DeQueue(SqQueue &Q, BiNode*& x) {
	if (Q.front == Q.rear)
		return 0;
	x = Q.val[Q.front];
	Q.front = (Q.front + 1) % Maxsize;
	return 1;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值