要检查它是完整的二叉树还是完全二叉树,或者两者都不是
To check wether it's a complete binary tree or fully binary tree or neither of the two
我对二叉树的概念很陌生。我被一个问题困了很多天了。它是要找出给定的树是二叉树还是完全二叉树,或者两者都不是。
我想过很多算法,但没有一个能满足每一个案例。我尝试了谷歌,但没有适当的解决方案。
我想过使用关卡顺序遍历技术,但在将所有节点插入队列后,我想不出如何知道它们的级别。
对于完全二叉树,我尝试计算所有节点的度数是否为 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
当我们分析它们时,我们必须知道在子树中发生了什么,其中B
和C
是根。因此,当我们比较B
和C
时,B
(左(将具有对值(3,2(,其中3代表H
,I
和深度为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
该算法生成一个"深度"数字,表示您可以沿着l1
、l2
、r1
和r2
路径走多远。 假设只有具有*
的节点实际存在 - 即 l
和l1
存在,但l2
不存在 - 那么l1
的深度将是2,但l2
的深度将是1。 r
根本不存在,那么r1
和r2
将是 0。
然后算法观察到:如果l1
、l2
、r1
和r2
相等,那么树是"完美的"。 如果它们不同,那么它可能仍然是一棵完整的树,但深度必须在某处减少 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));
}
}
- 从父数组测试用例构造二叉树失败
- 打印时有二叉树问题.用户输入不打印任何内容
- 试图找到二叉树的深度
- 二叉树结构平衡,使用递归时EXC_BAD_ACCESS
- 指向二叉树中新节点的指针
- 我试图用这段代码找到二叉树的高度,但它一直返回 0,有人可以告诉我为什么吗?
- C++:如何计算二叉树中其值模块高度小于 2 的节点数?
- 二叉树级别顺序遍历在leetcode中
- 运行无限循环的最小二叉树问题
- 打印二叉树中的常见元素
- 二叉树基准测试结果
- 带有矢量重复值的二叉树打印出来
- 签入二叉树的函数是平衡C++
- 我有两棵二叉树.我想在不更改输入树的情况下深度复制两个二叉树的结果
- 如何计算给定数字在二叉树中出现的次数?
- 为什么我的二叉树会覆盖其根的叶子?
- 将二叉树的节点插入链表 (C++)
- 二叉树级别顺序插入 c++
- 在 C++ 中创建两个不同的二叉树时出现分段错误
- 要检查它是完整的二叉树还是完全二叉树,或者两者都不是