在这里讨论下,最简单的二叉树的实现和它的各种遍历的实现。一般的这种无规则的二叉树,实际用途一般,远不如什么二叉查找树等等用途广,所以这里主要是实现一个带有插入方法的二叉树,然后方便我们来用各种方式遍历它,包括递归的和非递归的。
为了简化插入操作(在插入的时候不需要指定插入的位置),我将每个节点的高度也保存在了节点中,然后在插入的时候,我会先看节点是否有左右子树,如果有的话,就比较左右子树的高度,将新插入的节点安排到高度小的一边去,这样可以保证整个树随着插入操作,还是可以很平衡,不会变成一个非常深的单边链表。
下面是树定义的完整代码:
#ifndef _BINARYTREE_
#define _BINARYTREE_
#include <iostream>
#include <stdexcept>
#include <queue>
#include <stack>
using namespace std;
template <typename T>
class BinaryTree
{
private:
struct BinaryTreeNode
{
T data;
BinaryTreeNode* left;
BinaryTreeNode* right;
int height;
BinaryTreeNode() : left(NULL),right(NULL),height(0) {}
BinaryTreeNode(const T& v, BinaryTreeNode* l=NULL,BinaryTreeNode* r=NULL) : data(v),left(l),right(r),height(0) {}
};
public:
BinaryTree(): root(NULL) {}
~BinaryTree()
{
Clear();
}
BinaryTree(const BinaryTree& rhs) : root(NULL) { operator=(rhs); }
const BinaryTree& operator= (const BinaryTree& rhs)
{
if( this != &rhs )
{
this->Clear();
this->CopyRecursive(root,rhs.root);
}
return *this;
}
void Insert(const T& value)
{
InsertHelper(root,value);
}
void PreOrderTraverse()
{
PreOrderTraverseRecursive(root);
}
void PreOrderTraverseNonRecursive()
{
if(root != NULL)
{
BinaryTreeNode* cur = root;
stack<BinaryTreeNode*> rights;
while( cur != NULL || !rights.empty() )
{
if( cur != NULL )
{
PrintNode(cur);
if( cur->right != NULL )
rights.push(cur->right);
cur = cur->left;
}
else
{
cur = rights.top();
rights.pop();
}
}
}
}
void InOrderTraverse()
{
InOrderTraverseRecursive(root);
}
void InOrderTraverseNonRecursive()
{
if( root != NULL )
{
BinaryTreeNode* cur = root;
stack<BinaryTreeNode*> remains;
while( cur != NULL || !remains.empty() )
{
if( cur != NULL )
{
remains.push(cur);
cur = cur->left;
}
else
{
cur = remains.top();
remains.pop();
PrintNode(cur);
cur = cur->right;
}
}
}
}
void PostOrderTraverse()
{
PostOrderTraverseRecursive(root);
}
void PostOrderTraverseNonRecursive()
{
if( root != NULL)
{
BinaryTreeNode* cur = root;
stack<BinaryTreeNode*> remains;
while( cur != NULL || !remains.empty() )
{
if( cur != NULL )
{
remains.push(cur);
remains.push(cur);
cur = cur->left;
}
else
{
cur = remains.top();
remains.pop();
if( !remains.empty() && cur == remains.top() )
{
cur = cur->right;
}
else
{
PrintNode(cur);
cur = NULL;
}
}
}
}
}
void LevelTraverse()
{
if(root != NULL )
{
queue<BinaryTreeNode*> traverseQueue;
traverseQueue.push(root);
while(!traverseQueue.empty())
{
BinaryTreeNode* temp = traverseQueue.front();
traverseQueue.pop();
PrintNode(temp);
if(temp->left != NULL)
traverseQueue.push(temp->left);
if(temp->right != NULL)
traverseQueue.push(temp->right);
}
}
}
int CalculateHeight()
{
return CalculateHeightRecursive(root);
}
private:
int CalculateHeightRecursive(BinaryTreeNode* subroot)
{
if( subroot == NULL )
{
return -1;
}
else
{
int leftHeight = CalculateHeightRecursive(subroot->left);
int rightHeight = CalculateHeightRecursive(subroot->right);
if(leftHeight > rightHeight)
{
return leftHeight + 1;
}
else
{
return rightHeight + 1;
}
}
}
int InsertHelper(BinaryTreeNode* &subroot,const T& value)
{
int newHeight = 0;
if( subroot == NULL )
{
subroot = new BinaryTreeNode(value,NULL,NULL);
return subroot->height;
}
else if( subroot->left == NULL )
{
newHeight = InsertHelper(subroot->left,value) + 1;
}
else if( subroot->right == NULL)
{
newHeight = InsertHelper(subroot->right,value) + 1;
}
else
{
if( subroot->left->height <= subroot->right->height )
{
newHeight = InsertHelper(subroot->left,value) + 1;
}
else
{
newHeight = InsertHelper(subroot->right,value) + 1;
}
}
if( subroot->height < newHeight )
subroot->height = newHeight;
return subroot->height;
}
void PreOrderTraverseRecursive(BinaryTreeNode* subroot)
{
if(subroot != NULL)
{
PrintNode(subroot);
PreOrderTraverseRecursive(subroot->left);
PreOrderTraverseRecursive(subroot->right);
}
}
void InOrderTraverseRecursive(BinaryTreeNode* subroot)
{
if( subroot != NULL)
{
InOrderTraverseRecursive(subroot->left);
PrintNode(subroot);
InOrderTraverseRecursive(subroot->right);
}
}
void PostOrderTraverseRecursive(BinaryTreeNode* subroot)
{
if(subroot != NULL)
{
PostOrderTraverseRecursive(subroot->left);
PostOrderTraverseRecursive(subroot->right);
PrintNode(subroot);
}
}
void Clear()
{
if(root != NULL )
{
queue<BinaryTreeNode*> traverseQueue;
traverseQueue.push(root);
while(!traverseQueue.empty())
{
BinaryTreeNode* temp = traverseQueue.front();
traverseQueue.pop();
if(temp->left != NULL)
traverseQueue.push(temp->left);
if(temp->right != NULL)
traverseQueue.push(temp->right);
delete temp;
}
root= NULL;
}
}
void CopyRecursive(BinaryTreeNode* &subroot, BinaryTreeNode* source)
{
if(source != NULL)
{
subroot = new BinaryTreeNode(source->data,NULL,NULL);
CopyRecursive(subroot->left,source->left);
CopyRecursive(subroot->right,source->right);
}
}
void PrintNode(BinaryTreeNode* node)
{
if(node != NULL)
{
cout << "[" << node->data << "," << node->height << "], ";
}
else
{
cout << "[], ";
}
}
BinaryTreeNode* root;
};
#endif
下面是测试代码:
void BinaryTreeTest1()
{
BinaryTree<int> tree;
tree.Insert(100);
tree.Insert(101);
tree.Insert(102);
tree.Insert(103);
tree.Insert(104);
cout << "PreOrder: ";
tree.PreOrderTraverse();
cout <<endl;
cout << "PreOrder non recursive:";
tree.PreOrderTraverseNonRecursive();
cout <<endl;
cout << "InOrder: ";
tree.InOrderTraverse();
cout <<endl;
cout << "InOrder non recursive: ";
tree.InOrderTraverseNonRecursive();
cout <<endl;
cout << "PostOrder: ";
tree.PostOrderTraverse();
cout <<endl;
cout << "PostOrder non recursive: ";
tree.PostOrderTraverseNonRecursive();
cout <<endl;
cout << "LevelOrder: ";
tree.LevelTraverse();
cout <<endl;
cout << "Tree Height is " << tree.CalculateHeight() << endl;
cout << "Tree2" << endl;
BinaryTree<int> tree2(tree);
cout << "PreOrder: ";
tree2.PreOrderTraverse();
cout <<endl;
cout << "InOrder: ";
tree2.InOrderTraverse();
cout <<endl;
cout << "PostOrder: ";
tree2.PostOrderTraverse();
cout <<endl;
cout << "LevelOrder: ";
tree2.LevelTraverse();
cout <<endl;
}
下面来部分说明一下:
首先是根据树左右的高度来插入新节点的函数,公共的Insert方法会调用下面的递归方法,思想很简单,当前节点如果是空的,那就是找到合适的插入位置,创建一个新节点,然后赋值给subroot,注意这个递归函数的参数subroot是节点指针的引用,正式这个引用的使用才能保证在这个函数里面可以更新它的父节点的对应指针。如果不是,那么就判断左右节点是否存在,假设左节点不存在就插入到左子树,如果左右节点都存在,那么就比较左右子树的高度,选择低的一边继续这个操作。另外,每次新插入一个节点,可能会导致这个节点到根节点的边上节点的高度发生更新,因此这个函数也负责返回当前节点插入新节点之后的新高度,并递归的从下至上的更新每个节点的高度。
int InsertHelper(BinaryTreeNode* &subroot,const T& value)
{
int newHeight = 0;
if( subroot == NULL )
{
subroot = new BinaryTreeNode(value,NULL,NULL);
return subroot->height;
}
else if( subroot->left == NULL )
{
newHeight = InsertHelper(subroot->left,value) + 1;
}
else if( subroot->right == NULL)
{
newHeight = InsertHelper(subroot->right,value) + 1;
}
else
{
if( subroot->left->height <= subroot->right->height )
{
newHeight = InsertHelper(subroot->left,value) + 1;
}
else
{
newHeight = InsertHelper(subroot->right,value) + 1;
}
}
if( subroot->height < newHeight )
subroot->height = newHeight;
return subroot->height;
}
另外,也写了一个计算树的高度的方法,这个方法可以用于计算那种没有保存高度信息的树,当然在这里是多余的,因为树的高度存在了对应的节点中。
对于树的三种遍历,前序,中序,后序,递归实现都很直白,就不说了,下面说说它们的非递归实现以及按层遍历。
按层遍历,使用了一个队列,队列的先进先出的特点,非常适合做层遍历。访问节点后,将左孩子放入队列,然后把右孩子放入队列,下次从队列中取出第一个,重复进行。
void LevelTraverse()
{
if(root != NULL )
{
queue<BinaryTreeNode*> traverseQueue;
traverseQueue.push(root);
while(!traverseQueue.empty())
{
BinaryTreeNode* temp = traverseQueue.front();
traverseQueue.pop();
PrintNode(temp);
if(temp->left != NULL)
traverseQueue.push(temp->left);
if(temp->right != NULL)
traverseQueue.push(temp->right);
}
}
}
前序遍历的非递归实现是参考它的递归实现的调用栈得来的,观察调用栈,我们可以知道每次前序遍历,在访问完了自己之后,下一个就是访问它的左子树,左子树访问结束了之后退回来才访问右子树,因此我们需要保存右子树的节点信息(右孩子入栈),然后改变当前节点指向左边孩子节点,因此有了如下的代码:
void PreOrderTraverseNonRecursive()
{
if(root != NULL)
{
BinaryTreeNode* cur = root;
stack<BinaryTreeNode*> rights;
while( cur != NULL || !rights.empty() )
{
if( cur != NULL )
{
PrintNode(cur);
if( cur->right != NULL )
rights.push(cur->right);
cur = cur->left;
}
else
{
cur = rights.top();
rights.pop();
}
}
}
}
中序遍历的非递归实现和前序遍历类似,也可由递归实现的调用栈得到,没有什么特别的,就不多说了。
后序遍历的非递归实现要麻烦一点,因为我们观察它的递归实现的调用栈,会发现,节点本身会被碰到两次,第一次是从它的左子树返回时碰到,这个时候不能访问节点本身,必须先访问右子树,第二次是从右子树返回的时候,这个时候可以访问节点本身了,但是我们无法区分当前是从左子树返回还是右子树返回的,因此我用一个小技巧来区分这两者。当遇到节点,准备访问它的左子树时,将节点本身连续入栈两次,那么从左子树返回时,出栈重新拿到这个节点(执行一个出栈操作)后,比较它和当前栈顶的值,如果相等,那就代表是左子树返回的,那么不访问它,转而去访问它的右子树,下次从右子树返回的时候,由于堆栈里面只剩下一个它的实例,因此我们可以明确知道是从右子树返回的,于是可以访问它,并且不需要再访问它的任何子节点了,因此有如下代码:
void PostOrderTraverseNonRecursive()
{
if( root != NULL)
{
BinaryTreeNode* cur = root;
stack<BinaryTreeNode*> remains;
while( cur != NULL || !remains.empty() )
{
if( cur != NULL )
{
remains.push(cur);
remains.push(cur);
cur = cur->left;
}
else
{
cur = remains.top();
remains.pop();
if( !remains.empty() && cur == remains.top() )
{
cur = cur->right;
}
else
{
PrintNode(cur);
cur = NULL;
}
}
}
}
}