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

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

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

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

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();
}

猜你喜欢

转载自www.cnblogs.com/redo19990701/p/11302448.html