C++高级数据结构算法 | 二叉树的四种遍历算法详解(递归与非递归实现)

版权声明:本文为博主整理文章,未经博主允许不得转载。 https://blog.csdn.net/ZYZMZM_/article/details/90649429


二叉树的遍历( t r a v e r s i n g    b i n a r y    t r e e traversing \ \ binary \ \ tree )是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次

这里有两个关键词:访问次序

访问其实是要根据实际的需要来确定做什么,比如对每个结点进行相关计算,输出打印等,它算是一个抽象操作。在这里我们可以简单地假定就是输出结点的数据信息。

二叉树的遍历次序不同于线性结构,一般的线性结构最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择,由于选择方式的不同,遍历的次序也就完全不同了。


二叉树遍历方法

二叉树的遍历方式可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为四种:

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

前序遍历

规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树

如下图,遍历顺序为:ABDGHCEIF。


接下来我们以下图为例对前序遍历过程做以分析:

遍历步骤:

  • 先访问根节点A,结果为A
  • 前序遍历根节点A的左子树,左子树的根节点是B,此时结果为AB
  • 继续前序遍历以B为根节点的左子树,此时的根节点是D,结果为ABD
  • 继续遍历以D为根节点的左子树H,此时结果为ABDH
  • 节点H不存在左子树,则遍历以D为根节点的右子树,此时结果为ABDHI
  • 递归遍历以B为根节点右子树E,由于E存在左子树J,此时结果为ABDHIE
  • E存在左子树,此时结果为ABDHIEJ。此时根节点A的左子树遍历完成,开始遍历右子树
  • 右子树根节点为C,此时结果为ABDHIEJC
  • 以C为根节点的子树存在左节点F,结果变为ABDHIEJCF
  • 节点F不存在子节点,遍历C的右节点,前序遍历最后结果为ABDHIEJCFG

中序遍历

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

如下图,遍历的顺序为:GDHBAEICF


接下来我们以下图为例对中序遍历过程做以分析:

遍历步骤:

  • 根节点A存在左子树,先中序遍历以节点B为根节点的左子树
  • 节点B存在左子树,中序遍历以节点D为根节点的左子树
  • 节点D存在左叶子节点H,此时结果为HD
  • 节点D存在右叶子节点I,此时结果为HDI
  • 以节点B为根节点的左子树遍历完成,此时访问根节点B,结果为HDIB
  • 访问B节点的右子树,右子树根节点存在左子树J,此时结果为HDIBJE
  • 根节点A的左子树访问完成,访问根节点A,结果变为HDIBJEA
  • 遍历根节点A的右子树,右子树存在左叶子节点F,此时结果为HDIBJEAF
  • 然后访问右子树的根节点C,中序遍历最后结果为HDIBJEAFCG

后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

如下图,遍历的顺序为:GHDBIEFCA


接下来我们以下图为例对后序遍历过程做以分析:

遍历步骤:

  • 根节点A存在左子树,先后序遍历以节点B为根节点的左子树
  • 节点B存在左子树,后序遍历以节点D为根节点的左子树
  • 节点D存在左叶子节点H,右叶子节点I,此时结果为HID
  • 节点B存在右子树,后序遍历以节点E为根节点的右子树
  • 节点E存在左叶子节点,不存在右叶子节点,此时结果为HIDJE
  • B节点的左子树、右子树遍历完成,访问B节点,结果为HIDJEB
  • 根节点的左子树遍历完成,后序遍历根节点的右子树,结果为HIDJEBFGC
  • 最后访问根节点,后序遍历最后结果为HIDJEBFGCA

层序遍历

规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

如下图,遍历的顺序为:ABCDEFGHI


接下来我们以下图为例对层序遍历过程做以分析:

遍历步骤:

  • 从第一层根节点开始访问,结果为A
  • 访问第二层,从左到右,结果为ABC
  • 访问第三层,结果为ABCDEFG
  • 访问第四层,层序遍历结果为ABCDEFGHIJ

前序遍历算法

递归实现

/*
 * 前序遍历的递归实现,我们直接根据定义,首先先访问根节点,
 * 然后前序遍历左子树,接着前序遍历右子树。
 * 上述的每个前序遍历就是一个递归的过程。
 */
void preOrder()
{
	preOrder(_root);
	cout << endl;
}

void preOrder(BSTNode* node)
{
	if (node != nullptr)
	{
		cout << node->_data << " ";
		preOrder(node->_left);
		preOrder(node->_right);
	}
}

非递归实现

/*
 * 前序遍历的非递归实现,我们需要借助栈这个数据结构,由于前序遍历是
 * 先访问根节点,然后前序遍历左子树,接着前序遍历右子树。
 * 那么非递归实现的话,我们先压入根节点,然后打印栈顶元素,但是由于
 * 出栈和入栈的顺序是相反的,因此我们接着需要先压入右孩子,再压入左孩子
 * 然后打印栈顶元素,继续压入左孩子的右孩子,左孩子的左孩子···
 * 上述便是迭代循环的过程。
 */
void nonpreOrder()
{
	if (_root == nullptr)
	{
		return;
	}

	stack <BSTNode*> stack;

	stack.push(_root);

	while (!stack.empty())
	{
		BSTNode* top = stack.top();
		cout << top->_data << " ";
		stack.pop();

		if (top->_right != nullptr)
		{
			stack.push(top->_right);
		}

		if (top->_left != nullptr)
		{
			stack.push(top->_left);
		}
	}
	cout << endl;
}

中序遍历算法

递归实现

/*
 * 中序遍历的递归实现,根据定义,我们首先中序遍历左子树,
 * 再打印根节点,然后再中序遍历右子树。
 * 上述的每个中序遍历就是一个递归的过程。
 */
void inOrder()
{
	inOrder(_root);
	cout << endl;
}

void inOrder(BSTNode* node)
{
	if (node != nullptr)
	{			
		inOrder(node->_left);
		cout << node->_data << " ";
		inOrder(node->_right);
	}
}

非递归实现

/*
 * 中序遍历的非递归实现,我们首先应该一直向左遍历,将结点压栈,
 * 直到结点为空,然后我们打印该节点值,并将其出栈,并继续遍历
 * 该节点的右子树,继续上述过程。
 */
void noninOrder()
{
	if (_root == nullptr)
	{
		return;
	}
	
	stack<BSTNode*> stack;

	BSTNode* top = _root;

	while (!stack.empty() || top != nullptr)
	{
		if (top != nullptr)
		{
			stack.push(top);
			top = top->_left;
		}
		else
		{
			top = stack.top();
			cout << top->_data << " ";
			stack.pop();
			top = top->_right;
		}
	}
	cout << endl;
}

后序遍历算法

递归实现

/*
 * 后序遍历的递归实现,我们只需先后序遍历左子树,再后序遍历右子树
 * 最后打印根节点值即可。
 * 上述的每个后序遍历就是一个递归的过程。
 */
void lastOrder()
{
	lastOrder(_root);
	cout << endl;
}

void lastOrder(BSTNode* node)
{
	if (node != nullptr)
	{
		lastOrder(node->_left);
		lastOrder(node->_right);
		cout << node->_data << " ";
	}
}

非递归实现

/*
 * 后序遍历的非递归实现,由于其根节点最后打印,因此我们需要借助两个栈来完成,
 * 因为我们需要借助一个辅助栈来保存其父节点,而另一个栈则保存我们的结果集。
 * 注意压栈顺序,本来我们应该先压右孩子,再压左孩子。但是我们使用另一个结果栈
 * 来存储结果集,辅助栈中的元素最终是出栈并压入结果栈的,因此,我们程序中
 * 应该先压入左孩子,再压入右孩子。
 * 然后每次循环将栈顶元素出栈并压入结果栈中。
 * 最后,结果栈中所存储的就是我们所需的后序遍历结果集。我们打印出栈中所有的
 * 元素即可
 */
void nonlastOrder()
{
	if (_root == nullptr)
	{
		return;
	}

	stack<BSTNode*> Stack;
	stack<BSTNode*> StackRes;

	BSTNode* top = _root;
	Stack.push(top);

	while (!Stack.empty())
	{
		top = Stack.top();
		StackRes.push(top);
		Stack.pop();

		if (top->_left != nullptr)
		{
			Stack.push(top->_left);
		}

		if (top->_right != nullptr)
		{
			Stack.push(top->_right);
		}
	}

	while (!StackRes.empty())
	{
		top = StackRes.top();
		cout << top->_data << " ";
		StackRes.pop();
	}
	cout << endl;
}

层序遍历算法

递归实现

/*
 * 层序遍历不同于之前讲解的前序、中序、后序遍历的深度优先遍历,而它是一种典型的
 * 广度优先遍历算法,那么对于层序遍历的递归实现,我们必须要获取到该树的层数,
 * 才能对递归进行一个有效的控制
 * 整体思路就是,我们在API接口中,向递归函数循环传入层数(0-n)
 * 在递归函数中,层数为零,我们直接打印其值即可,若层数不为零,我们就递归
 * 地继续向下遍历。
 * 如何向下继续遍历?我们每次递归遍历就将层数减一即可,那么比如我们遍历第3层
 * 元素,那么传入的参数为3,我们递归调用函数,不断的将参数减一,当减到0时便是
 * 已经遍历到第三层了,参数减到0,递归结束条件满足,我们直接打印其值即可。
 */
void levelOrder()
{
	int level = Treelevel(); // 求层数
	for (int i = 0; i < level; ++i)
	{
		levelOrder(_root, i);
	}
}

void levelOrder(BSTNode* node, int level)
{
	if (node == nullptr)
	{
		return;
	}
	if (level == 0)
	{
		cout << node->_data << " ";
	}
	else
	{
		
		levelOrder(node->_left, level - 1);
		levelOrder(node->_right, level - 1);
	}
}

int Treelevel(BSTNode* node)
{
	if (node == nullptr)
	{
		return 0;
	}

	int left = Treelevel(node->_left);
	int right = Treelevel(node->_right);

	return (left > right ? left : right) + 1;
}

非递归实现

/*
 * 层序遍历的非递归实现,正常思路我们先遍历左子树,再遍历右子树,由于是
 * 层序遍历,从上到下,从左到右,那么从左到右刚好满足队列的性质,先入先出
 * 那么我们需要借助一个队列来保存我们遍历到的元素。
 * 首先压入根节点。
 * 每次遍历我们拿到队列的首元素,然后遍历其左孩子,并入队;然后遍历其右孩子
 * 并入队,然后将队首元素打印并出队,继续迭代直到队列为空。
*/
void nonlevelOrder()
{
	queue<BSTNode *> Nodequeue;
	
	if (_root == nullptr)
	{
		return;
	}
	
	Nodequeue.push(_root);
	
	while (!Nodequeue.empty())
	{
		BSTNode* front = Nodequeue.front();
		
		if (front->_left != nullptr)
		{
			Nodequeue.push(front->_left);
		}

		if (front->_right != nullptr)
		{
			Nodequeue.push(front->_right);
		}
		  
		cout << front->_data << " ";
		Nodequeue.pop();
	}
	cout << endl;
}

猜你喜欢

转载自blog.csdn.net/ZYZMZM_/article/details/90649429