栈实现二叉树的先,中,后序遍历

栈实现二叉树先,中,后序遍历

如果是使用递归来实现二叉树的先,中,后序遍历只需要更改三行代码的位置,但若是使用栈来写那便会有趣得多

根结点与其左右子树间的输出优先级

graph TD
1((根结点))---2((左子树))
1---3((右子树))

遍历方式 输出优先级
先序 根结点>左子树>右子树
中序 左子树>根结点>右子树
后序 左子树>右子树>根节点

使用栈的规则

栈内元素是指向结点的指针

  1. 只有栈顶元素指向的结点内容才会被输出

    • 方便,不用记忆太多结点
  2. 结点内容输出后指向该结点的指针会立即出栈
    • 避免结点重复输出
  3. 当一个指针出栈时,如果该指针指向的结点的左右子树有未被使用过(未遍历过,或者不为空),那么指向其左右子树的指针将随后入栈,且指向后输出的结点的指针早入栈
    • 入栈是避免信息的丢失,顺序是栈的特性(先进后出)
  4. 栈中初始化只有一个指向根节点的指针
    • 遍历往往以根节点作为基础参数
  5. 当栈为空时遍历结束
    • 即遍历完所有结点(可以由2,3推导出来)

算法

无论是根节点还是左右子树在栈中的表示方式是一样的都是指针,所以我们可以将左右子树各当作是"一个结点",这样问题就简化为具有3个结点的完全二叉树的输出了

在简化的基础上再考虑上特殊情况规则就可以实现了

通用逻辑栈中初始化只有一个指向根节点的指针,当栈为空时遍历结束

先序遍历

推导

graph TD
1((根结点))---2((左子树))
1---3((右子树))

  • 输出顺序为:根结点->左子树->右子树
  • 结合规则说明
    1. 根结点优先输出,结合规则2可得出栈顶指针指向的结点直接输出,栈顶指针会立即被出栈(提前备份)
    2. 栈顶指针出栈,结合规则3可得出备份指向结点的左子树和右子树的指针会被入栈(非空情况下),且右子树先于左子树入栈
  • 综上所述可得出规律
    1. 先输出栈顶指针指向的结点
    2. 栈顶指针出栈(栈顶指针备份,否则左右子树信息丢失)
    3. 备份指针指向结点的右子树入栈(如果右子树指针不为空)
    4. 备份指针指向结点的左子树入栈(如果左子树指针不为空)
    5. 回到步骤1

C++代码实现

	void preorder_travel() {
		if (root == NULL)
			return;
		std::stack<Node*> s;
		s.push(root);//根指针入栈
		while (!s.empty())
		{
			std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
			Node* temp = s.top();//备份栈顶指针
			s.pop();//栈顶指针出栈
			if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
			if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
		}
	}

中序遍历

推导

graph TD
1((根结点))---2((左子树))
1---3((右子树))

  • 输出顺序为:左子树->根结点->右子树
  • 结合规则说明
    1. 左子树优先输出,其次是根节点,但子树不可能直接输出,所以一直搜索栈顶指针指向结点的左子树的左子树的...,直到没有找到没有左子树的结点,然后输出该结点(作为根节点),结合规则2可得出重复压入栈顶指向结点的左子树指针,直到栈顶指向结点的左子树指针为空,然后输出该结点,随后栈顶指针出栈(提前备份)
    2. 由步骤1可得出栈顶指针总是次栈顶指针指向结点的左子树,且左子树(原栈顶指针)输出之后到根结点(现栈顶指针)指向结点输出,结合规则2可得出重复输出栈顶指针指向的结点(输出之前备份,输出之后出栈)
      • 输出栈顶指针,结合规则3可得出若是备份指向结点的右子树存在,则将其入栈

        • 右子树入栈打破2中的前提(栈顶指针总是次栈顶指针指向结点的左子树),所以2停止
    3. 回到到步骤1
  • 综上所述可得出规律
    1. 重复压入栈顶结点左子树指针,直到栈顶结点左子树指针为空,然后输出该结点,栈顶指针出栈(栈顶指针备份,否则左右子树信息丢失)
    2. 重复输出栈顶指针指向的结点(输出之前备份,输出之后出栈),直到备份结点右子树不为空,然后将其右子树入栈,回到1

C++代码实现

void inorder_travel() {
		if (root == NULL)
			return;
		std::stack<Node*> s;
		s.push(root);//根指针入栈
		while (!s.empty())
		{
			while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
			{
				s.push(s.top()->L);
			}
			while (!s.empty())//重复检查
			{
				std::cout << s.top->data << " ";//输出栈顶指针指向的结点
				Node* temp = s.top();//备份栈顶指针
				s.pop();//栈顶指针出栈
				if (temp->R != NULL) {//如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
					s.push(temp->R);
					break;
				}
			}
		}
}

后序遍历

推导

graph TD
1((栈顶结点))---2((左子树))
1---3((右子树))

  • 输出顺序为:左子树->右子树->栈顶结点
  • 结合规则说明
    1. 左子树优先输出,其次是右子树,但子树不可能直接输出,所以一直搜索栈顶指针指向结点的左子树的左子树的...,直到没有找到没有左子树的结点,结合规则2可得出重复压入栈顶指向结点的左子树指针,直到栈顶指向结点的左子树指针为空
    2. 由步骤1可得出栈顶指针总是次栈顶指针指向结点的左子树,且左子树(原栈顶指针)输出之后到右子树(现栈顶指针指向结点的右子树)输出
      • 在步骤2前提下反复检查

        • 若是栈顶指针指向结点的右子树遍历过(使用一个last来标记上一次输出的结点指针),输出栈顶指针,栈顶出栈
        • 若是栈顶指针指向结点的右子树为空,输出栈顶指针,栈顶出栈
        • 若是栈顶指针指向结点的右子树不为空,由输出顺序可得出右子树入栈
          • 右子树入栈打破2中的前提(栈顶指针总是次栈顶指针指向结点的左子树),所以2停止
    3. 回到到步骤1
  • 综上所述可得出规律
    1. 重复压入栈顶结点左子树指针,直到栈顶指针指向结点的左子树为空
    2. 重复输判断如果栈顶指针指向结点右子树遍历过或者为空,则栈顶出栈.否则将栈顶指针指向结点右子树入栈
    3. 回到步骤1

C++代码实现

void postorder_travel() {
	if (root == NULL)
		return;
	std::stack<Node*> s;
	s.push(root);//根指针入栈
	while (!s.empty())
	{
		while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
		{
			s.push(s.top()->L);
		}
		Node* last = NULL;//上一次遍历过的指针
		while (!s.empty())//重复检查
		{
			if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
				std::cout << s.top()->data << " ";//输出栈顶指向的结点
				last = s.top();//更新指针last
				s.pop();//栈顶指针出栈
			}
			else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树不为空
			{
				s.push(s.top()->R);//将右子树入栈
				break;//退出检查
			}
		}
	}
}

整体代码

#include <iostream>
#include <stack>
template <typename T>
class BST {
public:
	BST() :root(NULL) {};
	~BST() {};
	void insert(T data) {
		Node* temp = new Node();
		temp->L = NULL;
		temp->R = NULL;
		temp->data = data;
		if (root == NULL) {
			root = temp;
		}
		else {
			Node* tracer = root;
			while (true)
			{
				if (tracer->data >= data)
					if (tracer->L == NULL) {
						tracer->L = temp;
						break;
					}
					else
						tracer = tracer->L;
				else
					if (tracer->R == NULL) {
						tracer->R = temp;
						break;
					}
					else
						tracer = tracer->R;
			}
		}
	}
	void preorder_travel() {
		if (root == NULL)
			return;
		std::stack<Node*> s;
		s.push(root);//根指针入栈
		while (!s.empty())
		{
			std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
			Node* temp = s.top();//备份栈顶指针
			s.pop();//栈顶指针出栈
			if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
			if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
		}
	}
	void inorder_travel() {
		if (root == NULL)
			return;
		std::stack<Node*> s;
		s.push(root);//根指针入栈
		while (!s.empty())
		{
			while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
			{
				s.push(s.top()->L);
			}
			while (!s.empty())//重复检查
			{
				std::cout << s.top->data << " ";//输出栈顶指针指向的结点
				Node* temp = s.top();//备份栈顶指针
				s.pop();//栈顶指针出栈
				if (temp->R != NULL) {//如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
					s.push(temp->R);
					break;
				}
			}
		}
	}
	void postorder_travel() {
		if (root == NULL)
			return;
		std::stack<Node*> s;
		s.push(root);//根指针入栈
		while (!s.empty())
		{
			while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
			{
				s.push(s.top()->L);
			}
			Node* last = NULL;//上一次遍历过的指针
			while (!s.empty())//重复检查
			{
				if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
					std::cout << s.top()->data << " ";//输出栈顶指向的结点
					last = s.top();//更新指针last
					s.pop();//栈顶指针出栈
				}
				else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树不为空
				{
					s.push(s.top()->R);//将右子树入栈
					break;//退出检查
				}
			}
		}
	}
private:
	struct Node
	{
		Node* L;
		Node* R;
		T data;
	};
	Node* root;
};
int main(){
	BST<int> b;
	b.insert(2);
	b.insert(4);
	b.insert(1);
	b.insert(5);
	b.insert(3);
	b.insert(0);
	b.preorder_travel();
	std::cout << std::endl;
	b.inorder_travel();
	std::cout << std::endl;
	b.postorder_travel();
}

原文地址:https://www.cnblogs.com/redo19990701/p/11302448.html

时间: 2024-10-07 05:55:08

栈实现二叉树的先,中,后序遍历的相关文章

二叉树的前中后序遍历迭代&amp;广度遍历

递归很是简单 但也应该掌握其迭代方式的遍历方法 这三种的迭代遍历方法需要通过栈来存储节点 尤其是后序遍历还需要 记录当前节点的右子树是否已被遍历 决定是否遍历当前节点 而其广度遍历 只需要一个队列来顺序记录遍历节点 即可轻松解决问题  主要思想在程序代码中来做说明 前序遍历:遍历结果返回一个vector容器中 std::vector<int> BinaryTree::pre_order_iter(Binary_node *root){    std::vector<int> res

二叉树的前中后序遍历简单的递归

二叉树的遍历 无外乎广度和深度 其中深度又分为前中后序遍历三种情况  这三种遍历若只是递归方法 自然很是简单 但递归代码简单 若嵌套层次太深 会栈溢出 二叉树节点数据结构: struct Binary_node{    int val;    Binary_node *left;    Binary_node *right;    Binary_node(int v = 0, Binary_node *le = nullptr, Binary_node *ri = nullptr) :val(v

二叉树系列 - 二叉树的前/中/后序遍历(非递归)

二叉树的遍历是二叉树中最最基础的部分. 这里整理二叉树不用递归实现三种顺序遍历的方式. 不用递归的话,一般需要栈来完成.当然线索二叉树(不需要栈或递归)也可以完成中序遍历,这种方式在这篇文章中已经讨论过.这里着重讨论使用栈的实现方式. 中序遍历 (1) 双while,第二个内层while是为了不断压入left child. vector<int> inorderTraversal(TreeNode *root) { vector<int> v; if(!root) return v

二叉树的前中后序遍历

#include<stdio.h> #include<string.h> #include<stdlib.h> #define Size 100 #define Resize 10 typedef struct Bitnode{ //定义结点 char data; struct Bitnode *lchild,*rchild; }Bitnode,*Bitree; typedef struct Stack{ //定义栈 Bitree *base; int top; int

POJ 2255 Tree Recovery &amp;&amp; Ulm Local 1997 Tree Recovery (二叉树的前中后序遍历)

链接:poj.org/problem?id=2255 题意: 分别给你一个二叉树的前序遍历序列和中序遍历序列,让你给出这个二叉树的后序遍历序列. 思路: 对于二叉树的三种遍历方式,都可以使用递归来实现,那么也一定可以使用递归来拆解,以达到从遍历序列确定二叉树具体结构的目的.对于前序遍历来说,第一个字母一定是根,并且在序列中根的左子树包含的点一定出现在根的右子树的前面.对于中序遍历序列来说,根前面出现的字母所代表的点一定出现在左子树中,根后面出现的字母所代表的点一定出现在右子树中.在根据前序与中序

Binary Tree Traversal 二叉树的前中后序遍历

[抄题]: [思维问题]: 不会递归.三要素:下定义.拆分问题(eg root-root.left).终止条件 [一句话思路]: [输入量]:空: 正常情况:特大:特小:程序里处理到的特殊情况:异常情况(不合法不合理的输入): [画图]: [一刷]: [二刷]: [三刷]: [四刷]: [五刷]: [总结]: [复杂度]:Time complexity: O() Space complexity: O() [英文数据结构,为什么不用别的数据结构]: [其他解法]: [Follow Up]: [L

二叉树的前序建立,前中后序遍历的非递归算法

二叉树的前序建立递归算法以及前中后序遍历的递归算法已经是人尽皆知了,递归算法也确实为代码的编写带来了很大的方便.然而,有时我们也确实需要它们的非递归算法.将递归算法转化为非递归算法可以帮助我们深入了解函数的调用与栈的原理.这里总结一下二叉树的这些重要的非递归算法. 一.前序建树 前序建树的基本思路是,接收用户输入的一组字符串,其中'#'代表空树,其他代表树结点的数据域值.例如,要建立如下一棵树 需要输入"AB#D##C##". 而非递归的思路是,1.设一个标志位来判断当前创建的结点是左

二叉树非递归先中后序遍历 及 非递归交换二叉树两个孩子的位置

看到一个非递归交换一个二叉树的左右孩子的位置,于是想实现之,才发现非递归的先中后序遍历都忘记了……于是杂七杂八的写了一些,抄抄资料就实现了,然后实现非递归交换两个孩子的位置还是相当容易的.先直接上代码吧,其实这东西还是得自己写写过一遍的,印象才会更加深刻: #include <iostream> #include <fstream> #include <string> #include <stack> using std::cout; using std::

002.深入浅出理解[二叉树的构建、先中后序遍历、树的深度、左右子树互换]

二叉树本来就是递归定义的,如果对递归还不是特别了解,建议看一下<001.深入浅出解释[递归]> 写一个递归函数很简单,只需要记住下面2点: 1.递归中止条件:对于二叉树来说一般是node==null的时候判断到了叶子结点 2.递归函数::描述一个中间过程,然后用代码实现,调用自身的时候传递的参数就是你想要递归的方式. 下面的代码就是一个二叉树的创建.先中后序遍历.树的深度.左右子树的互换的过程 #include <stdio.h> // 定义二叉树的结点 struct treeNo