层序 = 一层一层看;完全二叉树 = 能按"座位号"整齐塞进数组
消防队检查一栋楼,不会"顺着楼梯一口气冲顶",而是一层查完再查下一层,每层从东到西挨个房间。把这套"一层一层"的规则用在树上,就是层序遍历。
而"完全二叉树"像一座座位号排得满满当当的阶梯影院:第 1 排 1 个座、第 2 排 2 个、第 3 排 4 个……座位号从左到右连号发。既然座位号这么规整,直接用数组下当代号就能管理整棵树——不用一根指针一根指针地连!
规则很简单:先看根,再看它的孩子(从左到右),再看孩子的孩子……一层扫完再扫下一层。对示例树:1 → 2、3 → 4、5、6、7,输出 1 2 3 4 5 6 7。
问题:怎么保证"一层扫完才轮到下一层"?答案是排队(队列):根先排队 → 访问根时,把它的孩子按顺序排在队尾 → 队头的人被访问时,再把它的孩子排到队尾。先来先服务,天然就是一层一层的!
完全二叉树按层序编号(1 号是根,2、3 号是它的左右孩子,4、5、6、7 号是下一层……),下标之间就有固定数学关系:
· 左孩子下标 = 2i · 右孩子下标 = 2i+1 · 父亲下标 = i/2(整除)
验证一下:3 号是 1 号的右孩子(2×1+1=3 ✓);5 号的父亲 = 5/2 = 2 ✓;4 号的左孩子 = 8(若存在)。
来一道经典题。数组(下标从 1 开始)是:8 3 10 1 6 14 4 7 2 13(共 10 个数)。
给数组画树,按公式逐个"落座":下标 1 是根(8);2、3 是 1 的左右孩子(3、10);4、5 是 2 的左右孩子(1、6);6、7 是 3 的左右孩子(14、4);8、9 是 4 的左右孩子(7、2);10 是 5 的左孩子(13),11 不存在所以没有右孩子。看,最后一层的 7、2、13 也是从左到右坐的——这是一棵完全二叉树。
给树写数组:就是做一遍层序遍历——一层一层、从左到右编号,把每个节点的值按顺序写进数组的下一个空位。对上图这棵树,层序序列是 8 3 10 1 6 14 4 7 2 13,和数组内容一模一样!
两段代码:第一段用队列做指针树的层序遍历;第二段把完全二叉树存进数组,用三公式秒查父亲和孩子。
// 代码 1:指针树的层序遍历(队列版) #include <bits/stdc++.h> using namespace std; struct Node { int val; Node *l, *r; Node(int v) : val(v), l(nullptr), r(nullptr) {} }; void bfs(Node *root) { if (!root) return; queue<Node*> q; q.push(root); // 根先排队 while (!q.empty()) { Node *p = q.front(); // 队头出列 q.pop(); cout << p->val << " "; // 访问(打印) if (p->l) q.push(p->l); // 孩子按左→右排到队尾 if (p->r) q.push(p->r); } } int main() { // 1 // / \ // 2 3 // / \ // 4 5 Node *root = new Node(1); root->l = new Node(2); root->r = new Node(3); root->l->l = new Node(4); root->l->r = new Node(5); bfs(root); // 输出:1 2 3 4 5 return 0; }
// 代码 2:完全二叉树的数组存储(下标从 1 开始) #include <bits/stdc++.h> using namespace std; const int MAXN = 1005; int a[MAXN]; int n; int main() { int vals[] = {0, 8, 3, 10, 1, 6, 14, 4, 7, 2, 13}; n = 10; for (int i = 1; i <= n; i++) a[i] = vals[i]; // 完全二叉树的层序遍历 = 按序打印数组 for (int i = 1; i <= n; i++) cout << a[i] << " "; cout << endl; // 输出:8 3 10 1 6 14 4 7 2 13 int i = 6; // 查询下标 6 的节点 if (i > 1) cout << "父亲 = " << a[i/2] << endl; // 输出:父亲 = 10 if (2*i <= n) cout << "左孩子 = " << a[2*i] << endl; // 输出:左孩子 = 7 if (2*i+1 <= n) cout << "右孩子 = " << a[2*i+1] << endl; // 输出:右孩子 = 2 return 0; }
a[1]。查父亲前记得判断 i > 1(根没有父亲);查孩子前判断 2*i <= n(下标超出 n 就是没有这个孩子)。