二叉链表树的三种遍历方式(递归与非递归) C++

文章目录

        • 1.二叉链表(Binary Linked List)
        • 2.二叉链表的先序建立
        • 3.二叉树的递归遍历
          • 3.1 先序遍历二叉树
          • 3.2 中序遍历二叉树
          • 3.3 后序遍历二叉树
        • 4.非递归遍历二叉树
          • 4.1 中序遍历
          • 4.2 先序遍历
          • 4.3 后序遍历
        • 5.完整实例代码

1.二叉链表(Binary Linked List)

二叉树的节点由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的节点至少包含三个域:数据域和左右指针域,这就是常见的二叉链表结构。有时,为了便于找到节点的双亲,还可以再结点结构中增加一个指向其双亲结点的指针域,这种结构称为三叉链表
二叉链表
C语言表示

typedef struct Binode{
	TelemType data; // 数据域
	struct Binode *left,*right; // 左右孩子指针 
}Binode,* Bitree;

2.二叉链表的先序建立

可以按照先序遍历的顺序输入建立一棵二叉链表树,其中none表示空结点,可以是# @ ' '

// 按先序输入建立二叉树 
int createBiTree(Bitree& T){  // 这里必须是引用传递,或者指针传递,否则不会对原地址的值进行修改 
	TelemType ch;
	scanf("%c",&ch);
	if(ch == none ) T = NULL; // 用#表示空节点
	else{
		T = new Binode;
		T->data = ch;
		createBiTree(T->left);  // 建立左子树
		createBiTree(T->right);  // 建立右子树 
	}
	return 1;	
}

3.二叉树的递归遍历

由于二叉树的定义是递归的,所以用递归的形式来写二叉树的三种遍历方式不仅容易理解,而且代码简洁。

3.1 先序遍历二叉树

若二叉树为空,则空操作;
否则:
1.访问根节点
2.先序遍历左子树
3.先序遍历右子树

// 按照先序遍历二叉树 
int preOrder(Bitree T){
	if(T){
		printf("%c",T->data); // 先遍历根
		preOrder(T->left);
		preOrder(T->right);
		return 1;
	} else return 0;
}
3.2 中序遍历二叉树

若二叉树为空,则空操作;
否则:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树

//中序遍历
int inOrder(Bitree T){
	if(T){
		if(T->left) inOrder(T->left);
		printf("%c",T->data);
		if(T->right) inOrder(T->right);
		return 1;
	} else return 0;
} 
3.3 后序遍历二叉树

若二叉树为空,则空操作;
否则:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点

//后序遍历
int postOrder(Bitree T){
	if(T){
		if(T->left) postOrder(T->left);
		if(T->right) postOrder(T->right);
		printf("%c",T->data);
		return 1;
	}return 0;
} 

4.非递归遍历二叉树

若采用非递归的方式来遍历二叉树,就需要用到来模拟实现递归。

4.1 中序遍历

中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。
若栈顶记录的指针非空,则应遍历左子树,即指向左子树根的指针进栈;
若栈顶记录为空,则应该退至上一层:

  • 若从左子树返回,则应访问当前层即栈顶指针所指的结点;
  • 若从右子树返回,则表明当前层的遍历结束,应继续退栈。
    从另一角度看,遍历右子树时,不再需要保存当前层的指针,直接修改栈顶记录的指针即可。

风格一:

int inOrederTraverse1(Bitree T){
	stack<Bitree> s;
	s.push(T);
	while(!s.empty()){
		while(s.top()) s.push(s.top()->left); // 向左走到尽头
		s.pop(); //空指针退栈
		if(! s.empty()){
			Bitree t = s.top();
			s.pop(); //根结点出栈
			printf("%c",t->data);
			s.push(t->right);   // 不管右子树是否为空,都要压栈,否则无法退出当前层 
		}
	}
	return 1;
}

风格二

int inOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T; //当前遍历节点 
	while(!s.empty() || p){
		 if(p){
		 	s.push(p); //根节点进栈
			p = p->left; //遍历左子树 
		 }else{
		 	p = s.top();
		 	s.pop(); //返回上一根节点
			printf("%c",p->data);
			p = p->right; // 遍历右节点  
		 }
	}
	return 1; 
}
4.2 先序遍历

前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);
若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。

风格一

int preOrderTraverse1(Bitree T){
	stack<Bitree> s;
	s.push(T);
	Bitree p;
	while(!s.empty()){
		while(s.top()){
			p = s.top();
			printf("%c",p->data); //先访问根
			s.push(p->left);//向左走到尽头 
		} 
		s.pop(); //空指针出栈 
		if(!s.empty()){
			p = s.top(); //退回上一层的根节点
			s.pop();
			s.push(p->right);
		} 
	} 
	return 1;
} 

风格二:

int preOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T; //当前访问节点
	while(!s.empty() || p) {
		if(p){
			printf("%c",p->data);
			s.push(p);
			p = p->left; //访问左节点
		}else{
			p = s.top(); //返回上一根节点
			s.pop();
			p = p -> right; 
		}
	}
	return 1;
}

风格三:
按照结点的访问顺序,我们可以先访问根节点,然后按照右子树-左子树的顺序压栈,利用栈的后进先出性质实现遍历。

void preOrderTraverse3(Bitree T){
 	stack<Bitree> s;
 	while(T){ 
		printf("%c",T->data);//先访问根
		//先把右子树压栈,
		//把左子树访问之后再访问右子树 
		if(T->right) s.push(T->right);
		T = T->left; //类似递归访问左子树
		if(T == NULL && !s.empty()){ //如果左子树为空,则访问右子树 
			T = s.top();
			s.pop(); 
		} 	
	}
 }
4.3 后序遍历

每个递归子树按照“左子树-右子树-根结点”顺序访问:
后序遍历的非递归实现较前两者难,因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。

方法一:
对根节点p可以分情况讨论:

  1. p如果是叶子节点,直接输出
  2. p如果有孩子,且孩子没有被访问过,则按照右孩子,左孩子的顺序依次入栈
  3. p如果有孩子,而且孩子都已经访问过,则访问p节点:
    • 如果左子树访问完,且右子树为空,则输出p;
    • 如果左右子树访问完,则输出p;
      关于如何表示p的孩子是否已经访问过,有以下可选方法:
      1.为每个结点设置一个标志位来对其状态进行判断
      2.用last来把保存最后一个访问过的结点,如果当前结点p满足(p->right==NULL && last ==p->left) || last=p->right,那么显然p的孩子都访问过了,接下来可以访问p了。
 int postOrderTraverse1(Bitree T){
 	stack<Bitree> s;
 	Bitree p; //当前访问节点
	Bitree last; //最后一个访问过的节点 
	last = p = T; 
	s.push(T);
	while(!s.empty()){
		p = s.top();
		if((p->left==NULL && p->right == NULL)
		|| (p->right == NULL && last == p->left)
		|| (last == p->right)){
			printf("%c",p->data);
			s.pop();
			last = p;
		}else{
			if(p->right) s.push(p->right);
			if(p->left) s.push(p->left);
		}
	} 
	return 1;
 }

方法二:
对于每个节点,都压入两遍,在循环体中,每次弹出一个节点赋给p,如果p仍然等于栈的头结点,说明p的孩子们还没有被操作过,应该把它的孩子们加入栈中,否则,访问p。也就是说,第一次弹出,将p的孩子压入栈中,第二次弹出,访问p。

void postOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T;
	s.push(p); s.push(p);
	while(!s.empty()){
		p = s.top();  s.pop(); //取栈顶根节点
		if(p == s.top() && !s.empty()){ //表示该节点的孩子结点还为访问 
			if(p->right){
				s.push(p->right); s.push(p->right);
			}
			if(p->left){
				s.push(p->left); s.push(p->left);
			}
		}
		else printf("%c",p->data); //第二次接触,表示孩子结点已经访问完
	}
}

不知道为什么,使用这种方法时,最后一个结点,也就是根节点不能打印,即便是只创建只有一个结点的这种单结点树的情况,也什么都不显示。如果,有知道的小伙伴希望能指出错误,谢谢~

方法三:
设置标志位

typedef struct flagNode{
	int flag;  //是不是第一次出现再栈顶 
	Binode* bnode;
}flagNode; 
void postOrderTraverse3(Bitree T){
	stack<flagNode*> s;
	Bitree p = T;
	flagNode* temp;
	while(p || !s.empty()){
		while(p){ //向左走到尽头 
			flagNode* fnode = new flagNode;
			fnode->bnode = p;
			fnode->flag = 1;
			s.push(fnode); 
			p = p->left;
		}
		if(!s.empty()){
			temp = s.top(); s.pop();
			if(temp->flag){//第一次
				temp->flag = 0;
				s.push(temp);
				p = temp->bnode->right; //访问右子树 
			}
			else{
				printf("%c",temp->bnode->data);
				p = NULL; //退回上一层 
			}
		}
	}
}

5.完整实例代码

#include 
#include 

using namespace std;


#define none '#'        
typedef char TelemType; // 节点数据域类型

// 二叉链表存储 
typedef struct Binode{
	TelemType data;
	struct Binode *left,*right;
}Binode,* Bitree;
// 按先序输入建立二叉树 
int createBiTree(Bitree& T){ 
	TelemType ch;
	scanf("%c",&ch);
	if(ch == none ) T = NULL;
	else{
		T = new Binode;
		T->data = ch;
		createBiTree(T->left);
		createBiTree(T->right);
	}
	return 1;	
}
// 按照先序遍历二叉树 
int preOrder(Bitree T){
	if(T){
		printf("%c",T->data);
		preOrder(T->left);
		preOrder(T->right);
		return 1;
	} else return 0;
}
//中序遍历
int inOrder(Bitree T){
	if(T){
		if(T->left) inOrder(T->left);
		printf("%c",T->data);
		if(T->right) inOrder(T->right);
		return 1;
	} else return 0;
} 
//后序遍历
int postOrder(Bitree T){
	if(T){
		if(T->left) postOrder(T->left);
		if(T->right) postOrder(T->right);
		printf("%c",T->data);
		return 1;
	}return 0;
} 
//非递归中序遍历 
int inOrderTraverse1(Bitree T){
	stack<Bitree> s;  //存放节点指针
	s.push(T);
	while(! s.empty()){
		while(s.top()) s.push(s.top()->left);  //向左走到尽头
		s.pop(); //空指针退栈
		if(!s.empty()){
			Bitree t = s.top();
			s.pop(); // 根节点出栈 
			printf("%c",t->data);
			s.push(t->right);
		}		
	 }
	return 1;
} 
int inOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T; //当前遍历节点 
	while(!s.empty() || p){
		 if(p){
		 	s.push(p); //根节点进栈
			p = p->left; //遍历左子树 
		 }else{
		 	p = s.top();
		 	s.pop(); //返回上一根节点
			printf("%c",p->data);
			p = p->right; // 遍历右节点  
		 }
	}
	return 1; 
}
 
//非递归前序遍历
int preOrderTraverse1(Bitree T){
	stack<Bitree> s;
	s.push(T);
	Bitree p;
	while(!s.empty()){
		while(s.top()){
			p = s.top();
			printf("%c",p->data); //先访问根
			s.push(p->left);//向左走到尽头 
		} 
		s.pop(); //空指针出栈 
		if(!s.empty()){
			p = s.top(); //退回上一层的根节点
			s.pop();
			s.push(p->right);
		} 
	} 
	return 1;
} 

int preOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T; //当前访问节点
	while(!s.empty() || p) {
		if(p){
			printf("%c",p->data);
			s.push(p);
			p = p->left; //访问左节点
		}else{
			p = s.top(); //返回上一根节点
			s.pop();
			p = p -> right; 
		}
	}
	return 1;
}
// 非递归先序遍历,按照节点的访问顺序压栈 根-左-右
void preOrderTraverse3(Bitree T){
 	stack<Bitree> s;
 	while(T){ 
		printf("%c",T->data);//先访问根
		//先把右子树压栈,
		//把左子树访问之后再访问右子树 
		if(T->right) s.push(T->right);
		T = T->left; //类似递归访问左子树
		if(T == NULL && !s.empty()){ //如果左子树为空,则访问右子树 
			T = s.top();
			s.pop(); 
		} 		
	}
 }
//非递归后序遍历
/*
* 1.如果是叶子节点,则输出
* 2.如果左子树访问完,且右子树为空,则输出
* 3.如果左右子树访问完,则输出
* 4.否则将右、左子树依次压栈
* 用last表示最后一个访问过的节点 
*/
 int postOrderTraverse1(Bitree T){
 	stack<Bitree> s;
 	Bitree p; //当前访问节点
	Bitree last; //最后一个访问过的节点 
	last = p = T; 
	s.push(T);
	while(!s.empty()){
		p = s.top();
		if((p->left==NULL && p->right == NULL)
		|| (p->right == NULL && last == p->left)
		|| (last == p->right)){
			printf("%c",p->data);
			s.pop();
			last = p;
		}else{
			if(p->right) s.push(p->right);
			if(p->left) s.push(p->left);
		}
	} 
	return 1;
 }
void postOrderTraverse2(Bitree T){
	stack<Bitree> s;
	Bitree p = T;
	s.push(p); s.push(p);
	while(!s.empty()){
		p = s.top();  s.pop(); //取栈顶根节点
		if(p == s.top() && !s.empty()){ //表示该节点的孩子结点还为访问 
			if(p->right){
				s.push(p->right); s.push(p->right);
			}
			if(p->left){
				s.push(p->left); s.push(p->left);
			}
		}
		else printf("%c",p->data); //第二次接触,表示孩子结点已经访问完
	}
}
//设置标志位
typedef struct flagNode{
	int flag;  //是不是第一次出现再栈顶 
	Binode* bnode;
}flagNode; 
void postOrderTraverse3(Bitree T){
	stack<flagNode*> s;
	Bitree p = T;
	flagNode* temp;
	while(p || !s.empty()){
		while(p){ //向左走到尽头 
			flagNode* fnode = new flagNode;
			fnode->bnode = p;
			fnode->flag = 1;
			s.push(fnode); 
			p = p->left;
		}
		if(!s.empty()){
			temp = s.top(); s.pop();
			if(temp->flag){//第一次
				temp->flag = 0;
				s.push(temp);
				p = temp->bnode->right; //访问右子树 
			}
			else{
				printf("%c",temp->bnode->data);
				p = NULL; //退回上一层 
			}
		}
	}
}
int main(){
	//先序输入:abc##de#g##f###
	Bitree T;
	createBiTree(T);
	cout<<"先序遍历:";
	preOrder(T);
	cout<<"\n后序遍历:";
	postOrder(T);
	cout<<"\n中序遍历:";
	inOrder(T);
	cout<< endl; 
	cout<<"\n非递归先序遍历:";
	preOrderTraverse1(T);
	cout<<"\n非递归后序遍历:";
	postOrderTraverse1(T);
	cout<<"\n非递归中序遍历:";
	inOrderTraverse1(T);
	
	return 0;
}

你可能感兴趣的:(====,数据结构与算法,====)