最近在复习算法,做了一下编程之美上面关于二叉树的三道题,感觉顺便就把二叉树总结下算了,于是就有了这篇文章,这些算法有些是自己思考出来的,比如重建二叉树,有些是以前的知识还有些是看了网上其它大牛总结而成,现在贴出来分享,希望大家指正。感觉算法的学习重要的不是算法本身而是算法背后的思想,就是一些本质上的东西,或者说思考的过程,这些才是真正重要的。对于二叉树来说,最重要的是它的递归特性,这是由它的定义决定的。写了这么多二叉树的算法,思考了下,对于二叉树的相关问题,首先要考虑的就是是否能应用它的递归性质。
二叉树常用算法总结
//////////////////////////////////////////////////////////////////////////
// 二叉树相关算法总结
// 2010.10.22
//
// By HappyAngel
// 这里的算法部分来自自己的思考原创,部分来自对网上的总结,收获不少。
//////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <stack>
#include <cstdlib>
#include <queue>
using namespace std;
typedef struct TreeNode
{
TreeNode* pLeftNode;
TreeNode* pRightNode;
char nodeName;
bool flag; //for post-order traverse
int level; //for level traverse
}TreeNode, * PTreeNode;
//recurisively create binary tree
PTreeNode CreateBinaryTree(void)
{
char c;
cin>>c;
PTreeNode pRoot;
if('#' == c)
{
return NULL;
}
else
{
pRoot = new TreeNode;
pRoot->nodeName = c;
pRoot->flag = false;
pRoot->level = 0;
pRoot->pLeftNode = CreateBinaryTree();
pRoot->pRightNode = CreateBinaryTree();
}
return pRoot;
}
void DeleteTree(PTreeNode pRoot)
{
if(NULL != pRoot)
{
DeleteTree(pRoot->pLeftNode);
DeleteTree(pRoot->pRightNode);
delete pRoot;
}
}
void access(const TreeNode& node)
{
cout<<node.nodeName<<" ";
}
//recursely preorder
void PreOrderTraverse(PTreeNode pNode)
{
if(pNode != NULL)
{
access(*pNode);
PreOrderTraverse(pNode->pLeftNode);
PreOrderTraverse(pNode->pRightNode);
}
}
//recursively inorder
void InOrderTraverse(PTreeNode pNode)
{
if(pNode != NULL)
{
InOrderTraverse(pNode->pLeftNode);
access(*pNode);
InOrderTraverse(pNode->pRightNode);
}
}
//recursely postorder
void PostOrderTraverse(PTreeNode pNode)
{
if(pNode != NULL)
{
PostOrderTraverse(pNode->pLeftNode);
PostOrderTraverse(pNode->pRightNode);
access(*pNode);
}
}
//////////////////////////////////////////////////////////////////////////
//非递归前序遍历二叉树
//思路:一样,要用堆栈保存左右子树,保存顺序为先右后左。
//
//////////////////////////////////////////////////////////////////////////
void NonRecursivePreOrder(PTreeNode pNode)
{
stack<PTreeNode> s;
PTreeNode p = pNode;
while(NULL != p || !s.empty())
{
access(*p);
if(NULL != p->pRightNode)
s.push(p->pRightNode);
if(NULL != p->pLeftNode)
s.push(p->pLeftNode);
if(!s.empty())
{
p = s.top();
s.pop();
}
else
p = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
// 非递归中序遍历二叉树
//思路:首先,由于是去掉了递归,必须保存以前访问过的中间节点,所以必须有堆栈。
//其次根据中序遍历的定义,是左中右,所以要找到最左边的叶子节点,并用堆栈保存根节点。
//访问完左节点后(NULL也算一个左节点),出栈并访问中间节点,最后访问右节点,并循环之。
//结束条件:堆栈为空或者指针为空,之所以加入指针为空是为了在最开始的时候不必把根节点压入堆栈。
//可以对所有节点一视同仁,简化判断。因此在中间要判断堆栈不为空才弹。
//////////////////////////////////////////////////////////////////////////
void NonRecursiveInOrder(PTreeNode pNode)
{
stack<PTreeNode> s;
PTreeNode p = pNode;
while(NULL != p || !s.empty())
{
while(NULL != p)
{
s.push(p);
p = p->pLeftNode;
}
if(!s.empty())
{
p = s.top();
s.pop();
access(*p);
p = p->pRightNode;
}
}
}
//////////////////////////////////////////////////////////////////////////
//非递归后序遍历二叉树
//用一个标记标志是否是从右子树回到中间节点,这样,还是先访问左,再访问右,
//最后访问中间。
//
//
//////////////////////////////////////////////////////////////////////////
void NonRecursivePostOrder(PTreeNode pNode)
{
stack<PTreeNode> s;
PTreeNode p = pNode;
while(NULL != p || !s.empty())
{
while(NULL != p)
{
s.push(p);
p = p->pLeftNode;
}
if(!s.empty())
{
p = s.top();
if(p->flag)
{
access(*p);
s.pop();//访问完了再出栈
p = NULL; //右子树已经访问
}
else
{
p->flag = true;
p = p->pRightNode; //访问右子树
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//根据先序和中序遍历的结果重建二叉树 此题原自编程之美3.9
//输入:pPreOrder, pInOrder, nTreeLen
//输出:pRoot
//算法思想:先序集合不变,中序集合是变化的,每次递归做三件事:找根节点 FindRoot ,即查找先序集合中第一个
//同时又在中序集合的节点;第二步,根据根节点切分中序集合 Split ,实质上是把中序分为左右子树集合
//;第三步,递归构建左子树与右子树;
//二叉树最本质的特性就是递归,这是由它的定义决定的,所以递归是解决二叉树问题的神器。
//////////////////////////////////////////////////////////////////////////
const int maxLen = 20;
void Split(char c, char* pInOrder, char** pInOrder1, char** pInOrder2)
{
*pInOrder1 = new char[maxLen];
*pInOrder2= new char[maxLen];
memset(*pInOrder1,0,sizeof(char)*maxLen);
memset(*pInOrder2,0,sizeof(char)*maxLen);
int j1=0;
int j2=0;
int i=0;
for(; NULL != pInOrder[i]; i++)
{
if(c != pInOrder[i])
{
(*pInOrder1)[j1] = pInOrder[i];
j1++;
}
else
break;
}
i++;
for(; NULL != pInOrder[i]; i++)
{
(*pInOrder2)[j2] = pInOrder[i];
j2++;
}
}
char FindRoot(char* pPreOrder, char* pInOrder)
{
char c = NULL;
for(int i=0; NULL != pPreOrder[i]; i++)
{
for(int j=0; NULL != pInOrder[j]; j++)
{
if(pPreOrder[i] == pInOrder[j])
{
c = pPreOrder[i];
return c;
}
}
}
return c;
}
PTreeNode CreateNode(char c)
{
PTreeNode p = new TreeNode;
p->nodeName = c;
p->pLeftNode = NULL;
p->pRightNode = NULL;
return p;
}
PTreeNode Rebuild2(char* pPreOrder, char* pInOrder)
{
if(0 == strlen(pInOrder))
{
return NULL;
}
//找根节点
char c = FindRoot(pPreOrder,pInOrder);
PTreeNode p = CreateNode(c);
char* pInOrder1 = NULL;
char* pInOrder2 = NULL;
//根据根节点切分中序集合
Split(c,pInOrder,&pInOrder1,&pInOrder2);
//重建左子树
p->pLeftNode = Rebuild2(pPreOrder,pInOrder1);
//重建右子树
p->pRightNode = Rebuild2(pPreOrder, pInOrder2);
delete pInOrder1;
delete pInOrder2;
return p;
}
void Rebuild(char* pPreOrder, char* pInOrder, int nTreeLen, PTreeNode* pRoot)
{
*pRoot = Rebuild2(pPreOrder,pInOrder);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//层次遍历二叉树
//思路:应用队列即可轻松实现,在实现上保持与其他遍历代码的一致性,主要体现在
//循环终止条件上
//
//////////////////////////////////////////////////////////////////////////
void LevelTraverse(PTreeNode root)
{
queue<PTreeNode> q;
PTreeNode p = root;
while(NULL != p || !q.empty())
{
if(!q.empty())
{
p = q.front();
q.pop();
}
access(*p);
if(NULL != p->pLeftNode)
{
q.push(p->pLeftNode);
}
if(NULL != p->pRightNode)
{
q.push(p->pRightNode);
}
p = NULL; //to make the program ends after accessing the last node
}
}
//////////////////////////////////////////////////////////////////////////
//非递归打印二叉树中某层次的节点
//
//
//////////////////////////////////////////////////////////////////////////
int PrintNodeAtLevel(PTreeNode root, int level)
{
queue<PTreeNode> q;
PTreeNode p = root;
if(root == NULL || level < 0)
return 0;
int l = 0;
while(NULL != p || !q.empty())
{
if(!q.empty())
{
p = q.front();
q.pop();
}
if(level == p->level)
{
access(*p);
}
if(NULL != p->pLeftNode)
{
q.push(p->pLeftNode);
p->pLeftNode->level = p->level+1;
}
if(NULL != p->pRightNode)
{
q.push(p->pRightNode);
p->pRightNode->level = p->level+1;
}
p = NULL; //to make the program ends after accessing the last node
}
return 1;
}
///////////////////////////////////////////////////////////////////////////
//递归按层打印二叉树,此法把记录层次的任务交给函数本身,乃递归的妙用
//
//////////////////////////////////////////////////////////////////////////
int RecursivePrintAtLevel(PTreeNode root, int level)
{
if(level < 0 || root == NULL)
return 0;
if(level == 0)
{
access(*root);
return 1;
}
return RecursivePrintAtLevel(root->pLeftNode,level-1) + RecursivePrintAtLevel(root->pRightNode,level-1);
}
//////////////////////////////////////////////////////////////////////////
//递归求二叉树的深度
//
//
//////////////////////////////////////////////////////////////////////////
int RecursiveGetTreeDepth(PTreeNode root)
{
if(NULL == root)
return 0;
else
{
int l = RecursiveGetTreeDepth(root->pLeftNode);
int r = RecursiveGetTreeDepth(root->pRightNode);
return (l > r ? l+1 : r+1);
}
}
int main(void)
{
PTreeNode p = CreateBinaryTree();
DeleteTree(p);
getchar();//skip enter
getchar();//pause
return 0;
}