跳转至

EC17-判断二叉树是否为满二叉树

代码实现

C
// 函数 func:递归计算二叉树的节点总数和最大深度
// 参数:
// p - 当前节点
// n - 指向节点总数的指针
// L - 指向当前深度的指针
// max_L - 指向最大深度的指针
void func(BiTree p, int *n, int *L, int *max_L) {
    if (p != NULL) {
        ++(*L); // 当前深度加 1

        // 更新最大深度
        if ((*L) > (*max_L)) {
            *max_L = *L;
        }

        ++(*n); // 节点总数加 1

        // 递归处理左子树和右子树
        func(p->lchild, n, L, max_L);
        func(p->rchild, n, L, max_L);

        --(*L); // 回溯时深度减 1(恢复状态)
    }
}

// 函数 Is_True:判断二叉树是否为满二叉树
bool Is_True(BiTree p) {
    int n = 0;      // 节点总数
    int L = 0;      // 当前深度
    int max_L = 0;  // 最大深度

    // 调用辅助函数 func 计算节点总数和最大深度
    func(p, &n, &L, &max_L);

    // 判断是否满足满二叉树性质
    // 满二叉树的节点总数 = 2^max_L - 1
    if (pow(2, max_L) - 1 == n) {
        return true; // 是满二叉树
    } else {
        return false; // 不是满二叉树
    }
}

代码逻辑解析

1. 满二叉树的定义

  • 满二叉树是指每个非叶子节点都有两个子节点,并且所有叶子节点都在同一层。
  • 对于深度为 h 的满二叉树,其节点总数为: [ n = 2^h - 1 ] 其中 h 是树的高度(从根节点开始计数,根节点高度为 1)。

2. 算法思路

  • 使用递归遍历整棵树,同时记录以下信息:
  • 节点总数 n
  • 树的最大深度 max_L
  • 遍历完成后,检查是否满足满二叉树的性质: [ n = 2^{max_L} - 1 ]

3. func 函数详解

  • 递归终止条件:如果当前节点为空,则返回。
  • 递归过程
  • 增加当前深度 L
  • 如果当前深度大于已知的最大深度 max_L,更新 max_L
  • 增加节点总数 n
  • 递归访问左子树和右子树。
  • 回溯时减少当前深度 L(恢复状态)。

4. Is_True 函数详解

  • 调用 func 函数计算节点总数 n 和最大深度 max_L
  • 根据公式 \( 2^{max\_L} - 1 \) 计算满二叉树应有的节点总数。
  • 比较实际节点总数 n 和理论值,判断是否为满二叉树。

时间复杂度分析

1. func 函数时间复杂度

  • 每个节点被访问 exactly 一次。
  • 时间复杂度:O(n),其中 n 是节点总数。

2. Is_True 函数时间复杂度

  • 主要开销在于调用 func 函数。
  • 时间复杂度:O(n)

3. 总体时间复杂度

O(n)


空间复杂度分析

1. 额外空间使用

  • 只使用了常量级别的局部变量(n, L, max_L)。

2. 递归调用栈空间

  • 递归深度取决于树的高度 h
  • 最好情况(完全二叉树):O(log n)。
  • 最坏情况(退化为链表):O(n)。

3. 总体空间复杂度

O(h),其中 h 是树的高度。 在最坏情况下为 O(n)


较难理解部分的说明

1. 回溯时的深度恢复

C
--(*L); // 回溯时深度减 1

这段代码的作用是恢复当前深度的状态。递归返回到上一层时,需要将深度减 1,以确保下一次递归时深度计算正确。

图解说明:

假设二叉树结构如下:

Text Only
1
2
3
4
5
        A
       / \
      B   C
     /
    D

递归过程中的深度变化:

Text Only
1
2
3
4
5
6
7
8
进入 A: L = 1
进入 B: L = 2
进入 D: L = 3
退出 D: L = 2
退出 B: L = 1
进入 C: L = 2
退出 C: L = 1
退出 A: L = 0

2. 满二叉树性质验证

图解说明:

满二叉树示例:

Text Only
1
2
3
4
5
        A       // 第 1 层
       / \
      B   C     // 第 2 层
     / \ / \
    D  E F  G   // 第 3 层

  • 节点总数:\( n = 7 \)
  • 最大深度:\( max\_L = 3 \)
  • 验证公式: [ 2^{max_L} - 1 = 2^3 - 1 = 7 ] 符合公式,因此是满二叉树。

非满二叉树示例:

Text Only
1
2
3
4
5
        A       // 第 1 层
       / \
      B   C     // 第 2 层
     /     \
    D       E   // 第 3 层

  • 节点总数:\( n = 5 \)
  • 最大深度:\( max\_L = 3 \)
  • 验证公式: [ 2^{max_L} - 1 = 2^3 - 1 = 7 ] 不符合公式,因此不是满二叉树。

延伸知识点

1. 改进版本 - 非递归实现

C
// 使用层次遍历(BFS)计算节点总数和最大深度
bool Is_True_Iterative(BiTree root) {
    if (root == NULL) return true; // 空树是满二叉树

    int n = 0;      // 节点总数
    int max_L = 0;  // 最大深度

    Queue* queue = createQueue(); // 假设有队列实现
    enqueue(queue, root);

    while (!isEmpty(queue)) {
        int level_size = size(queue);
        max_L++; // 每次进入新层,深度加 1

        for (int i = 0; i < level_size; i++) {
            BiTree node = dequeue(queue);
            n++; // 节点总数加 1

            if (node->lchild) enqueue(queue, node->lchild);
            if (node->rchild) enqueue(queue, node->rchild);
        }
    }

    // 判断是否满足满二叉树性质
    if (pow(2, max_L) - 1 == n) {
        return true;
    } else {
        return false;
    }
}

2. 满二叉树与其他特殊二叉树的关系

  • 满二叉树:每层节点数达到最大值,且所有叶子节点在同一层。
  • 完全二叉树:除最后一层外,其他层都是满的,且最后一层的节点从左到右连续排列。
  • 完美二叉树:与满二叉树等价。

3. 满二叉树的应用场景

  • 数据存储:堆结构(如最大堆、最小堆)通常基于满二叉树构建。
  • 哈夫曼编码:哈夫曼树是一种特殊的满二叉树。
  • 分治算法:许多分治算法(如归并排序、快速排序)的递归树可以近似看作满二叉树。
  • 网络拓扑:某些通信协议的路由结构基于满二叉树设计。

4. 性能优化建议

C
// 优化版本 - 提前终止
bool Is_True_Optimized(BiTree p) {
    int n = 0;      // 节点总数
    int max_L = 0;  // 最大深度

    Queue* queue = createQueue();
    enqueue(queue, p);

    while (!isEmpty(queue)) {
        int level_size = size(queue);
        max_L++;

        // 检查当前层是否满足满二叉树性质
        if (pow(2, max_L - 1) != level_size && !isEmpty(queue)) {
            return false; // 当前层节点数不满足满二叉树性质
        }

        for (int i = 0; i < level_size; i++) {
            BiTree node = dequeue(queue);
            n++;

            if (node->lchild) enqueue(queue, node->lchild);
            if (node->rchild) enqueue(queue, node->rchild);
        }
    }

    return true;
}

这种方法体现了数学性质验证的思想,通过简单的公式计算即可高效地判断二叉树是否为满二叉树,是计算机科学中经典问题之一。