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

To check wether it's a complete binary tree or fully binary tree or neither of the two

本文关键字:完全二叉树 或者 两者都 检查 二叉树      更新时间:2023-10-16

我对二叉树的概念很陌生。我被一个问题困了很多天了。它是要找出给定的树是二叉树还是完全二叉树,或者两者都不是。

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

我想过使用关卡顺序遍历技术,但在将所有节点插入队列后,我想不出如何知道它们的级别。

对于完全二叉树,我尝试计算所有节点的度数是否为 0 或 2,但如果树有一些具有度数的中间节点,则此逻辑也是错误的。

我使用链表制作了一棵树,基本 - 左孩子,右孩子关系方式。

对于完全二叉树,我做了一个无序遍历并检查了度数,如果为 0 或 2,但如果有一个在某个较早级别的节点度数为 0,那么输出也会成真,这是错误的。

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

谢谢。

而且我正在使用C++,所以如果逻辑使用指针,那就没关系了。

检查完整很容易:
根据这里的定义。http://courses.cs.vt.edu/cs3114/Summer11/Notes/T03a.BinaryTreeTheorems.pdf

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

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();
}

请考虑执行以下操作:

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;
}

函数以递归方式调用,直到它到达返回每个叶子深度的叶子,然后比较深度,如果它们相等(递归在每个子树中达到相同的深度(,则返回深度的值。因此,如果树未满,该函数将返回-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代表HI和深度为3的J,2代表缺少其右子节点的E节点。 C(右(也将具有值(3,2(,因此树是不完整的,因为两个子树中都有"破裂",因此并非所有节点都左对齐。

一种方法确实可能是级别顺序遍历,正如您所建议的那样,将其实现为 BFS,并推送到您的队列对 (level,node(。树已满当且仅当除最后一个级别之外的每个级别具有上一个或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^level 节点,并返回 true,否则返回 false - 这意味着它检查树是否已满。

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

以下是使用节省空间的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.

伪代码:-

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")
}      

下面的代码为"已完成" - 注释显示要删除的半线以测试完美的二叉树(即所有叶子深度相同的二叉树(。 这是可编译的代码,末尾有 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路径走多远。 假设只有具有*的节点实际存在 - 即 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 个(,以考虑来自父节点的最多四个子路径。

一次考虑四个

子路径有点不寻常和复杂,但它使我们能够轻松检测到四个节点的深度相差超过 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 childrenn";
        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 childrenn";
        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 childrenn";
        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 leveln";
    {
        std::cerr << "left node left child, right node two childrenn";
        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 childrenn";
        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 childn";
        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 childn";
        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));
    }
}