总结学习二叉树4-二叉树的遍历

二叉树链式结构的遍历

遍历(Traversal) 是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问 题。 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。

树结点结构代码:

template<class T>
struct BTNode {
    
    
	BTNode(T data = T())
		: _data(data),
		_lchild(nullptr),
		_rchild(nullptr) {
    
    }
	
	BTNode<T>* _lchild; // 指向左孩子结点
	BTNode<T>* _rchild; // 指向右孩子节点
	T _data; // 数据域
};

以此二叉树为例:
在这里插入图片描述

前序/中序/后序的递归结构遍历:是根据访问结点操作发生位置命名.

  1. NLR:前序遍历(Preorder Traversal 亦称先序遍历)—根左右——访问根结点的操作发生在遍历其左右子树之前。
    根左右----先遍历根,再遍历左孩子,最后遍历右孩子;
  2. LNR:中序遍历(Inorder Traversal)—左根右——访问根结点的操作发生在遍历其左右子树之中(间)。
    左根右----先遍历左孩子,再遍历根,最后遍历右孩子;
  3. LRN:后序遍历(Postorder Traversal)—左右根——访问根结点的操作发生在遍历其左右子树之后。
    左右根----先遍历左孩子,再遍历右孩子,最后遍历根;
  4. 层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
    层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
    上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
    在这里插入图片描述

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为
根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

二叉树递归方式遍历代码

前序遍历代码(递归)

递归的代码非常简单, 这种遍历实现方式并不是一般的考察重点.

template<class T>
void RPreOrder(BTNode<T>* root) {
    
    
	if (root != nullptr) {
    
    
		std::cout << root->_data << '-';
		RPreOrder(root->_lchild);
		RPreOrder(root->_rchild);
	}
}

中序遍历代码(递归)

template<class T>
void RMidOrder(BTNode<T>* root) {
    
    
	if (root != nullptr) {
    
    
		RMidOrder(root->_lchild);
		std::cout << root->_data << ' ';
		RMidOrder(root->_rchild);
	}
}

后序遍历代码(递归)

template<class T>
void RPostOrder(BTNode<T>* root) {
    
    
	if (root) {
    
    
		RPostOrder(root->_lchild);
		RPostOrder(root->_rchild);
		std::cout << root->_data << ' ';
	}
}

递归非常耗费计算机资源, 容易导致堆栈溢出,所以我们在写程序的时候要尽量避免使用递归。将上述递归代码改写成迭代的版本。改写之后,代码如下:

二叉树非递归方式代码

前序遍历(非递归)

前序遍历也就是遵循了下图所示的遍历顺序
就是先自上而下访问左侧链上的节点,再自下而上访问左侧链上的节点的右子树。
在这里插入图片描述
代码思路: 借助栈完成

  1. 访问当前结点(如果存在)
  2. 将当前结点右孩子结点入栈(如果存在)
  3. 如果当前结点存在左子树,则访问左子树,否则访问栈顶(并出栈栈顶元素)

代码:

// 借助栈实现
template<class T>
void PreOr1(BTNode<T>* root) {
    
    
	std::stack<BTNode<T>*> tmp;
	if (root)
		tmp.push(root);
	while (!tmp.empty()) {
    
    
		root = tmp.top();
		std::cout << root->_data << ' ';
		tmp.pop();
		// 这里先入栈右孩子, 是因为 栈 遵循 陷入后出的原则, 左孩子结点后入栈,先访问
		if (root->_rchild)
			tmp.push(root->_rchild);
		if (root->_lchild)
			tmp.push(root->_lchild);
	}
}

中序遍历(非递归)

中序遍历的顺序抽象为,先访问二叉树的左侧链上的最底部的节点,然后访问该节点的右子树(如果有的话),然后访问该节点的父节点,然后访问该节点的父节点的右子树(如果有的话)……直至全部节点被访问完毕。

代码思路:

  1. 从当前结点开始遍历其左子树, 将所有节点入栈, 直至左子树为空
  2. 访问栈顶结点, 并出栈栈顶元素,
    (1). 如果当前结点存在右孩子结点,则将右孩子结点置为当前结点,重复步骤1
    (2). 如果不存在,重复步骤2
// 中序遍历非递归版
template<class T>
void MidOr(BTNode<T>* root) {
    
    
	std::stack<BTNode<T>*> tmp;
	while (root || !tmp.empty()) {
    
    
		while (root) {
    
    
			tmp.push(root);
			root = root->_lchild;
		}
		root = tmp.top();
		tmp.pop();
		std::cout << root->_data << ' ';
		root = root->_rchild;
	}
}

后序遍历(非递归)

代码思路: 借助栈完成----和前序相似
首先按照 根右左 的方式遍历, 最终在将结果逆序变为 左右根

  1. 访问当前结点(如果存在)
  2. 将当前结点左孩子结点入栈(如果存在)
  3. 如果当前结点存在右子树,则访问右子树,否则访问栈顶(并出栈栈顶元素)
  4. 最终将访问结果逆序
// 后序遍历--非递归版
// 依据前序遍历原理-------后序遍历是   左右根  其逆序是  根右左
// 所以可以按照  根右左遍历   然后将遍历结果 逆序显示.
template<class T>
void PostOr(BTNode<T>* root) {
    
    
	std::stack<BTNode<T>*> tmp,res;
	if(root)
		tmp.push(root);
	while (!tmp.empty()) {
    
    
		root = tmp.top();
		tmp.pop();
		res.push(root);
		if (root->_lchild)
			tmp.push(root->_lchild);
		if (root->_rchild)
			tmp.push(root->_rchild);
	}
	while (!res.empty()) {
    
    
		std::cout << res.top()->_data << ' ';
		res.pop();
	}
}

层序遍历

代码思路: 借助队列实现

  1. 如果根结点存在, 将根入队列
  2. 取队头结点访问, 并出队列
  3. 如果队顶元素存在左孩子结点,便将左孩子结点入队.如果存在右孩子结点,则将右孩子结点入队.
template<class T>
void FloorOrder(BTNode<T>* root) {
    
    
	// 借助队列实现
	std::queue<BTNode<T>*> q1;
	if (root)
		q1.push(root);
	while (!q1.empty()) {
    
    
		root = q1.front();
		std::cout << root->_data << ' ';
		q1.pop();
		if (root->_lchild)
			q1.push(root->_lchild);
		if (root->_rchild)
			q1.push(root->_rchild);
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_42562387/article/details/115049602