数据结构|二叉树的遍历

不得不说,第一次接触数据结构,二叉树的这一章让我多少有点觉得吃力,里面的代码实现,琢磨了几天才做出来,今天做一个整体代码总结,希望能帮助到大家。

前导知识

我们知道,一个满二叉树或完全二叉树,我们是完全可以使用顺序存储结构的。

因为我们满二叉树或者完全二叉树的结点个数较为完整,因为完全二叉树接近满二叉树,满二叉树是完全二叉树的特例,所以我们可以使用满二叉树的形式存储。

对于完全二叉树中有些结点无法与满二叉树完全对应,所以我们适当添加一些空结点来对应满二叉树。

但是如果是一个普通二叉树,仍然采用顺序存储结构的话,就会非常浪费空间,当只有右单分支的情况下,我们需要分配2的h次方-1的空结点,所以为了存储空间的节约,我们采用链式存储结构存储一棵普通二叉树。

二叉树的遍历分为递归和非递归遍历。

递归遍历分为先序遍历,后序遍历,中序遍历

创建一棵二叉树

这里建议使用getChar(),因为getChar()可以避免空格和回车字符,scanf必须在占位符前面加上一个空格才能避免把回车当作一个字符

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <stdbool.h>
#define MAXSIZE 100

typedef struct TreeNode{
	char data;
 	struct TreeNode *lchild;
	struct TreeNode *rchild;
}TreeNode;

TreeNode* create(TreeNode *treeList){
	char value;
	printf("请输入一个字符\n");
	scanf(" %c",&value);//注意啊,回车也会被当作一个字符 
	 if(value=='#'){
		treeList=NULL;
	}else{
		treeList=(TreeNode *)malloc(sizeof(TreeNode));
		treeList->data=value;
			//由于每一层都有返回值,
		//所以我们必须保存每次递归后的左右节点的值,否则链表就会连接不起来 
		treeList->lchild=create(treeList->lchild);
		treeList->rchild=create(treeList->rchild);
	}
	return treeList;
	
} 

先序递归遍历

先输出根结点,然后遍历每个结点的左结点,直到左结点为空,我们在循环遍历右结点,以此类推直到遍历整个二叉树

void PreOrderOut(TreeNode *treeList){//先序输出 
	if(treeList!=NULL){
		printf("先根结点:%c\n",treeList->data);//输出根结点
		PreOrderOut(treeList->lchild);//遍历左结点,直到左结点为空
		PreOrderOut(treeList->rchild);//遍历右结点,直到右结点为空
	}
}

中序和后序递归遍历也是再这个基础之上进行变换,是不是很简单,三句话就解决问题,只要你逻辑够清楚,就很好理解了。

void InOrderOut(TreeNode *treeList){//中序输出 
	if(treeList!=NULL){
		InOrderOut(treeList->lchild);
		printf("中序根结点:%c\n",treeList->data);
		InOrderOut(treeList->rchild);
	}
}
void LastOrderOut(TreeNode *treeList){//后序输出 
	if(treeList!=NULL){
		LastOrderOut(treeList->lchild);
		LastOrderOut(treeList->rchild);
		printf("后续根结点:%c\n",treeList->data);
	}
}

层次遍历

所谓层次遍历,就是从根结点开始,从上到下,从左到右依次遍历二叉树每个结点

这里为了方便,我们采用循环队列来存储结点,队列的特点是先进先出,刚好满足我们的层次遍历的特点。依次从根节点开始,将第一个结点存储到队列中,队列的队尾下标加1,然后再输出,然后再保存左结点和右节点,之后再输出左节点,保存左节点的孩子结点,队列的队头下标加1,然后再输出右节点,然后保存右节点的孩子节点,以保证每次循环不丢失下一个结点的位置,直到队头队尾位置相等了,就说明此时已经遍历完毕了。

typedef struct TreeNode{//二叉树存储结构
	char data;
 	struct TreeNode *lchild;
	struct TreeNode *rchild;
}TreeNode;

typedef struct QueueNode{//队列存储结构
	TreeNode *data[MAXSIZE];
	int top;
	int base; 
}QueueNode;

void push1(QueueNode *queuenode,TreeNode *treeList){//入队方法
	printf("进队列:%c\n",treeList->data);
	if(treeList!=NULL&&((queuenode->top+1)%MAXSIZE!=queuenode->base)){
		queuenode->data[queuenode->top]=treeList;
		//printf("打印一下:%c,%d\n",queuenode->data[queuenode->top]->data,queuenode->top);
		queuenode->top=(queuenode->top+1)%MAXSIZE;
		
	}
}

TreeNode* pop1(QueueNode *queuenode){//出队方法
	TreeNode *treenode;
	if(queuenode->top!=queuenode->base){//只要队头不等于队尾,就代表当前队列还不为空,还有结点
		treenode=queuenode->data[queuenode->base];
		//printf("输出值:%c,%d\n",treenode->data,queuenode->base);
		queuenode->base=(queuenode->base+1)%MAXSIZE;//这里我们采用循环队列来实现存储,这样可以更有效的利用空间
	
	}
	return treenode;
}

void levelOrderOut(TreeNode *treeList){//层次遍历 
	QueueNode *queuenode;
	TreeNode *p;
	queuenode=(QueueNode*)malloc(sizeof(QueueNode));
	queuenode->top=queuenode->base=0;
	if(treeList!=NULL)
	push1(queuenode,treeList);
	while(queuenode->top!=queuenode->base){
		p=pop1(queuenode);
		if(p->lchild!=NULL)//注意细节啊,在完整执行一次后,如果自己估算的没问题,就可能时变量什么的错了 
		push1(queuenode,p->lchild);
		if(p->rchild!=NULL)
		push1(queuenode,p->rchild);
	}
	
}

非递归的实现就稍微复杂一点了

先序非递归二叉树

非递归二叉树的遍历方式和递归二叉树的区别在于,我们需要保存其左右结点,以保证每个结点能正确被访问到,而递归层层嵌套,每一层执行完毕会回到上一层,所以我们可以不必保存每个结点。

我们可以使用栈来保存遍历的结点,栈的特点是后进先出,先保存当前根结点,然后输出,再保存右结点,栈顶位置加1,保存左节点,后进先出,所以先输出左节点,栈顶位置减1,然后再保存左节点的孩子节点,再输出右结点,保存右结点孩子结点,以此类推,直到二叉树遍历完毕。

typedef struct StackNode{//栈存储结构
	TreeNode *top[MAXSIZE];
	int length;
}StackNode;


typedef struct TreeNode{//二叉树存储结构
	char data;
 	struct TreeNode *lchild;
	struct TreeNode *rchild;
}TreeNode;

void push(StackNode *stacknode,TreeNode *treeList){//入栈方法
	if(treeList!=NULL){
		stacknode->top[stacknode->length]=treeList;
		stacknode->length++;
	}
	
}

TreeNode* pop(StackNode *stacknode){//出栈方法
	TreeNode *node;
	if(stacknode->length!=0)
	stacknode->length--;
	node=stacknode->top[stacknode->length];
	printf("出栈一个结点:%c\n",node->data);
	return node;
}


void PreOrderIn(TreeNode *treeList){//先序非递归遍历 
	StackNode *stacknode;
	TreeNode *node;
	stacknode=(StackNode*)malloc(sizeof(StackNode));
	stacknode->length=0;
	if(treeList!=NULL){
		push(stacknode,treeList);
		while(stacknode->length	!=NULL){
			node=pop(stacknode);
			if(node->rchild!=NULL){
				push(stacknode,node->rchild);
			}
			if(node->lchild!=NULL){
				push(stacknode,node->lchild);
			}
		}
	}
}

中序非递归二叉树

中序非递归二叉树的特点是先入栈所有的左结点,然后再依次输出左节点,保存其右节点,然后再输出右结点,再以同样的方式遍历输出,直到整个二叉树遍历完毕。

typedef struct StackNode{//栈存储结构
	TreeNode *top[MAXSIZE];
	int length;
}StackNode;

typedef struct TreeNode{//二叉树存储结构
	char data;
 	struct TreeNode *lchild;
	struct TreeNode *rchild;
}TreeNode;

void push(StackNode *stacknode,TreeNode *treeList){//入栈方法
	if(treeList!=NULL){
		stacknode->top[stacknode->length]=treeList;
		stacknode->length++;
	}
	
}

TreeNode* pop(StackNode *stacknode){//出栈方法
	TreeNode *node;
	if(stacknode->length!=0)
	stacknode->length--;
	node=stacknode->top[stacknode->length];
	printf("出栈一个结点:%c\n",node->data);
	return node;
}


void InOrderIn(TreeNode *treeList){//中序非递归遍历 
	StackNode *stacknode;
	TreeNode *p;
	TreeNode *node;
	p=treeList;
	stacknode=(StackNode*)malloc(sizeof(StackNode));
	stacknode->length=0;
	while(p!=NULL||stacknode->length!=0){//栈不为空或者树不为空则执行 
		while(p!=NULL){
			push(stacknode,p);//让所有左孩子进栈 
			p=p->lchild;
		}
		if(stacknode->length!=0){
			node=pop(stacknode);
			push(stacknode,node->rchild);//每个结点的右孩子进栈 
		}
	}
}

后序非递归遍历

后序非递归与前面不同的是,这里采用了do-while循环,由于我们的栈初始化是为空的,所以不能直接使用while循环,而是先执行一次再判断,后序遍历的规则是左结点,右节点,根节点,所以我们同样遍历所有左结点并保存,然后我们利用flag来表示我们当前在操作栈顶元素,如果当前flag为false,代表当前我们没有在操作栈顶元素,而是在操作未入栈的元素,因此我们将当前未入栈的右节点入栈,再重新按照一开始的遍历方式从栈顶元素开始操作,然后我们获取当前的栈顶元素,判断此时的结点的右孩子是否为空或者是已经遍历过的结点,我们用r保存,如果是,就更新当前变量r的结点,如果不是,就移动到右节点位置,保存右节点到栈中,再重新将所有变量置空,读取当前的右节点,并且出栈,并且将当前t置空,以保证每次操作的都是一个新的结点,而不是已经操作过的旧结点,就这样依次类推,完成遍历。

void push(StackNode *stacknode,TreeNode *treeList){
	if(treeList!=NULL){
		stacknode->top[stacknode->length]=treeList;
		stacknode->length++;
	}
	
}

TreeNode* pop(StackNode *stacknode){
	TreeNode *node;
	if(stacknode->length!=0)
	stacknode->length--;
	node=stacknode->top[stacknode->length];
	printf("出栈一个结点:%c\n",node->data);
	return node;
}

TreeNode* GetTop(StackNode *stacknode,TreeNode	*p){//获取栈顶结点 
	if(p!=NULL){
		return p;
	}else{
		p=stacknode->top[stacknode->length-1];
	}
	return p;
}


void LastOrderIn(TreeNode *treeList){//后序非递归 
	StackNode *stacknode;
	stacknode=(StackNode*)malloc(sizeof(StackNode));
	stacknode->length=0;
	TreeNode *p;
	TreeNode *t;
	TreeNode *r;
	bool flag;
	p=treeList;
	do{
		while(p!=NULL){
			push(stacknode,p);
			p=p->lchild;
		}
		r=NULL;
		t=NULL;
		flag=true;
		while(flag&&stacknode->length!=0){
			t=GetTop(stacknode,t);
			if(t->rchild==r){
				r=pop(stacknode);
				t=NULL;
			}else{
				t=t->rchild;
				push(stacknode,t);
				flag=false;
			}
		}
	} while(stacknode->length!=0);
}

后序非递归这一块解释有一点绕可能(我讲的不够好,望各位见谅),如果各位看不懂或者还有什么不明白可以直接评论,我再给你们解释,这些代码都是我实战一遍之后才放出来给大家看的,所以执行上有什么问题可以找我。

虽然看起来复杂,但是我相信天下无难事,只怕有心人。加油!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值