zoukankan      html  css  js  c++  java
  • 二叉树常用算法总结

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

     

      

    二叉树常用算法总结
    //////////////////////////////////////////////////////////////////////////
    // 二叉树相关算法总结
    // 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;
    }
  • 相关阅读:
    windows10下安装zookeeper kafka
    WisDom.Net 框架设计(八) 持久层
    WisDom.Net 框架设计(五) 权限设计
    WisDom.Net 框架设计(二) 服务总线
    业务安全篇-渗透中的表单测试
    【网摘】身在腾讯:浪潮之巅的惶惑与自救
    短链生成
    【读书笔记】——读书之前
    config 设置的两种方式
    ping通但是浏览器打不开网页解决
  • 原文地址:https://www.cnblogs.com/HappyAngel/p/1859875.html
Copyright © 2011-2022 走看看