需求
二叉树是一种常见的适合使用递归进行遍历的数据结构,但是如同绝大多数递归操作,二叉树的遍历也可以使用非递归的方式来实现,研究二叉树的非递归实现有助于更好的掌握二叉树的结构和使用方式。
递归好是好,只是太无脑。只会用递归解自循环数据就像只会用方程组解应用题那样,丧失了对数据结构本质的解析能力。
实现
上图所示是一个简单的二叉树的示意图。如果要对上述二叉树进行中序遍历,我们应该先对根节点的左子树(如果有)进行遍历,再对根节点进行操作,最后对右子树(如果有)进行遍历。对每个左子树和右子树的每个节点都进行递归式上述操作即可实现对所有节点的遍历。这用递归很容易实现,但是如果不使用递归呢?
假设节点类定义如下:
class BSTNode
{
public:
BSTNode(int v);
int value;
BSTNode* left;
BSTNode* right;
BSTNode();
~BSTNode();
private:
};
BSTNode::BSTNode(int v)
{
value = v;
left = nullptr;
right = nullptr;
}
BSTNode::~BSTNode()
{
if (left != nullptr)
delete left;
if (right != nullptr)
delete right;
}
插入
首先来看看比较简单的插入操作:只需要从上到下搜索到符合要求的节点即可,不需要返回曾经搜索过的节点来对它的另一个子树进行遍历。这样我们就不需要记录曾经访问过哪些节点,只需要在每次循环中将符合要求的下一个节点的地址保存下来作为下一次循环需要访问的节点即可,如此循环直到找到对应的位置后将数据插入。
代码如下:
void BST::add(int value){
if (root == nullptr){
root = new BSTNode(value);
return;
}
auto temp = root;
while (temp!=nullptr)
{
//待插入值小于当前节点值
if (value < temp->value){
//当前节点左孩子为空则将当前值插入左孩子,循环结束
if (temp->left == nullptr){
temp->left = new BSTNode(value);
return;
}
//当前节点左孩子不为空则将左孩子节点作为下个循环的节点
else
temp = temp->left;
}
//待插入值大于当前节点值
else{
//当前节点右孩子为空则将当前值插入右孩子,循环结束
if (temp->right == nullptr){
temp->right = new BSTNode(value);
return;
}
//当前节点右孩子不为空则将右孩子节点作为下个循环的节点
else
temp = temp->right;
}
}
}
很简单,一个循环一个指针即可解决问题。
遍历
插入操作很简单,但是如果是遍历呢?对于遍历操作而言可不想插入操作那样仅仅找到符合要求的位置即可,因为需要将所有节点都遍历到,所以除了只有左孩子的树,都会需要返回曾经访问过的节点再次对其进行访问。这时候该怎么办呢?
如下图所示:在一次中序遍历中,我们会从根节点先一路访问左子树找到第一个没有左子树的节点并对其进行操作,然后对其右子树(如果有)进行操作,对其右子树操作结束后再返回其父节点进行操作。
将上次遍历的父节点作为当前节点进行操作后再对其右子树(如果有)进行遍历,然后再将其父节点作为下次访问的节点,如此循环直至结束:
如上图所示,由于对于某些节点进行访问之后需要回到曾经访问过的节点,所以需要知道当前访问的节点是首次访问还是第二次访问,这时我们就需要一个栈来对数据进行保存了:最后一个入栈的是最下层的节点也就是需要最先访问的节点。
对于每个节点,在第一次访问时,如果它有左孩子,则将左孩子推入栈,然后将其左孩子作为当前节点进入下一次循环;如果它是第一次访问且没有左孩子或者是二次访问则对该节点进行操作,操作结束后进行出栈操作(将已操作国的节点从栈中删除),如果它有右孩子则将右孩子节点入栈并将其作为下一次访问的当前节点并进入下一次操作,如果没有右孩子则将父节点作为下次访问的节点。
由于每次循环都将下次要访问的节点先入栈,则如果栈顶节点等于当前节点则说明是首次访问,如果栈顶节点不等于当前节点则说明是二次访问,而栈为空则说明已经遍历完所有节点。
首先将根节点入栈,并将根节点作为当前节点。
代码如下:
void BST::inorderPtr(){
stack<BSTNode*> nodeList;
nodeList.push(root);
auto temp = root;
while (!nodeList.empty())
{
//如果当前节点等于栈顶节点并且左孩子不为空则将左孩子入栈,且将左孩子作为下次访问的节点
if (temp == nodeList.top() && nodeList.top()->left != nullptr){
nodeList.push(nodeList.top()->left);
temp = nodeList.top();
}
//否则该节点就是需要操作的当前节点
else
{
//对节点的操作
cout << nodeList.top()->value << endl;
//操作结束后如果右孩子节点不为空将栈顶节点出栈并将当前节点的右孩子节点入栈作为下次访问的节点
if (nodeList.top()->right != nullptr){
temp = nodeList.top()->right;
nodeList.pop();
nodeList.push(temp);
}
//否则对栈顶节点出栈
else
{
nodeList.pop();
}
}
}
}
测试
测试代码:
BST bst;
bst.add(10);
bst.add(8);
bst.add(4);
bst.add(2);
bst.add(5);
bst.add(6);
bst.add(20);
bst.add(13);
bst.add(16);
bst.inorderPtr();
结果:
2
4
5
6
8
10
13
16
20
从结果看,上述操作复合中序遍历的结果。
转载请注明出处:http://blog.csdn.net/ylbs110/article/details/51527102
全部代码:
#include "stdafx.h"
#include <iostream>
#include <stack>
using namespace std;
class BSTNode
{
public:
BSTNode(int v);
int value;
BSTNode* left;
BSTNode* right;
BSTNode();
~BSTNode();
private:
};
BSTNode::BSTNode(int v)
{
value = v;
left = nullptr;
right = nullptr;
}
BSTNode::~BSTNode()
{
if (left != nullptr)
delete left;
if (right != nullptr)
delete right;
}
class BST
{
public:
BST();
~BST();
BSTNode* root;
void add(int value);
void inorderPtr();
private:
};
BST::BST()
{
}
BST::~BST()
{
}
void BST::add(int value){
if (root == nullptr){
root = new BSTNode(value);
return;
}
auto temp = root;
while (temp!=nullptr)
{
//待插入值小于当前节点值
if (value < temp->value){
//当前节点左孩子为空则将当前值插入左孩子,循环结束
if (temp->left == nullptr){
temp->left = new BSTNode(value);
return;
}
//当前节点左孩子不为空则将左孩子节点作为下个循环的节点
else
temp = temp->left;
}
//待插入值大于当前节点值
else{
//当前节点右孩子为空则将当前值插入右孩子,循环结束
if (temp->right == nullptr){
temp->right = new BSTNode(value);
return;
}
//当前节点右孩子不为空则将右孩子节点作为下个循环的节点
else
temp = temp->right;
}
}
}
void BST::inorderPtr(){
stack<BSTNode*> nodeList;
nodeList.push(root);
auto temp = root;
while (!nodeList.empty())
{
//如果当前节点等于栈顶节点并且左孩子不为空则将左孩子入栈,且将左孩子作为下次访问的节点
if (temp == nodeList.top() && nodeList.top()->left != nullptr){
nodeList.push(nodeList.top()->left);
temp = nodeList.top();
}
//否则该节点就是需要操作的当前节点
else
{
//对节点的操作
cout << nodeList.top()->value << endl;
//操作结束后如果右孩子节点不为空将栈顶节点出栈并将当前节点的右孩子节点入栈作为下次访问的节点
if (nodeList.top()->right != nullptr){
temp = nodeList.top()->right;
nodeList.pop();
nodeList.push(temp);
}
//否则对栈顶节点出栈
else
{
nodeList.pop();
}
}
}
}
#include "stdafx.h"
#include "BST.h"
int _tmain(int argc, _TCHAR* argv[])
{
BST bst;
bst.add(10);
bst.add(8);
bst.add(4);
bst.add(2);
bst.add(5);
bst.add(6);
bst.add(20);
bst.add(13);
bst.add(16);
bst.inorderPtr();
return 0;
}