zoukankan      html  css  js  c++  java
  • 二叉树相关问题合集

    1.如何判断一个二叉树是否是平衡的?

    平衡二叉树,又称AVL树(发明者名字命名,http://en.wikipedia.org/wiki/AVL_tree)。它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度之差之差的绝对值不超过1。

    问题:判断一个二叉排序树是否是平衡二叉树这里是二叉排序树的定义解决方案:根据平衡二叉树的定义,如果任意节点的左右子树的深度相差不超过1,那这棵树就是平衡二叉树。首先编写一个计算二叉树深度的函数,利用递归实现。

    template<typename T>
    static int Depth(BSTreeNode<T>* pbs)
    {
        if (pbs==NULL)
            return 0;
        else
        {
            int ld = Depth(pbs->left);
            int rd = Depth(pbs->right);
            return 1 + (ld >rd ? ld : rd);
        }
    }
    下面是利用递归判断左右子树的深度是否相差1来判断是否是平衡二叉树的函数:
    
    template<typename T>
    static bool isBalance(BSTreeNode<T>* pbs)
    {
        if (pbs==NULL) 
            return true;
        int dis = Depth(pbs->left) - Depth(pbs->right);
        if (dis>1 || dis<-1 )
            return false;
        else
            return isBalance(pbs->left) && isBalance(pbs->right);
    }

    2.打印二叉树中的所有路径

    路径的定义就是从根节点到叶子节点的点的集合。

    还是利用递归:用一个list来保存经过的节点,如果已经是叶子节点了,那么打印list的所有内容;如果不是,那么将节点加入list,然后继续递归调用该函数,只不过,入口的参数变成了该节点的左子树和右子树。

    void printArray(ElemType  a[], int len) {  
        int i;  
        for (i = 0; i < len; i++) {  
            cout<<a[i]<<" ";
        }  
        cout<<endl;  
    }  
    
    
    //打印所有路径
    void printAllPathsRec(BiTree t,ElemType path[],int pathlen)
    {
        if(t==NULL) return;
        //append this node to the path array
        path[pathlen]=t->data;
        pathlen++;
        // it's a leaf, so print the path that led to here  
        if(t->left==NULL&&t->right==NULL)
            printArray(path,pathlen);
        else
        {
            printAllPathsRec(t->left,path,pathlen);
            printAllPathsRec(t->right,path,pathlen);
        }
    }
    
    void printAllPaths(BiTree t)
    {
        ElemType path[1000];
        printAllPathsRec(t,path,0);
    }

    3..在二叉树中找出和为某一值的所有路径?

    其实只要能打印出所有路径,看是否满足和为sum就可以了。

    例如输入整数22和如下二元树
                                                10
                                                /  
                                               5   12
                                             /     
                                           4     7  
    则打印出两条路径:10, 12和10, 5, 7。

    分析:这是百度的一道笔试题,考查对树这种基本数据结构以及递归函数的理解。
    当访问到某一结点时,把该结点添加到路径上,并累加当前结点的值。如果当前结点为叶结点并且当前路径的和刚好等于输入的整数,则当前的路径符合要求,把它打印出来。如果当前结点不是叶结点,则继续访问它的子结点。当前结点访问结束后,递归函数将自动回到父结点。因此我们在函数退出之前要在路径上删除当前结点并减去当前结点的值,以确保返回父结点时路径刚好是根结点到父结点的路径。我们不难看出保存路径的数据结构实际上是一个栈结构,因为路径要与递归调用状态一致,而递归调用本质就是一个压栈和出栈的过程

    参考代码:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    struct Node
    {
        int value;
        Node* left;
        Node* right;
        Node(){left=NULL;right=NULL;}
        Node(int v){value=v;left=NULL;right=NULL;}
    };
    
    void findpath(Node* root,vector<int>& nodes,int sum)
    {
        if(root == NULL) return;
        nodes.push_back(root->value);
        if(root->left == NULL && root->right == NULL)
        {
            if(root->value == sum)
            {
                for(int i=0;i<nodes.size();i++)
                    cout<<nodes[i]<<" ";
                cout<<endl;
            }
        }
        else
        {
            if(root->left != NULL)
            {
                findpath(root->left,nodes,sum-root->value);
            }
            if(root->right != NULL)
            {
                findpath(root->right,nodes,sum-root->value);
            }
        }
        nodes.pop_back();
    }
    
    int main()
    {
        Node *tmp ;
        Node* root = new Node(10);
        tmp = new Node(5);
        root->left = tmp ;
        tmp = new Node(12);
        root->right = tmp;
        tmp = new Node(4);
        root->left->left = tmp;
        tmp = new Node(7);
        root->left->right = tmp;
        vector<int> v;
        findpath(root,v,22); 
        return 0;
    } 

    3.怎样编写一个程序,把一个有序整数数组放到二叉树中?

    递归,还是利用递归:

    设有int array[begin,end],首先将array[(begin + end)/2]加入二叉树,然后递归去做array[begin,(begin + end)/2 - 1]和array[(begin + end)/2 + 1, end]。注意写好函数的形式就可以了。一切都很自然。

    4.判断整数序列是不是二叉搜索树的后序遍历结果?

    看看吧,后续遍历是这样做的:左右根,所以访问的最有一个节点实际上就是整棵二叉树的根节点root:然后,找到第一个大于该节点值的根节点b,b就是root右子树最左边的节点(大于根节点的最小节点)。那么b前面的就是root的左子树。既然是二叉搜索树的遍历结果,那么在b和root之间的遍历结果,都应该大于b。去拿这个作为判断的条件。

    5。将二叉查找树变为有序的双向链表

    要求不能创建新节点,只调整指针。
    递归解法:
    (1)如果二叉树查找树为空,不需要转换,对应双向链表的第一个节点是NULL,最后一个节点是NULL
    (2)如果二叉查找树不为空:

    如果左子树为空,对应双向有序链表的第一个节点是根节点,左边不需要其他操作;
    如果左子树不为空,转换左子树,二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点,同时将根节点和左子树转换后的双向有序链 表的最后一个节点连接;
    如果右子树为空,对应双向有序链表的最后一个节点是根节点,右边不需要其他操作;
    如果右子树不为空,对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点,同时将根节点和右子树转换后的双向有序链表的第一个节点连 接。

    参考代码如下:

    /******************************************************************************
    参数:
    pRoot: 二叉查找树根节点指针
    pFirstNode: 转换后双向有序链表的第一个节点指针
    pLastNode: 转换后双向有序链表的最后一个节点指针
    ******************************************************************************/
    void Convert(BinaryTreeNode * pRoot, 
                 BinaryTreeNode * & pFirstNode, BinaryTreeNode * & pLastNode)
    {
        BinaryTreeNode *pFirstLeft, *pLastLeft, * pFirstRight, *pLastRight;
        if(pRoot == NULL) 
        {
            pFirstNode = NULL;
            pLastNode = NULL;
            return;
        }
    
        if(pRoot->m_pLeft == NULL)
        {
            // 如果左子树为空,对应双向有序链表的第一个节点是根节点
            pFirstNode = pRoot;
        }
        else
        {
            Convert(pRoot->m_pLeft, pFirstLeft, pLastLeft);
            // 二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点
            pFirstNode = pFirstLeft;
            // 将根节点和左子树转换后的双向有序链表的最后一个节点连接
            pRoot->m_pLeft = pLastLeft;
            pLastLeft->m_pRight = pRoot;
        }
    
        if(pRoot->m_pRight == NULL)
        {
            // 对应双向有序链表的最后一个节点是根节点
            pLastNode = pRoot;
        }
        else
        {
            Convert(pRoot->m_pRight, pFirstRight, pLastRight);
            // 对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点
            pLastNode = pLastRight;
            // 将根节点和右子树转换后的双向有序链表的第一个节点连接
            pRoot->m_pRight = pFirstRight;
            pFirstRight->m_pLeft = pRoot;
        }
    
        return;
    }

     更多:http://www.bkjia.com/Pythonjc/776842.html

    6.判断一个节点是否在一颗子树中

    可以和当前根节点相等,也可以在左子树或者右子树中。 

     bool BinTree::is_in_tree(BinTreeNode *r,BinTreeNode *t)const
     {
         if(r == NULL)
         {
             return false;
         }
         else if(r == t)
         {
             return true;
         }
         else
         {
             bool has = false;
             if(r->get_left() != NULL)
             {
                 has = is_in_tree(r->get_left(),t);
             }
             if(!has && r->get_right()!= NULL)
             {
                 has = is_in_tree(r->get_right(),t);
             }
             return has;
         }
     }

    LCA:求两个节点的最近公共祖先

    求两个节点的公共祖先可以用到上面的:判断一个节点是否在一颗子树中。(1)如果两个节点同时在根节点的右子树中,则最近公共祖先一定在根节点的右子树中。(2)如果两个节点同时在根节点的左子树中,则最近公共祖先一定在根节点的左子树中。(3)如果两个节点一个在根节点的右子树中,一个在根节点的左子树中,则最近公共祖先一定是根节点。当然,要注意的是:可能一个节点pNode1在以另一个节点pNode2为根的子树中,这时pNode2就是这两个节点的最近公共祖先了。显然这也是一个递归的过程啦:

    1 //求两个节点的最近公共祖先
     2 BinTreeNode* BinTree::get_nearest_common_father(BinTreeNode *r,BinTreeNode *pNode1,BinTreeNode *pNode2)const
     3 {
     4     //pNode2在以pNode1为根的子树中(每次递归都要判断,放在这里不是很好。)
     5     if(is_in_tree(pNode1,pNode2))
     6     {
     7         return pNode1;
     8     }
     9     //pNode1在以pNode2为根的子树中
    10     if(is_in_tree(pNode2,pNode1))
    11     {
    12         return pNode2;
    13     }
    14     bool one_in_left,one_in_right,another_in_left,another_in_right;
    15     one_in_left = is_in_tree(r->get_left(),pNode1);
    16     another_in_right = is_in_tree(r->get_right(),pNode2);
    17     another_in_left = is_in_tree(r->get_left(),pNode2);
    18     one_in_right = is_in_tree(r->get_right(),pNode1);
    19     if((one_in_left && another_in_right) || (one_in_right && another_in_left))
    20     {
    21         return r;
    22     }
    23     else if(one_in_left && another_in_left)
    24     {
    25         return get_nearest_common_father(r->get_left(),pNode1,pNode2);
    26     }
    27     else if(one_in_right && another_in_right)
    28     {
    29         return get_nearest_common_father(r->get_right(),pNode1,pNode2);
    30     }
    31     else
    32     {
    33         return NULL;
    34     }
    35 }

    递归解法:
    (1)如果两个节点分别在根节点的左子树和右子树,则返回根节点
    (2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树
    参考代码如下:
    bool FindNode(BinaryTreeNode * pRoot, BinaryTreeNode * pNode)
    {
        if(pRoot == NULL || pNode == NULL)
            return false;
    
        if(pRoot == pNode)
            return true;
    
        bool found = FindNode(pRoot->m_pLeft, pNode);
        if(!found)
            found = FindNode(pRoot->m_pRight, pNode);
    
        return found;
    }
    
    BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, 
                                         BinaryTreeNode * pNode1, 
                                         BinaryTreeNode * pNode2)
    {
        if(FindNode(pRoot->m_pLeft, pNode1))
        {
            if(FindNode(pRoot->m_pRight, pNode2))
                return pRoot;
            else
                return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2);
        }
        else
        {
            if(FindNode(pRoot->m_pLeft, pNode2))
                return pRoot;
            else
                return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2);
        }
    }

    递归解法效率很低,有很多重复的遍历,下面看一下非递归解法。
    非递归解法:
    先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点
    参考代码如下:

    bool GetNodePath(BinaryTreeNode * pRoot, BinaryTreeNode * pNode, 
                     list<BinaryTreeNode *> & path)
    {
        if(pRoot == pNode)
        {    
            path.push_back(pRoot);
            return true;
        }
        if(pRoot == NULL)
            return false;
        path.push_back(pRoot);
        bool found = false;
        found = GetNodePath(pRoot->m_pLeft, pNode, path);
        if(!found)
            found = GetNodePath(pRoot->m_pRight, pNode, path);
        if(!found)
            path.pop_back();
        return found;
    }
    BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1, BinaryTreeNode * pNode2)
    {
        if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
            return NULL;
        list<BinaryTreeNode*> path1;
        bool bResult1 = GetNodePath(pRoot, pNode1, path1);
        list<BinaryTreeNode*> path2;
        bool bResult2 = GetNodePath(pRoot, pNode2, path2);
        if(!bResult1 || !bResult2) 
            return NULL;
        BinaryTreeNode * pLast = NULL;
        list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
        list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
        while(iter1 != path1.end() && iter2 != path2.end())
        {
            if(*iter1 == *iter2)
                pLast = *iter1;
            else
                break;
            iter1++;
            iter2++;
        }
        return pLast;
    }

    最近公共祖先LCA:Tarjan算法

     --------------------------------------------------------------

    一篇文章:

    LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。

       LCA算法有在线算法也有离线算法,所谓的在线算法就是实时性的,比方说,给你一个输入,算法就给出一个输出,就像是http请求,请求网页一样。给一个实时的请求,就返回给你一个请求的网页。而离线算法则是要求一次性读入所有的请求,然后在统一得处理。而在处理的过程中不一定是按照请求的输入顺序来处理的。说不定后输入的请求在算法的执行过程中是被先处理的。

          本文先介绍一个离线的算法,就做tarjan算法(发明者名字,http://baike.baidu.com/view/4064042.htm)。这个算法是基于并查集和DFS的。Dfs的作用呢,就是递归,一次对树中的每一个节点进行处理。而并查集的作用就是当dfs每访问完(注意,这里是访问完)到一个点的时候,就通过并查集将这个点,和它的子节点链接在一起构成一个集合,也就是将并查集中的pnt值都指向当前节点。这样就把树中的节点分成了若干个的集合,然后就是根据这些集合的情况来对输入数据来进行处理。

          比方说当前访问到的节点是u,等u处理完之后呢,ancestor[u]就构成了u的集合中的点与u点的LCA,而ancestor[fa[u]]就构成了,u的兄弟节点及其兄弟子树的集合中点与u的LCA,而ancestor[fa[fa[u]]]就构成了u的父亲节点的兄弟节点及其兄弟子树的集合中的点与u的LCA。然后依次类推,这样就构成了这个LCA的离线算法。

    下面来分析一下代码:

    int findp(int x)

    {

        if(pnt[x]!=x)   pnt[x] = findp(pnt[x]);

        return pnt[x];

    }                            

    int unionset(int x,int y)

    {

        int x = findp(x);

        int y = findp(y);

        pnt[y] = x;

    }      //以上两步是并查集的操作,没啥过多解释。

    void Lcancestor(int parent)

    {

        pnt[parent] = parent;   //当访问到一个点的时候,先将其自己形成一个集合

        ancestor[findp(parent)] = parent; 

        for(int i=0;i<=child[parent].size();i++)

        {     //接着一次访问节点的子节点,

            Lcancestor(child[parent][i]);   //依次对子节点进行访问。

            unionset(parent,child[parent][i]);   //在处理完后,将子节点的集合链接到父节点

            ancestor[findp(child[parent][i])] = parent;

        }   //实际上这一步起到了并查集的压缩节点的作用。这样可以将查询降低到O(1)

        color[parent] = true;

        if( parent = first && color[second] )     //这里的first和second主要针对的是查询的每次操作时输入的两个数。

        {

            ans = ancestor[findp(second)] ;

        }

        if( parent = second && color[first] )

        {

            ans = ancestor[findp(first)];

        }

    }

    LCA还有其他的算法,例如,将每个点到根节点的路径构成一个链表,那么LCA就是求两个链表的公共节点中位置最靠后的一个点。还有的LCA可以与RMQ问题结合起来,至于什么事RMQ问题,将会在下一篇博文中给出解释。

    转自:http://blog.sina.com.cn/s/blog_509a46a60100tkyq.html

     http://wenku.baidu.com/link?url=H8ESxv08tV7qJA151kHKn0g7WGCLocX2QiSFwHGpdd1NKd_VXRgawWxXsOKaGvyklhIRKyj909PWaq-KLfvrlYGKXdMrA2zWR8XEuiQo-FS

    http://kmplayer.iteye.com/blog/604518

    7.求二叉树中叶子节点的个数
    递归解法:
    (1)如果二叉树为空,返回0
    (2)如果二叉树不为空且左右子树为空,返回1
    (3)如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数
    参考代码如下:

    int GetLeafNodeNum(BinaryTreeNode * pRoot)
    {
        if(pRoot == NULL)
            return 0;
        if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
            return 1;
        int numLeft = GetLeafNodeNum(pRoot->m_pLeft); // 左子树中叶节点的个数
        int numRight = GetLeafNodeNum(pRoot->m_pRight); // 右子树中叶节点的个数
        return (numLeft + numRight);
    }

    更多:

    http://blog.csdn.net/luckyxiaoqiang/article/details/7518888

    http://blog.csdn.net/randyjiawenjie/article/details/6772145

    http://blog.sina.com.cn/s/blog_95bf1ccc01017wm4.html

  • 相关阅读:
    诚聘Python等课程兼职讲师
    Ogre3d
    OGRE中 场景管理器,场景节点和实体
    第四天:原型模式建造者模式
    第二天:装饰模式及面向对象设计原则4则
    表达式求值:面向对象版本
    第五天:模板方法外观模式观察者模式
    第三天:代理模式工厂方法抽象工厂
    第一天:简单工厂与策略模式
    idea files count
  • 原文地址:https://www.cnblogs.com/youxin/p/3803220.html
Copyright © 2011-2022 走看看