左神算法基础class4—题目1实现二叉树的先序、中序、后序遍历,包括递归方式和非递归方式

题目:实现二叉树的先序、中序、后序遍历,包括递归方式和非递归方式

1.数据结构

使用二叉链表作为树的存储方式,设计一个数据域、两个指针域(指向左右孩子)。

class Tree
{
public:
	int data;
	Tree* left;
	Tree* right;
	Tree(int x){this->data = x;right = NULL; left = NULL;}//构造函数
};

初始化如下图所示的树

在这里插入图片描述

	Tree *head = new Tree(1);//new返回的是指针,所以左侧定义节点指针变量作为头节点
	head->left = new Tree(2);
	head->left->left = new Tree(4);
	head->left->right = new Tree(5);
	head->right = new Tree(3);
	head->right->left = new Tree(6);
	head->right->right= new Tree(7);

2.先序遍历

(1)递归版

先打印节点信息,再整颗遍历左子树,最后右子树,对于如上图的树,输出1,2,4,5,3,6,7

void preOrderRecur(Tree* head)
{
	if(head == NULL)
		return;
	cout<<head->data<<" ";
	preOrderRecur(head->left);
	preOrderRecur(head->right);
}

(2)非递归版

为什么使用栈?
使用栈作为辅助的存储结构,为什么要用到栈,原因是树的数据结构只能够不断查找子树,而不能返回根节点,而从先序遍历的结果来看,对于子树和根都需要访问到而且并不是先跟在子树的访问方式,所以需要用栈来暂存一些节点,达到逆向访问的目的。
思路:
由于先序遍历是中左右的输出顺序,那么使用栈需要先压入右侧子树再压入左侧子树。具体来说首先压入根节点,在栈不空的情况下进行如下循环,先弹出一个栈里的元素,再分别压入弹出元素的右子树和左子树。
具体过程:
①压入根节点1进入循环
②弹出1,压入3,2(输出1)
②弹出2,压入5,4(输出1,2)
③弹出4,4没有子树,不压入(输出1,2,4)
④弹出5,5没有子树,不压入(输出1,2,4,5)
⑤弹出3,压入7,6(输出1,2,4,5,3)
⑥弹出6,6没有子树,不压入(输出1,2,4,5,3,6)
⑦弹出7,7没有子树,不压入(输出1,2,4,5,3,6,7)
⑧栈空结束循环,结束
核心代码:

void preOrderUnRecur(Tree* head)
{
	//由于需要获取弹出栈的左右子树压入,
	//所以需要把栈设计成树的指针的数据结构
	//不能设计成int型,会丢失子树信息
	stack<Tree*> t;

	t.push(head);//压入根节点
	while(!t.empty())//栈不为空时
	{
		head = t.top();//把栈顶指针(要弹出的数)存储,用来寻找左右子树
		cout<<t.top()->data<<" ";
		t.pop();
		//先压入右再压入左
		if(head->right!=NULL)
			t.push(head->right);
		if(head->left!=NULL)
			t.push(head->left);
	}
}

(3)完整代码

#include<iostream>
#include<stack>
using namespace std;
class Tree
{
public:
	int data;
	Tree* left;
	Tree* right;
	//构造函数
	Tree(int x){this->data = x;right = NULL; left = NULL;}
};

void preOrderRecur(Tree* head)
{
	if(head == NULL)
		return;
	cout<<head->data<<" ";
	preOrderRecur(head->left);
	preOrderRecur(head->right);
}
void preOrderUnRecur(Tree* head)
{
	//由于需要获取弹出栈的左右子树压入,
	//所以需要把栈设计成树的指针的数据结构
	//不能设计成int型,会丢失子树信息
	stack<Tree*> t;

	t.push(head);//压入根节点
	while(!t.empty())
	{
		head = t.top();//把栈顶指针(要弹出的数)存储,用来寻找左右子树
		cout<<t.top()->data<<" ";
		t.pop();
		//先压入右再压入左
		if(head->right!=NULL)
			t.push(head->right);
		if(head->left!=NULL)
			t.push(head->left);
	}
}
int main()
{
	Tree *head = new Tree(1);//new返回的是指针,所以左侧定义节点指针变量作为头节点
	head->left = new Tree(2);
	head->left->left = new Tree(4);
	head->left->right = new Tree(5);
	head->right = new Tree(3);
	head->right->left = new Tree(6);
	head->right->right= new Tree(7);
	//preOrderRecur(head);
	preOrderUnRecur(head);
	system("pause");
	return 0;
}

2.中序遍历

(1)递归版

头节点为空直接返回,先整颗遍历左子树,再当前节点,最后右子树,对于如上图的树,输出4,2,5,1,6,3,7

void inOrderRecur(Tree* head)
{
	if(head == NULL)
		return;
	inOrderRecur(head->left);
	cout<<head->data<<" ";
	inOrderRecur(head->right);
}

(2)非递归版

思路:
在栈不空或者当前节点不为空的情况下进行循环,当前节点不为空时,入栈并把当前节点指向其左子树;当前节点为空时,出栈,当前节点指向出栈数的右子树。
具体过程:
①当前节点指向1,不为空,压入1,指向1的左子树2
②当前节点指向2,不为空,压入2,指向2的左子树4
③当前节点指向4,不为空,压入4,指向4的左子树NULL
④当前节点指向NULL,空,出栈4,指向4的右子树NULL(输出4)
⑤当前节点指向NULL,空,出栈2,指向2的右子树5(输出4,2)
⑥当前节点指向5,不为空,压入5,指向5的左子树NULL
⑦当前节点指向NULL,空,出栈5,指向5的右子树NULL(输出4,2,5)
⑧当前节点指向NULL,空,出栈1,指向1的右子树3(输出4,2,5,1)
⑨当前节点指向3,不为空,压入3,指向3的左子树6
⑩当前节点指向6,不为空,压入6,指向6的左子树NULL
当前节点指向NULL,空,出栈6,指向6的右子树NULL(输出4,2,5,1,6)
当前节点指向NULL,空,出栈3,指向3的右子树7(输出4,2,5,1,6,3)
当前节点指向7,不为空,压入7,指向7的左子树NULL
当前节点指向NULL,空,出栈7,指向7的右子树NULL(输出4,2,5,1,6,3,7)
当前节点为空且栈为空,退出循环结束。
核心代码:

void inOrderUnRecur(Tree* head)
{
	stack<Tree*> t;
	while(!t.empty() || head != NULL)
	{
		//当前节点不为空,入栈并把当前节点指向其左子树
		if(head != NULL)
		{
			t.push(head);
			head = head->left;
		}
		//当前节点为空时,出栈,当前节点指向出栈数的右子树
		else
		{
			head = t.top();
			cout<<t.top()->data<<" ";
			t.pop();
			head = head->right;
		}
	}
}

(3)完整代码

#include<iostream>
#include <stack>
using namespace std;
class Tree
{
public:
	int data;
	Tree* left;
	Tree* right;
	Tree(int x)
	{
		this->data = x;
		this->left = NULL;
		this->right = NULL;
	}
};

void inOrderRecur(Tree* head)
{
	if(head == NULL)
		return;
	inOrderRecur(head->left);
	cout<<head->data<<" ";
	inOrderRecur(head->right);
}

void inOrderUnRecur(Tree* head)
{
	stack<Tree*> t;
	while(!t.empty() || head != NULL)
	{
		//当前节点不为空,入栈并把当前节点指向其左子树
		if(head != NULL)
		{
			t.push(head);
			head = head->left;
		}
		//当前节点为空时,出栈,当前节点指向出栈数的右子树
		else
		{
			head = t.top();
			cout<<t.top()->data<<" ";
			t.pop();
			head = head->right;
		}
	}
}

int main()
{
	Tree *head = new Tree(1);
	head->left = new Tree(2);
	head->left->left = new Tree(4);
	head->left->right = new Tree(5);
	head->right = new Tree(3);
	head->right->left = new Tree(6);
	head->right->right= new Tree(7);
	inOrderUnRecur(head);
	//inOrderRecur(head);
	system("pause");
	return 0;
}

3.后序遍历

(1)递归版

先遍历整颗左子树,再右子树,最后打印节点信息,对于如上图的树,输出4,5,2,6,7,3,1

void PosOrderRecur(Tree* head)
{
	if(head==NULL)
		return;
	PosOrderRecur(head->left);
	PosOrderRecur(head->right);
	cout<<head->data<<" ";
}

(2)非递归版

思路:
由于后序是左右中的顺序,而先序是中左右的顺序,考虑用先序代码修改为后序遍历
具体过程:
先序非递归版是先压右后压左入栈得到中左右的,把压栈顺序调整为先压左后压右可变为中右左,再把结果放入一个栈中,最后弹出就得到左右中的后序遍历。
核心代码:

void PosOrderUnRecur(Tree* head)
{
	stack<Tree*> t;
	stack<Tree*> t1;
	t.push(head);
	while(!t.empty())
	{
		head = t.top();
		//cout<<t.top()->data<<" ";
		t1.push(t.top());
		t.pop();
		if(head->left!=NULL)
		{
			t.push(head->left);
		}
		if(head->right!=NULL)
		{
			t.push(head->right);
		}
	}
	//出栈
	while(!t1.empty())
	{
		cout<< t1.top()->data<<" ";
		t1.pop();
	}
}

(3)完整代码

#include<iostream>
#include<stack>
using namespace std;

class Tree
{
public:
	int data;
	Tree* right;
	Tree* left;
	Tree(int x)
	{
		this->data = x;
		this->right = NULL;
		this->left = NULL;
	}
};

void PosOrderRecur(Tree* head)
{
	if(head==NULL)
		return;
	PosOrderRecur(head->left);
	PosOrderRecur(head->right);
	cout<<head->data<<" ";
}
void PosOrderUnRecur(Tree* head)
{
	stack<Tree*> t;
	stack<Tree*> t1;
	t.push(head);
	while(!t.empty())
	{
		head = t.top();
		//cout<<t.top()->data<<" ";
		t1.push(t.top());
		t.pop();
		if(head->left!=NULL)
		{
			t.push(head->left);
		}
		if(head->right!=NULL)
		{
			t.push(head->right);
		}
	}
	//出栈
	while(!t1.empty())
	{
		cout<< t1.top()->data<<" ";
		t1.pop();
	}
}

int main()
{
	Tree *head = new Tree(1);
	head->left = new Tree(2);
	head->left->left = new Tree(4);
	head->left->right = new Tree(5);
	head->right = new Tree(3);
	head->right->left = new Tree(6);
	head->right->right= new Tree(7);

	//PosOrderRecur(head);
	PosOrderUnRecur(head);

	system("pause");
	return 0;
}

4.先序、中序、后序的另一种理解

在这里插入图片描述
对于上述树,访问顺序如下图为
1,2,4,4,4,2
5,5,5,2,1
3,6,6,6,3
7,7,7,3,1
其中444等连续三个数表示的是访问4的左右节点。
在这里插入图片描述
对于访问顺序来说,每个节点都被访问三次。
先序遍历实际是打印第一次访问到这个节点:如图1,2,4,5,3,6,7
中序遍历实际是打印第二次访问到这个节点:4,2,5,1,6,3,7
后序遍历实际是打印第三次访问到这个节点:4,5,2,6,7,3,1

发布了51 篇原创文章 · 获赞 1 · 访问量 1376

猜你喜欢

转载自blog.csdn.net/shi_xiao_xuan/article/details/103868153