二叉树遍历的那些事儿

一,先序创建节点

  • 首先,按先序遍历创建节点:先序遍历列表如下,( ϕ \phi ϕ 代表空格)
a b c ϕ \phi ϕ ϕ \phi ϕ d e ϕ \phi ϕ g ϕ \phi ϕ ϕ \phi ϕ f ϕ \phi ϕ ϕ \phi ϕ
  • 其次由先序序列,对应的二叉树状图如下,
    在这里插入图片描述

  • 注意点:
    1,scanf 的时候要注意冲洗缓冲区
    2,递归建立节点

  • 代码

bool CreateBiTree(BiTNode *(&T)){	// 按先序次序输入二叉树的节点的值,空格字符表示空树
	elemType2 ch = 'c';

	printf("输入元素\n");
	scanf("%c", &ch);
	fflush(stdin);

	if(ch == ' '){
		T = NULL;
	}
	else{
		T = (BiTNode *)malloc(sizeof(BiTNode));
		if(!T){
			return false;
		}

		T->data = ch;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}
	return true;
}

二,递归遍历(根据根的先后分为 CLR, LCR, LRC)

访问函数

  • 进行遍历前,先设置访问函数
  • 代码
bool Visit(elemType2 e){
	printf("%c\t", e);
	return true;
}

先序遍历(CLR)

  • 代码
													// 先序
bool PreOrderTraverse(BiTNode *T, bool (*v)(elemType2)){	// 使用回调函数还挺有意思的
	if(T != NULL){
		v(T->data);
		PreOrderTraverse(T->lchild, v);
		PreOrderTraverse(T->rchild, v);
	}
	return true;
}

中序遍历(LCR)

  • 代码
bool InOrderTraverse(BiTNode *T, bool (*v)(elemType2)){	// 中序
	if(T != NULL){
		InOrderTraverse(T->lchild, v);
		v(T->data);
		InOrderTraverse(T->rchild, v);
	}
	return true;
}

后序遍历(LRC)

  • 代码
bool PostOrderTraverse(BiTNode *T, bool (*v)(elemType2)){	// 后序
	if(T != NULL){
		PostOrderTraverse(T->lchild, v);
		PostOrderTraverse(T->rchild, v);
		v(T->data);
	}
	return true;
}

三,非递归遍历

进行非递归遍历要用到自己的栈

先序遍历(CLR)

  • 首先根节点入栈,然后沿着一个方向入栈,不妨设一直沿左孩子入栈,当遇到虚拟节点为空时候,停止入栈,准备出栈操作。即入栈顺序为 A->B->C(注意虚拟节点是否入栈,决定了不同的算法,本文中假设虚拟节点不入栈)
  • 由 根->左->右 的访问次序,来进行访问,即入栈时候,就可以当成访问了 根,
    此时,访问顺序为 A->B->C
  • 虚拟节点不入栈,那么,退回到上次入栈的元素,即 C,此时 C 因为已经访问,可以出栈,并将其右孩子入栈,这里要理解的是,右孩子也应该当成根处理,入栈时候即可访问它,然后沿着右孩子的左孩子方向入栈,依次类推。
    在这里插入图片描述
  • 代码:(结合代码很容易理解)
bool PreOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){	// 非递归前序遍历,要用到自己的栈
	// 每次都是当成根节点访问的
	// 虚拟节点
	BiTNode *p=NULL;
	SqStack S;
	
	p = T;
	InitStack(S);
	
	//printf("%p\n", p);
	// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
	while(p || !(S.top == S.base)){
		// 左边一直入栈
		if(p){
			v(p->data);
			Push(S, p);
			p = p->lchild;
		}
		else{  	// 访问栈顶节点,并出栈,继续压入栈
			Pop(S, p);
			p = p->rchild;
		}
	}
	
	return true;
}

中序遍历

中序遍历不同的是,由于要优先访问左孩子,所以沿根的左孩子入栈时并未访问它。
而弹栈的时候,即上面的 C 弹栈,C 是 B 的左孩子,所以这时候要访问 C。总结为,弹的是上个节点的左孩子,所以弹的时候再访问,同时这里要注意弹的也是本节点的根。要站在上一个节点的角度看问题。

  • 代码:(结合代码很容易理解)
bool InOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){	// 非递归中序遍历,要用到自己的栈
	// 每次都是当成根节点访问的
	// 虚拟节点
	BiTNode *p=NULL;
	SqStack S;
	
	p = T;
	InitStack(S);
	
	//printf("%p\n", p);
	// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
	while(p || !(S.top == S.base)){
		// 左边一直入栈
		if(p){
			Push(S, p);
			p = p->lchild;
		}
		else{  	// 访问栈顶节点,并出栈,继续压入栈
			Pop(S, p);
			v(p->data);
			p = p->rchild;	// 继续访问右边
		}
	}
	
	return true;

}

后序遍历

后序遍历稍微复杂一些,要记录每个节点的访问次数。

  • 依然从根开始,沿着一个方向访问,本文以左孩子方向访问。
  • 从上一个节点来看,左孩子遍历不能退栈,右孩子遍历了才可以退栈。
  • 第一次左孩子入栈的时候坐上标记,表示第一次访问。当左孩子由于访问到了虚拟节点,要退回来时,即退到根节点,第二次访问做上标记;接着再去访问右孩子,右边完毕会退到根节点,第三次访问根节点时,根节点再弹出栈(注意这里弹栈后根节点要为空)。引用如下段落可以说明更清楚操作过程:

转载地址在这里插入图片描述

  • 探讨一下为什么要进行标记?因为左边要退回,不能访问左边,右边也要退回,不能访问右边,只有根节点弹栈的时候,根节点要访问。而要弹栈的根节点先后三次访问,很难区分是是否再右边退回的时候进行的访问,为了简便区分,于是做上了标记。如果不做标记也是可以的,谨记,要做到只有当根节点的右节点访问了,才能弹出栈,这里还是要判断右节点到底访问没有。
  • 代码
bool PostOrderTraverse_None(BiTNode *T, bool (*v)(elemType2)){	// 非递归后序遍历,要用到自己的栈
	// 每次都是当成根节点访问的
	// 虚拟节点
	BiTNode *p=NULL;
	BiTNode *topTemp=NULL;

	SqStack S;
	
	p = T;
	InitStack(S);
	
	//printf("%p\n", p);
	// 不停入栈和出栈操作,当不能入了以及不能出了,就停止
	while(p || !(S.top == S.base)){
		// 左边一直入栈
		if(p){
			Push(S, p);
			p->flag = 1;
			p = p->lchild;
		}			
		else{  	// 进来的时候 p 为空,pop 后 p 非空
			GetTop(S, topTemp);
			if(topTemp->flag == 1){	// 左边退栈
				topTemp->flag = 2;
				p = topTemp->rchild;
			}
			else{	// 右边退栈
				Pop(S, p);
				v(p->data);
				p = NULL;	// 此时无需访问左右
			}
		}
	}
	
	return true;

}

补充,层序遍历

  1. 要利用队列来实现层序遍历,如下入队顺序是 A->B->C->D->E->F->G,
  2. 即根入队,实现第一层入队,然后第一层出队,然后如果根有左孩子,那么左孩子入队;接着如果有右孩子那么右孩子入队。
  3. 队列头部出队,左孩子来到头部,再进行把左孩子当成根,回到第 2 步。
  • 其实根据上面的步骤,自然而然原来的对头出队,新的元素每到队头,又从队尾增加队列元素,那么就实现了层序遍历(有点儿神奇)
    在这里插入图片描述
  • 代码
bool LevelOrderTraverse(BiTNode *T, bool (*v)(elemType2)){ 
	LinkQueue lkT;

	InitQueue_Link(lkT);
	
	EnQueue_Link(lkT, T);  // 根节点先入队
	
	while(lkT.front != lkT.rear){
		if(T->lchild != NULL){		// 如果有左孩子,那么左孩子入队;
			EnQueue_Link(lkT, T->lchild);
		}
		
		if(T->rchild != NULL){	// 如果有右孩子,那么右孩子入队;
			EnQueue_Link(lkT, T->rchild);
		}
		DeQueue_Link(lkT, T);	// 根节点出队,那么头部就是左孩子,相当于第二层开始了
		v(T->data);
		GetHead(lkT, T);	// 更新了 T
	}


	return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值