检查它是完整的二叉树还是完全二叉树,或者两者都不是

时间:2013-12-05 10:30:09

标签: c++ algorithm tree

我是二元树的概念新手。我被困在一个问题很多天了。它是为了查找给定的树是二叉树还是完全二叉树,或者两者都不是。

我想到了很多算法,但没有一个能满足每一个案例。 我试过谷歌,但没有适当的解决方案。

我想过使用Level Order Traversal Technique但是在将所有节点插入队列之后无法知道如何知道它们的级别。

对于完全二进制树,我尝试计算所有节点的度数是0还是2但是如果树有一个度数的中间节点这个逻辑也是错误的。

我使用链表创建了一个树,基本 - 左子,右子关系方式。

对于完全二叉树,我做一个inorder traverl并检查度数是否为0或2,但这是错误的,如果某个节点在某个较早的级别为0,那么输出也会生效。

对于完整的二叉树,我无法想出任何合适的东西。

谢谢。

我正在使用C ++,所以如果逻辑使用指针那么它就没问题了。

5 个答案:

答案 0 :(得分:7)

检查完全很容易:
按此定义。 http://courses.cs.vt.edu/cs3114/Summer11/Notes/T03a.BinaryTreeTheorems.pdf

如果所有节点都有0个或2个子节点,则树已满。

bool full(Node* tree)
{
    // Empty tree is full so return true.
    // This also makes the recursive call easier.
    if (tree == NULL)
    {   return true;
    }

    // count the number of children
    int count = (tree->left == NULL?0:1) + (tree->right == NULL?0:1);

    // We are good if this node has 0 or 2 and full returns true for each child.
    // Don't need to check for left/right being NULL as the test on entry will catch
    // NULL and return true.
    return count != 1 && full(tree->left) && full(tree->right);
}

完成有点困难 但最简单的方法似乎是遍历树的广度(从左到右)。在每个节点上,遍历列表的左侧和右侧(即使它们是NULL)。在达到第一个NULL之后,应该只剩下要查找的NULL对象。如果在此之后找到非NULL对象,则它不是完整的树。

bool complete(Node* tree)
{
    // The breadth first list of nodes.
    std::list<Node*>  list;
    list.push_back(tree);   // add the root as a starting point.

    // Do a breadth first traversal.
    while(!list.empty())
    {
        Node* next = list.front();
        list.pop_front();

        if (next == NULL)
        {   break;
        }

        list.push_back(next->left);
        list.push_back(next->right);
    }

    // At this point there should only be NULL values left in the list.
    // If this is not true then we have failed.

    // Written in C++11 here.
    // But you should be able to traverse the list and look for any non NULL values.
    return std::find_if(list.begin(), list.end(), [](Node* e){return e != NULL;}) != list.end();
}

答案 1 :(得分:3)

考虑如下操作:

int is_full_tree(node *root, int depth){
if (root->left != NULL && root->right != NULL){
    int left = is_full_tree(root->left, depth+1);
    int right = is_full_tree(root->right, depth+1);
    if (left == right && left != -1)
        return left; // or right doesn't matter
    else
        return -1;
}
else if (root->left == NULL && root->right == NULL)
    return depth;
return -1;
}

函数get被递归调用,直到它到达返回每个离开深度的叶子,然后比较深度,如果它们相等(递归在每个子树中达到相同的深度),则返回深度值。因此,如果树未满,则函数返回-1,如果树已满,则返回一些表示深度的值。

第一个电话应该是is_full_tree(root,0)

修改

检查树是否是完整的二叉树(所有级别都有除之外的所有节点最后一个节点并且所有节点都被推到左侧),因此如果深度为叶子相等,或者左边比右边的那边大1(相反的东西不成立),因此我们修改如下:

std::pair<int,int> is_complete_tree(node *root, int depth){
    if (root->left != NULL && root->left != NULL){
        std::pair<int,int> left = is_complete_tree(root->left, depth+1);
        std::pair<int,int> right = is_complete_tree(root->right, depth+1);
        if (left.first != -1 && left.second != -1 && right.first != -1 && right.second != -1)
            if (left.first == right.first && left.first == left.second)
                return right; //assuming right.first - right.second == 1 or 0
            else if (left.first == left.second && right.first == right.second && left.first - right.first == 1)
                return std::pair<int,int>(left.first,right.first); 
            else if (left.first - left.second == 1 && left.second == right.first  && right.first == right.second)
                return left;
            else
                return std::pair<int,int>(-1,-1);
        else
            return std::pair<int,int>(-1,-1);
    }

    else if (root->left != NULL && root->right == NULL)
        if (root->left->right == NULL && root->left->left == NULL)
            return std::pair<int,int>(depth+1,depth); // the root->left is NULL terminated
        else 
            return std::pair<int,int>(-1,-1); // the .left is not NULL terminated

    else if (root->left == NULL && root->right == NULL) //condition for leaves
        return std::pair<int,int>(depth,depth);
    return std::pair<int,int>(-1,-1); // if .left == NULL and .right != NULL
    }

你也可以将第二个算法概括为两件事。您可以添加一个标记,该标记将作为参数逐个引用,并且只有在第一个else if评估为true时才会被修改,这将意味着父母的左深度加长1然后他的正确的深度。因此,如果它是完整的树,算法将再次返回树的深度,否则返回-1。

第二个算法的想法与第一个算法相同。不同之处在于我们必须跟踪“最大”和“最小”深度(没有最大和最小深度这样的东西但是这个想法背后的直觉,“最小深度”将是深度的在任何子树中记录的只有1(左)子的最深节点,以便能够分析树,例如:

         A
      /    \
    B        C
   /\        /\
 D    E    F    G
/\   /\   /\   /\
H I J     K L  M

我们必须知道在我们分析它们时BC是根的子树中发生了什么。因此,当我们比较BC时,B(左)将具有对值(3,2),其中3代表H,{{ 1}}和I深度为3,而J节点缺少他的右孩子。 E(右)也将具有值(3,2),因此树不完整,因为两个子树中都存在“破裂”,因此并非所有节点都保持对齐。

答案 2 :(得分:2)

执行此操作的一种方法实际上可能是级别顺序遍历,如您所建议的那样,将其实现为BFS,并推送到(级别,节点)的队列对。 当且仅当除了最后一个级别之外的每个级别都是前一个节点数量的两倍或2^level 时,树已满。

查看以下伪代码:

is_full_binary(root) { 
  queue q = new queue()
  q.push(pair(0,root))
  int currentNodes = 0
  int current = 0
  while q.isEmpty() == false { 
    level, node = q.pop()
    q.push(pair(level+1, node.left)) // make sure such exist before...
    q.push(pair(level+1, node.right)) //same here
    if level == current
         currentNodes++
    else {
         if currentNodes != 2^current
              return false
         currentNodes = 0
         current = level
    }
  }
return true
}

上面的伪代码检查每个级别,如果它具有正好2 ^级别的节点,并返回true,否则返回false - 意味着它检查树是否已满。

检查它是否已满,但是完整需要为最后一级做更多的工作 - 并留给你,它的概念将非常相似。

答案 3 :(得分:0)

下面的代码为“完成” - 注释显示要删除的半行以测试完美的二叉树(即所有叶子在相同深度的树)。这是可编译的代码,最后有3个测试用例。

算法位为depths(),在此处转载以供讨论:内联及以下额外注释(且无cout跟踪)。

LR depths(Node& n)
{
    // NULL/NULL is handled correctly below anyway, but nice not to have to think about it
    if (n.l == NULL && n.r == NULL) return LR();

    // get the depths of the child nodes... LR() is (0,0)
    LR l12 = n.l ? ++depths(*n.l) : LR();
    LR r12 = n.r ? ++depths(*n.r) : LR();

    // >= ensures left-hand branches are as deep or deeper, i.e. left packing
    if (l12.l >= l12.r && l12.r >= r12.l && r12.l >= r12.r &&
        // also check the leftmost-to-rightmost depth range is 0 (full tree below)
        // or 1 (perfect tree)
        (l12.l == r12.r || l12.l == 1 + r12.r))
        return LR(l12.l, r12.r);

    throw false; // children can't be part of a full or complete tree
}

解释这个概念 - 考虑看起来像这样的任何树(或其中一部分),其中子/孙节点可能存在或不存在:

     Node
     /   \
  *l       r 
  / \     / \
*l1 l2   r1 r2

该算法生成一个“深度”数字,表示您可以走l1l2r1r2路径的距离。假设只有* s存在的节点 - 即ll1存在但l2不存在 - 那么l1的深度将为2,但l2将为1. r根本不存在,则r1r2将为0.

然后算法观察:如果l1l2r1r2相等,则树是“完美的”。如果它们不同,那么它可能仍然是一棵完整的树,但深度必须在某处减少1:例如如果孙子是叶子节点,那么(2,2,2,1)或(2,2,1,1)或(2,1,1,1)或(1,1,1,0)或(1) ,1,0,0)或(1,0,0,0)。测试它的最简单方法是检查l1 >= l2 >= r1 >= r2 && l1 == r2 + 1 - 即从不增加深度,并且两端的深度仅相差1。

最后,算法深入到树中,直到它处理一个至少有一个孩子的节点,然后进行验证,然后传递跨越范围的下端和下端(最多相隔1个)。考虑来自父节点的最多四个子路径。

一次考虑四个子路径有点不寻常和复杂,但它允许我们轻松检测到四个节点之间深度相差超过1的情况。

如果我一次只考虑2个节点,那么如果没有额外的已经步进或最大深度可见的变量,则无法检测到具有两个步骤的树...但正如Vikram的答案所证明的那样 - 使用这样的变量更容易。

完整代码:

#include <iostream>
#include <algorithm>
#include <string>
#include <cassert>

struct Node
{
    std::string n;
    Node* l;
    Node* r;
    Node(const char n[]) : n(n), l(0), r(0) { }
    Node(const char n[], Node& l, Node& r) : n(n), l(&l), r(&r) { }
    Node(const char n[], Node& l, int) : n(n), l(&l), r(0) { }
    Node(const char n[], int, Node& r) : n(n), l(0), r(&r) { }
};

struct LR
{
    int l, r;
    LR(int l, int r) : l(l), r(r) { }
    LR() : l(0), r(0) { }
    LR& operator++() { ++l; ++r; return *this; }
    bool operator==(const LR& rhs) const { return l == rhs.l && r == rhs.r; }
};

std::ostream& operator<<(std::ostream& os, const LR& lr)
{ return os << lr.l << ',' << lr.r; }

LR depths(Node& n)
{
    if (n.l == NULL && n.r == NULL) return LR();
    LR l12 = n.l ? ++depths(*n.l) : LR();
    LR r12 = n.r ? ++depths(*n.r) : LR();
    if (l12.l >= l12.r && l12.r >= r12.l && r12.l >= r12.r &&
        (l12.l == r12.r || l12.l == 1 + r12.r))
    {
        LR result = LR(l12.l, r12.r);
                std::cout << "depths(" << n.n << "), l12 " << l12 << ", r12 " << r12
                  << ", result " << result << '\n';
        return result;
    }
    std::cerr << "!complete @ " << n.n << ' ' << l12 << ' ' << r12 << '\n';
    throw false;
}

bool is_complete_tree(Node& root)
{
    try
    {
        depths(root);
        return true;
    }
    catch (...)
    {
        return false;
    }
}

int main()
{
    {
        std::cerr << "left node no children, right node two children\n";
        Node rl("rl"), rr("rr"), r("r", rl, rr), l("l"), root("root", l, r);
        assert(!is_complete_tree(root));
    }

    {
        std::cerr << "left node two children, right node no children\n";
        Node ll("ll"), lr("lr"), l("l", ll, lr), r("r"), root("root", l, r);
        assert(is_complete_tree(root));
    }

    {
        std::cerr << "left node two children, right node two children\n";
        Node ll("ll"), lr("lr"), l("l", ll, lr);
        Node rl("rl"), rr("rr"), r("r", rl, rr);
        Node root("root", l, r);
        assert(is_complete_tree(root));
    }

    std::cerr << ">>> test 3-level tree with 1 missing leaf at 3rd level\n";

    {
        std::cerr << "left node left child, right node two children\n";
        Node ll("ll"), l("l", ll, 0);
        Node rl("rl"), rr("rr"), r("r", rl, rr);
        Node root("root", l, r);
        assert(!is_complete_tree(root));
    }

    {
        std::cerr << "left node right child, right node two children\n";
        Node           lr("lr"), l("l", 0, lr);
        Node rl("rl"), rr("rr"), r("r", rl, rr);
        Node root("root", l, r);
        assert(!is_complete_tree(root));
    }

    {
        std::cerr << "left node two children, right node left child\n";
        Node ll("ll"), lr("lr"), l("l", ll, lr);
        Node rl("rl"),           r("r", rl, 0 );
        Node root("root", l, r);
        assert(is_complete_tree(root));
    }

    {
        std::cerr << "left node two children, right node right child\n";
        Node ll("ll"), lr("lr"), l("l", ll, lr);
        Node           rr("rr"), r("r", 0,  rr);
        Node root("root", l, r);
        assert(!is_complete_tree(root));
    }
}

答案 4 :(得分:0)

以下是使用节省空间的DFS算法而不是BFS算法的简单解决方案: -

1. Use DFS on tree
2. Record the min depth & max depth among all paths from root to null
3. if max depth == min depth then it is full binary tree
4. if max depth == min depth + 1 && flag < 2 where denotes change in depth of null from left then complete binary tree
5. else not both.

Psuedo代码: -

void caldepths(Node p,int depth) {

   if(p==null) {

      if(max!=-infinity && max<depth) {

         flag = 2;
      }

      if(max<depth)
          max = depth

      if(min>depth)
          min = depth



      if(max!=-infinity && depth<max && flag==0) {
           flag = 1; 
      }
      if(depth==max && flag==1) {

            flag = 2;   
      }

   }

  else {

      caldepths(p.left,depth+1)
      caldepths(p.right,depth+1)
  }

}

void caltype(root) {

   max = -infinity
   min = infinity
   flag = 0;
   caldepths(root,0)
   if(max == min)
      print("full binary tree")
   if(max == min+1 && flag<2)
      print("complete binary tree")
   else print("Not both")
}