最近在复习算法,做了一下编程之美上面关于二叉树的三道题,感觉顺便就把二叉树总结下算了,于是就有了这篇文章,这些算法有些是自己思考出来的,比如重建二叉树,有些是以前的知识还有些是看了网上其它大牛总结而成,现在贴出来分享,希望大家指正。感觉算法的学习重要的不是算法本身而是算法背后的思想,就是一些本质上的东西,或者说思考的过程,这些才是真正重要的。对于二叉树来说,最重要的是它的递归特性,这是由它的定义决定的。写了这么多二叉树的算法,思考了下,对于二叉树的相关问题,首先要考虑的就是是否能应用它的递归性质。
二叉树常用算法总结
//
// 二叉树相关算法总结
// 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 ;
}
// 二叉树相关算法总结
// 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 ;
}