二叉树各种遍历算法

二叉树是许多算法题的常用结构,其遍历算法的各种变种就是各种题目。具体的顺序如下:

  • 先序遍历:先根、后左、再右
  • 中序遍历:先左、后根、再右
  • 后序遍历:先左、后右、再根

递归版先序、中序、后序遍历

最简单、最直接的版本

#include <iostream>
using namespace std;

// 二叉树节点
struct Node
{
	int value;
	struct Node* left;
	struct Node* right;
	Node(int data) : value(data), left(nullptr), right(nullptr) {};
};

// 先序遍历
void PreOrder(Node* head)
{
	if (head == nullptr)
		return;
	cout << head->value << " ";
	PreOrder(head->left);
	PreOrder(head->right);
}

// 中序遍历
void InOrder(Node* head)
{
	if (head == nullptr)
		return;
	InOrder(head->left);
	cout << head->value << " ";
	InOrder(head->right);
}

// 后序遍历
void PosOrder(Node* head)
{
	if (head == nullptr)
		return;
	PosOrder(head->left);
	PosOrder(head->right);
	cout << head->value << " ";
}

int main()
{
	Node* n1 = new Node(1);
	Node* n2 = new Node(2);
	Node* n3 = new Node(3);
	Node* n4 = new Node(4);
	Node* n5 = new Node(5);
	n1->left = n2;
	n1->right = n3;
	n2->left = n4;
	n2->right = n5;
	PreOrder(n1);
	cout << endl;
	InOrder(n1);
	cout << endl;
	PosOrder(n1);
	cout << endl;
	delete n1;
	delete n2;
	delete n3;
	delete n4;
	delete n5;
	system("pause");
    return 0;
}

非递归版本

用递归方法解决的问题都能用非递归的方法来做,因为递归无非是使用了系统的函数栈来保存信息。我们自己申请一个栈来代替函数栈即可实现非递归方法。

先序遍历

先序遍历比较容易理解,首先将根节点入栈。从栈中取出栈顶节点,打印该点,接着先将右孩子入栈,再将左孩子入栈(因为栈的特点是先进后出,要先遍历左孩子就得后入栈)。不断重复该步骤直至栈为空。

void PreOrderUnRecur(Node* head)
{
	if (head)
	{
		stack<Node*> stack;
		stack.push(head);
		Node* cur;
		while (!stack.empty())
		{
			cur = stack.top();
			stack.pop();
			cout << cur->value << " ";
			// 右孩子先进栈,后出
			if (cur->right)
				stack.push(cur->right);
			if (cur->left)
				stack.push(cur->left);
		}
	}
}

中序遍历

令cur等于head
步骤1:先把cur入栈,然后不停让cur=cur->left,重复此步骤。即把cur下的所有左孩子节点入栈。直到cur为空。
步骤2:从栈中弹出栈顶给cur,打印该节点,然后令cur=cur->right,回到步骤2。
步骤3:当栈为空时且cur为空时,过程结束。
入栈的顺序可参考下图:
《二叉树各种遍历算法》

void InOrderUnRecur(Node* head)
{
	if (head)
	{
		stack<Node*> stack;
		Node* cur = head;
		while (!stack.empty() || cur != nullptr)
		{
			while (cur != nullptr)
			{
				stack.push(cur);
				cur = cur->left;
			}

			cur = stack.top();
			stack.pop();
			cout << cur->value << " ";
			cur = cur->right;
		}
	}
}

后序遍历

方法一:

申请两个栈s1、s2,先将头节点入栈s1。从s1中弹出栈顶节点记为cur,然后依次将cur的左孩子和右孩子压入s1。在这过程中每一个从s1中弹出的节点均压入s2。当s1为空后,从s2中依次弹出的节点便是后序遍历的次序。
主要就是利用两个栈来进行“倒腾“,压入s2的顺序为中、右、左,弹出的顺序就变成了左、右、中。

void PosOrderUnRecur(Node* head)
{
	if (head)
	{
		stack<Node*> s1, s2;
		s1.push(head);
		Node* cur;
		while (!s1.empty())
		{
			cur = s1.top();
			s1.pop();
			s2.push(cur);
			if (cur->left)
				s1.push(cur->left);
			if (cur->right)
				s1.push(cur->right);
		}
		while (!s2.empty())
		{
			cout << s2.top()->value << " ";
			s2.pop();
		}
	}
}

方法二:
申请一个栈,将头节点压入栈。
现在我们思考一下:当遍历到一个节点时,如何判断这次遍历是打印该点,还是先处理它的孩子呢?
有以下几种情况:

  1. 该节点的左右孩子皆为空,即该节点为叶子节点,那么这次遍历就是打印该点。
  2. 如果上一次打印的节点为该节点的右孩子,说明该节点的子树处理完毕,这次遍历就是打印该点。
  3. 如果上一次打印的节点为该节点的左孩子,且该节点的右孩子为空,说明该节点的子树处理完毕,这次遍历就是打印该点。
  4. 否则说明子树没有被访问过,按照右孩子、左孩子的顺序入栈。
void PosOrderUnRecur(Node* head)
{
	if (head)
	{
		stack<Node*> stack;
		stack.push(head);
		Node* last = nullptr;
		Node* top;
		while (!stack.empty())
		{
			top = stack.top();
			if ((top->left == nullptr && top->right == nullptr) || // 叶子节点
				(top->right == nullptr && last == top->left) || // 上个访问为左孩子,且右孩子为空
				last == top->right) // 上个访问为右孩子
			{
				cout << top->value << " ";
				last = top;
				stack.pop();
			}
			else
			{
				if (top->right)
					stack.push(top->right);
				if (top->left)
					stack.push(top->left);
			}
		}
	}
}

层次遍历

利用队列来做。

void LevelOrder(Node* head)
{
	if (head)
	{
		queue<Node*> queue;
		queue.push(head);
		Node* cur;
		while (!queue.empty())
		{
			cur = queue.front();
			queue.pop();
			cout << cur->value << " ";
			if (cur->left)
				queue.push(cur->left);
			if (cur->right)
				queue.push(cur->right);
		}
	}
}
    原文作者:silence1772
    原文地址: https://blog.csdn.net/silence1772/article/details/83623336
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞