跳转至

EC15-使用层次遍历计算二叉树的宽度

代码实现

方法一:层次遍历思想(广度优先搜索)

C
// 函数 level:使用层次遍历计算二叉树的宽度
// 二叉树的宽度定义为各层节点数的最大值
int level(BiTree bt) {
    BiTree que[maxsize];           // 使用数组实现队列
    int front = 0, rear = 0;       // 队列的头指针和尾指针
    int res = 0;                   // 当前层的节点计数器
    int last = 1;                  // 当前层的最后一个节点在队列中的位置
    int width = 0;                 // 记录最大宽度

    // 如果二叉树不为空
    if (bt != NULL) {
        que[++rear] = bt;          // 将根节点入队

        // 当队列不为空时继续遍历
        while (front != rear) {
            bt = que[++front];     // 出队一个节点
            res++;                 // 当前层节点数加1

            // 将左子节点入队
            if (bt->lchild != NULL) {
                que[++rear] = bt->lchild;
            }

            // 将右子节点入队
            if (bt->rchild != NULL) {
                que[++rear] = bt->rchild;
            }

            // 如果当前出队的是当前层的最后一个节点
            if (front == last) {
                // 更新最大宽度
                if (res > width) {
                    width = res;
                }
                res = 0;           // 重置当前层节点计数器
                last = rear;       // 更新下一层的最后一个节点位置
            }
        }
    }

    return width;                  // 返回二叉树的最大宽度
}

方法二:递归遍历思想(深度优先搜索)

C
// 函数 func:递归遍历二叉树,统计每层的节点数
// 参数:bt - 当前节点,A[] - 存储每层节点数的数组,L - 当前层数指针
void func(BiTree bt, int A[], int *L) {
    // 如果当前节点不为空
    if (bt != NULL) {
        ++(*L);                    // 进入下一层,层数加1
        A[(*L)]++;                 // 当前层的节点数加1

        // 递归处理左子树
        func(bt->lchild, A, L);

        // 递归处理右子树
        func(bt->rchild, A, L);

        --(*L);                    // 回退到上一层,层数减1
    }
}

// 函数 max_width:计算二叉树的最大宽度
int max_width(BiTree bt) {
    int A[maxsize] = {0};         // 初始化数组,存储每层的节点数
    int L = 0;                    // 初始化当前层数为0

    // 递归遍历二叉树,统计每层节点数
    func(bt, A, &L);

    int width = 0;                // 初始化最大宽度为0

    // 遍历数组,找出最大值
    for (int i = 1; i < maxsize; i++) {
        if (A[i] > width) {
            width = A[i];          // 更新最大宽度
        }
    }

    return width;                 // 返回二叉树的最大宽度
}

代码逻辑解析

方法一:层次遍历(BFS)

1. 核心思想

  • 按层遍历二叉树
  • 统计每一层的节点数
  • 记录所有层中节点数的最大值

2. 关键变量

  • que[]:循环队列,存储待访问的节点
  • front, rear:队列的头尾指针
  • res:当前层的节点计数器
  • last:标记当前层的最后一个节点在队列中的位置
  • width:记录最大宽度

3. 层次控制机制

C
1
2
3
4
5
6
7
// 当处理完当前层的最后一个节点时
if (front == last) {
    // 更新最大宽度
    if (res > width) width = res;
    res = 0;        // 重置计数器
    last = rear;    // 设置下一层的结束位置
}

方法二:递归遍历(DFS)

1. 核心思想

  • 通过深度优先遍历记录每个节点所在的层数
  • 使用数组统计每层的节点数
  • 找出节点数最多的层

2. 递归机制

C
1
2
3
4
++(*L);              // 进入子节点,层数加1
A[(*L)]++;           // 当前层节点数加1
// 递归处理子树
--(*L);              // 回退,屌数减1

时间复杂度分析

方法一:层次遍历

  • 节点访问:每个节点被访问 exactly 一次
  • 队列操作:入队和出队操作都是 O(1)
  • 时间复杂度O(n),其中 n 是节点总数

方法二:递归遍历

  • 递归调用:每个节点被访问 exactly 一次
  • 数组操作:每次访问进行常数时间的数组操作
  • 查找最大值:需要遍历数组,但数组大小与节点数相关
  • 时间复杂度O(n)

空间复杂度分析

方法一:层次遍历

  • 队列空间:最坏情况下队列需要存储一层的所有节点
  • 完全二叉树:O(n/2) ≈ O(n)
  • 链式结构:O(1)
  • 空间复杂度O(w),其中 w 是树的最大宽度

方法二:递归遍历

  • 数组空间:O(h),其中 h 是树的高度
  • 递归栈:O(h)
  • 空间复杂度O(h)

较难理解部分的说明

1. 层次遍历中的 last 变量

C
// 图示说明
//       A(1)
//      / \
//     B(2) C(3)
//    / \   \
//   D(4)E(5)F(6)

队列状态变化
初始[A]           last=1, front=0, rear=1
第1层出队A入队B,C  [B,C]       last=3, res=1
第1层结束front=2==last=3  width=1, res=0, last=3
第2层出队B入队D,E  [C,D,E]     last=5, res=1
第2层出队C入队F  [D,E,F]       last=6, res=2
第2层结束front=5==last=6  width=2, res=0, last=6

2. 递归遍历的层数管理

C
// 递归深度变化示例
//       A(1)
//      / \
//     B(2) C(2)
//    /
//   D(3)

调用栈变化
func(A, L=0)  L=1, A[1]++
├─ func(B, L=1)  L=2, A[2]++
  ├─ func(D, L=2)  L=3, A[3]++
    └─ 回退L=2
  └─ 回退L=1
└─ func(C, L=1)  L=2, A[2]++
   └─ 回退L=1

图解说明

假设二叉树结构如下:

Text Only
1
2
3
4
5
6
7
        A(1)          // 第1层:1个节点
       / \
      B(2) C(3)       // 第2层:2个节点
     / \   \
    D(4)E(5)F(6)     // 第3层:3个节点
   /
  G(7)               // 第4层:1个节点

方法一执行过程

Text Only
1
2
3
4
5
第1层:A              → 节点数 = 1
第2层:B, C           → 节点数 = 2  
第3层:D, E, F        → 节点数 = 3
第4层:G              → 节点数 = 1
最大宽度 = 3

方法二执行结果

Text Only
1
2
3
4
5
A[1] = 1  // 第1层1个节点
A[2] = 2  // 第2层2个节点
A[3] = 3  // 第3层3个节点
A[4] = 1  // 第4层1个节点
最大宽度 = 3


延伸知识点

1. 改进版本 - 返回宽度和层数

C
typedef struct {
    int maxWidth;
    int maxLevel;
} WidthInfo;

WidthInfo getWidthWithLevel(BiTree root) {
    if (root == NULL) {
        WidthInfo info = {0, 0};
        return info;
    }

    int levelCount[maxsize] = {0};
    int maxLevel = 0;

    // 使用队列进行层次遍历
    typedef struct { BiTree node; int level; } QueueItem;
    QueueItem queue[maxsize];
    int front = 0, rear = 0;

    queue[rear].node = root;
    queue[rear].level = 1;
    rear++;

    while (front < rear) {
        QueueItem item = queue[front++];
        int currentLevel = item.level;

        levelCount[currentLevel]++;
        if (currentLevel > maxLevel) maxLevel = currentLevel;

        if (item.node->lchild) {
            queue[rear].node = item.node->lchild;
            queue[rear].level = currentLevel + 1;
            rear++;
        }

        if (item.node->rchild) {
            queue[rear].node = item.node->rchild;
            queue[rear].level = currentLevel + 1;
            rear++;
        }
    }

    int maxWidth = 0;
    int maxWidthLevel = 1;
    for (int i = 1; i <= maxLevel; i++) {
        if (levelCount[i] > maxWidth) {
            maxWidth = levelCount[i];
            maxWidthLevel = i;
        }
    }

    WidthInfo info = {maxWidth, maxWidthLevel};
    return info;
}

2. 使用 STL 队列的现代版本

C++
#include <queue>
#include <algorithm>

int getWidthSTL(BiTree root) {
    if (!root) return 0;

    std::queue<BiTree> q;
    q.push(root);
    int maxWidth = 0;

    while (!q.empty()) {
        int levelSize = q.size();  // 当前层的节点数
        maxWidth = std::max(maxWidth, levelSize);

        // 处理当前层的所有节点
        for (int i = 0; i < levelSize; i++) {
            BiTree node = q.front();
            q.pop();

            if (node->lchild) q.push(node->lchild);
            if (node->rchild) q.push(node->rchild);
        }
    }

    return maxWidth;
}

3. 计算宽度的其他定义

C
// 最大宽度(考虑节点间空隙)
int getMaxWidthWithGaps(BiTree root) {
    if (!root) return 0;

    std::queue<std::pair<BiTree, int>> q;  // 节点及其位置编号
    q.push({root, 1});
    int maxWidth = 0;

    while (!q.empty()) {
        int levelSize = q.size();
        int leftPos = q.front().second;
        int rightPos = leftPos;

        for (int i = 0; i < levelSize; i++) {
            auto [node, pos] = q.front();
            q.pop();
            rightPos = pos;

            if (node->lchild) q.push({node->lchild, 2 * pos});
            if (node->rchild) q.push({node->rchild, 2 * pos + 1});
        }

        maxWidth = std::max(maxWidth, rightPos - leftPos + 1);
    }

    return maxWidth;
}

4. 实际应用场景

  • 界面布局:计算树形结构 UI 组件的最大宽度
  • 打印格式化:确定树形数据的打印宽度
  • 内存分配:预估树形结构的存储空间需求
  • 性能分析:分析树形算法的空间复杂度
  • 图形渲染:确定树状图的最大横向跨度

5. 算法比较总结

方法 时间复杂度 空间复杂度 优点 缺点
层次遍历 O(n) O(w) 直观易懂,可边遍历边计算 需要队列空间
递归遍历 O(n) O(h) 代码简洁 需要额外数组,递归栈开销

两种方法各有优势,选择哪种取决于具体的应用场景和性能要求。