初学二叉树那会儿,始终掌握不好二叉树的遍历方法,更认为非递归遍历晦涩难懂没有掌握的意义。实际上非递归的遍历方法很有用处,由于每次递归都需要将函数的信息入栈,当递归层数太深很容易就导致栈溢出,所以这个时候就必须用到非递归遍历二叉树了。而且,当看懂非递归遍历后,你会发现,其实非递归也很简单。
我们知道,要想处理二叉树,离不开其遍历方式。遍历二叉树有三种方式,前序遍历,中序遍历,后序遍历,每种方式都有递归版本和非递归版本。
所以,其实遍历规则很简单,永远都是先遍历左子节点,再遍历右子节点,而什么时候遍历根节点由遍历方式决定。
下面先看看简单的递归遍历二叉树代码,优点是代码简单易读易懂,且容易编写,缺点是容易导致栈溢出。
二叉树节点定义:
class TreeNode {
public:
TreeNode(int val_) :val(val_), left(NULL), right(NULL) {}
TreeNode* left;
TreeNode* right;
int val;
};
前序遍历——递归:
void PreOrder(TreeNode* root) {
if (root != NULL) {
cout << root->val << endl;
PreOrder(root->left);
PreOrder(root->right);
}
}
中序遍历——递归:
void InOrder(TreeNode* root) {
if (root != NULL) {
InOrder(root->left);
cout << root->val << endl;
InOrder(root->right);
}
}
后序遍历——递归:
void PostOrder(TreeNode* root) {
if (root != NULL) {
PostOrder(root->left);
PostOrder(root->right);
cout << root->val << endl;
}
}
可以发现,递归遍历十分简单,不过需要注意的是不要忘了边界条件。
非递归遍历的主要思想是通过循环与栈这种数据结构来解决遍历二叉树的问题。
前序遍历——非递归:
先访问根节点,再分别访问左右子节点;非递归前序遍历用栈来模拟遍历过程,
1. 首先将根节点入栈
2. 开始循环,访问根节点,再将根节点出栈
3. 根节点左子节点入栈,最后右子节点入栈,完成一次循环
4. 接下来循环进行先前3个步骤的处理直到栈为空则完成遍历。
void PreOrderNonRecursive(TreeNode* root) {
stack sta;
if (root == NULL)
return;
sta.push(root);
while (!sta.empty()) {
TreeNode* pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL)
sta.push(pNode->right);
if (pNode->left != NULL)
sta.push(pNode->left);
}
}
中序遍历——非递归:
中序遍历稍微复杂一点,先访问左子节点,再访问根节点,最后访问右子节点;同样需要借助栈来进行遍历,
1. 由于最先访问左子节点,所以需要先访问最左边的节点,先将根节点入栈,用一个中间变量记录每次入栈节点,以便判断其是否有左子节点
2. 如果中间入栈的节点有左子节点,那么继续入栈其左子节点,直到不再有左子节点为止
3. 栈顶元素出栈,即访问根节点
4. 判断栈顶元素是否有右子节点,若有,入栈其右子节点
5. 循环2-4步骤,直到栈为空,完成遍历。
void InOrderNonRecursive(TreeNode* root) {
stack<TreeNode*> sta;
if (root == NULL)
return;
sta.push(root);
TreeNode* pNode = root;
while (!sta.empty()) {
while (pNode != NULL&&pNode->left != NULL) {
pNode = pNode->left;
sta.push(pNode);
}
pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL) {
sta.push(pNode->right);
pNode = pNode->right;
}
else
pNode = NULL;
}
}
后序遍历——非递归:
先遍历左右子节点,最后遍历根节点,非递归后序遍历是三种遍历方式中最复杂的一种,不过只要抓住一点基本思想就行:判断每个节点的左右子节点是否为NULL,或者其左右子节点是否已经被访问过,如果是,那么访问该节点。
1. 边界条件检查,入栈根节点
2. 记录当前栈顶元素,如果栈顶元素左右子节点均为NULL或者均已被访问,那么访问当前节点
3. 否则依次入栈其右子节点和左子节点
4. 循环步骤2和步骤3,直到栈为空结束
void PostOrderNonRecursive(TreeNode* root) {
if (root == NULL)
return;
stack<TreeNode*> sta;
TreeNode* curNode = root;
TreeNode* preNode = NULL;
sta.push(root);
while (!sta.empty()) {
curNode = sta.top();
if ((curNode->left == NULL&&curNode->right == NULL) ||
(preNode != NULL && (preNode == curNode->left || preNode == curNode->right))) {
cout << curNode->val << endl;
sta.pop();
preNode = curNode;
}
else {
if (curNode->right != NULL)
sta.push(curNode->right);
if (curNode->left != NULL)
sta.push(curNode->left);
}
}
}
上边分别介绍了三种遍历二叉树的方法,递归与非递归版本。下边对代码做一个简单的测试,分别以前中后序遍历输出二叉树的值,感兴趣的读者可以自行修改测试方法。
另外在构建测试用的二叉树时,正好用上不久前的算法:用前序遍历和后序遍历的二叉树序列,重构二叉树。下边贴上测试代码:
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
if (pre.size() == 0 || vin.size() == 0)
return NULL;
return construct(pre, vin, 0, pre.size(), 0, vin.size());
}
TreeNode* construct(vector<int>& pre, vector<int>& vin, int startPreorder, int endPreorder, int startInorder, int endInorder) {
int rootValue = pre[startPreorder];
TreeNode* root = new TreeNode(rootValue);
if (startPreorder == endPreorder) {
if (startInorder == endInorder - 1 && pre[startPreorder] == vin[startInorder])
return root;
}
int rootInorder = 0;
for (int i = startInorder; i < endInorder; i++) {
if (vin[i] != rootValue)
continue;
else {
rootInorder = i;
break;
}
}
if (rootInorder - startInorder > 0) {
root->left = construct(pre, vin, startPreorder + 1, startPreorder + rootInorder - startInorder + 1, startInorder, rootInorder);
}
if (rootInorder - startInorder < endPreorder - startPreorder - 1) {
root->right = construct(pre, vin, startPreorder + rootInorder - startInorder + 1, endPreorder, rootInorder + 1, endInorder);
}
return root;
}
};
int main() {
vector<int> pre = { 1,2,4,7,8,9,5,3,6 };
vector<int> vin = { 4,8,7,9,2,5,1,6,3 };
Solution sol;
TreeNode* root = sol.reConstructBinaryTree(pre, vin); //构建二叉树
PreOrder(root); //递归前序遍历
InOrder(root); //递归中序遍历
PostOrder(root); //递归后序遍历
PreOrderNonRecursive(root); //非递归前序遍历
InOrderNonRecursive(root); //非递归中序遍历
PostOrderNonRecursive(root); //非递归后序遍历
return 0;
}
代码写得比较仓促,如有错误还请大家指出。谢谢。