二叉树链式结构的遍历
遍历(Traversal) 是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问 题。 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。
树结点结构代码:
template<class T>
struct BTNode {
BTNode(T data = T())
: _data(data),
_lchild(nullptr),
_rchild(nullptr) {
}
BTNode<T>* _lchild; // 指向左孩子结点
BTNode<T>* _rchild; // 指向右孩子节点
T _data; // 数据域
};
以此二叉树为例:
前序/中序/后序的递归结构遍历:是根据访问结点操作发生位置命名.
- NLR:前序遍历(Preorder Traversal 亦称先序遍历)—根左右——访问根结点的操作发生在遍历其左右子树之前。
根左右----先遍历根,再遍历左孩子,最后遍历右孩子; - LNR:中序遍历(Inorder Traversal)—左根右——访问根结点的操作发生在遍历其左右子树之中(间)。
左根右----先遍历左孩子,再遍历根,最后遍历右孩子; - LRN:后序遍历(Postorder Traversal)—左右根——访问根结点的操作发生在遍历其左右子树之后。
左右根----先遍历左孩子,再遍历右孩子,最后遍历根; - 层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为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 << ' ';
}
}
递归非常耗费计算机资源, 容易导致堆栈溢出,所以我们在写程序的时候要尽量避免使用递归。将上述递归代码改写成迭代的版本。改写之后,代码如下:
二叉树非递归方式代码
前序遍历(非递归)
前序遍历也就是遵循了下图所示的遍历顺序
就是先自上而下访问左侧链上的节点,再自下而上访问左侧链上的节点的右子树。
代码思路: 借助栈完成
- 访问当前结点(如果存在)
- 将当前结点右孩子结点入栈(如果存在)
- 如果当前结点存在左子树,则访问左子树,否则访问栈顶(并出栈栈顶元素)
代码:
// 借助栈实现
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). 如果当前结点存在右孩子结点,则将右孩子结点置为当前结点,重复步骤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;
}
}
后序遍历(非递归)
代码思路: 借助栈完成----和前序相似
首先按照 根右左 的方式遍历, 最终在将结果逆序变为 左右根
- 访问当前结点(如果存在)
- 将当前结点左孩子结点入栈(如果存在)
- 如果当前结点存在右子树,则访问右子树,否则访问栈顶(并出栈栈顶元素)
- 最终将访问结果逆序
// 后序遍历--非递归版
// 依据前序遍历原理-------后序遍历是 左右根 其逆序是 根右左
// 所以可以按照 根右左遍历 然后将遍历结果 逆序显示.
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();
}
}
层序遍历
代码思路: 借助队列实现
- 如果根结点存在, 将根入队列
- 取队头结点访问, 并出队列
- 如果队顶元素存在左孩子结点,便将左孩子结点入队.如果存在右孩子结点,则将右孩子结点入队.
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);
}
}