第5章 树
1004. Counting Leaves
笔记
- 统计树每层叶子的个数,可用DFS或BFS
- 在DFS加入参数
depth
,可表示当前层号,但还需要全局变量记录树的层数 - 可用邻接表存储树
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, M = 210, ROOT = 1;
int n, m;
int h[N], e[M], ne[M], idx;
int cnt[N], max_depth;
void add(int u, int v) {
e[idx] = v;
ne[idx] = h[u];
h[u] = idx++;
}
void dfs(int u, int depth) {
if (h[u] == -1) {
cnt[depth]++; // 叶节点
max_depth = max(max_depth, depth);
return;
}
for (int p = h[u]; ~p; p = ne[p])
dfs(e[p], depth + 1);
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
int u, son, k;
for (int i = 0; i < m; i++) {
cin >> u >> k;
while(k--) {
cin >> son;
add(u, son);
}
}
dfs(ROOT, 0);
cout << cnt[0];
for (int i = 1; i <= max_depth; i++)
cout << ' ' << cnt[i];
cout << endl;
return 0;
}
1020. Tree Traversals
笔记
- 根据中序遍历和后序遍历(或前序遍历)可唯一确定一棵二叉树
- 后序遍历序列 = m 1 m_1 m1个数构成的左子树后序遍历序列 + m 2 m_2 m2个数构成的左子树后序遍历序列 + 根节点
- 中序遍历序列 = m 1 m_1 m1个数构成的左子树中序遍历序列 + 根节点 + m 2 m_2 m2个数构成的左子树中序遍历序列
- 后序遍历最后一个位置是根节点,查找其在中序遍历的位置,可把中序遍历分成两段,左段是左子树的中序遍历,右段是右子树的中序遍历。根据左子树的中序遍历序列,可确定左子树的结点个数 m m m,从而后序遍历前 m m m个数构成的就是左子树的后序遍历序列,之后的数至倒数第二个就是右子树的后序遍历序列,借助递归则可继续做下去
- 构造完二叉树后可用BFS实现层序遍历
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 35;
int n;
int inorder[N], postorder[N];
unordered_map<int, int> l, r, pos;
int build(int in_l, int in_r, int post_l, int post_r) {
int root = postorder[post_r]; // 当前后序遍历段的根节点下标
int k = pos[root]; // 根节点在中序遍历的位置(哈希表优化查找)
if (in_l < k) {
int m = (k - 1) - in_l + 1; // 左子树结点个数
l[root] = build(in_l, k - 1, post_l, post_l + (k - 1 - in_l));
}
if (k < in_r) {
int m = in_r - (k + 1) + 1; // 右子树结点个数
r[root] = build(k + 1, in_r, post_l + (k - 1 - in_l) + 1, post_r - 1);
}
return root;
}
int q[N], hh, tt;
void bfs(int root) {
q[++tt] = root;
while(hh < tt) {
int u = q[++hh];
if (l.count(u)) q[++tt] = l[u];
if (r.count(u)) q[++tt] = r[u];
}
cout << q[1];
for (int i = 2; i <= n; i++) cout << ' ' << q[i];
cout << endl;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> postorder[i];
for (int i = 0; i < n; i++) {
cin >> inorder[i];
pos[inorder[i]] = i; // 根据结点值查找其中序序列的位置
}
int root = build(0, n - 1, 0, n - 1);
bfs(root);
return 0;
}
1021. Deepest Root
笔记
- 可用并查集查找连通个数,每成功合并一次,则说明是树的一条边,如果给的所有边都能成功合并,则是一棵树,否则不能合并的边数就是连通数
- 可参照“树的中心”那题寻找各个点的最大深度,在本题中,各个边的权重都是1,不存在负数权值,能简化代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e4 + 10, M = 2 * N;
// 邻接表
int n;
int h[N], e[M], ne[M], idx;
void add(int u, int v) {
e[idx] = v;
ne[idx] = h[u];
h[u] = idx++;
}
// 并查集
int parent[N];
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
// 树的中心
int d1[N], d2[N], p1[N], up[N];
int dfs_down(int u, int father) {
for (int p = h[u]; ~p; p = ne[p]) {
int v = e[p];
if (v == father) continue; // 不往上走
int d = 1 + dfs_down(v, u);
if (d > d1[u]) {
d2[u] = d1[u];
d1[u] = d;
p1[u] = v;
} else if (d > d2[u])
d2[u] = d;
}
return d1[u];
}
void dfs_up(int u, int father) {
for (int p = h[u]; ~p; p = ne[p]) {
int v = e[p];
if (v == father) continue; // 不往上走
if (p1[u] == v) up[v] = 1 + max(up[u], d2[u]); // u的最长路径经过v
else up[v] = 1 + max(up[u], d1[u]); // u的最长路径不经过v
dfs_up(v, u);
}
}
int main() {
cin >> n;
int u, v;
for (int i = 1; i <= n; i++) parent[i] = i; // 初始化并查集
memset(h, -1, sizeof h); // 初始化邻接表
int cnt = n;
for (int i = 0; i < n - 1; i++) {
cin >> u >> v;
add(u, v);
add(v, u);
if (find(u) != find(v)) {
parent[find(u)] = find(v); // 合并
cnt--;
}
}
if (cnt > 1) printf("Error: %d components\n", cnt);
else {
// 找树的中心(树形DP)
dfs_down(1, -1);
dfs_up(1, -1);
int max_depth = -1;
for (int i = 1; i <= n; i++)
max_depth = max(max_depth, max(up[i], d1[i]));
for (int i = 1; i <= n; i++)
if (max(up[i], d1[i]) == max_depth)
cout << i << endl;
}
return 0;
}
1043. Is It a Binary Search Tree
笔记
- 把二叉搜索树的前序序列排序即可得到中序序列
- 若能根据前序序列和中序序列构造二叉树,则说明是一个二叉搜索树
- 可在构造二叉搜索树时,计算后序序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, cnt;
int pre[N], in[N], post[N];
bool build(int pre_l, int pre_r, int in_l, int in_r) {
if (in_l > in_r) return true;
int root = pre[pre_l];
int k;
for (k = in_l; k <= in_r; k++)
if (in[k] == root)
break; // 从前往后找第1个root(重复元素的第1个)
if (k > in_r) return false;
bool res = true;
if(!build(pre_l + 1, pre_l + 1 + (k - 1 - in_l), in_l, k - 1)) res = false;
if(!build(pre_l + 1 + (k - 1 - in_l) + 1, pre_r, k + 1, in_r)) res = false;
// res = res && build(pre_l + 1, pre_l + 1 + (k - 1 - in_l), in_l, k - 1);
// res = res && build(pre_l + 1 + (k - 1 - in_l) + 1, pre_r, k + 1, in_r);
post[cnt++] = root;
return res;
}
bool build_r(int pre_l, int pre_r, int in_l, int in_r) {
if (in_l > in_r) return true;
int root = pre[pre_l];
int k;
for (k = in_r; k >= in_l; k--)
if (in[k] == root)
break; // 从后往前找第1个root(重复元素的最后1个)
if (k < in_l) return false;
bool res = true;
if(!build_r(pre_l + 1, pre_l + 1 + (k - 1 - in_l), in_l, k - 1)) res = false;
if(!build_r(pre_l + 1 + (k - 1 - in_l) + 1, pre_r, k + 1, in_r)) res = false;
// res = res && build(pre_l + 1, pre_l + 1 + (k - 1 - in_l), in_l, k - 1);
// res = res && build(pre_l + 1 + (k - 1 - in_l) + 1, pre_r, k + 1, in_r);
post[cnt++] = root;
return res;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> pre[i];
in[i] = pre[i];
}
sort(in, in + n);
if (build(0, n - 1, 0, n - 1)) {
puts("YES");
cout << post[0];
for (int i = 1; i < n; i++)
cout << ' ' << post[i];
cout << endl;
} else {
reverse(in, in + n);
cnt = 0;
if (build_r(0, n - 1, 0, n - 1)) {
puts("YES");
cout << post[0];
for (int i = 1; i < n; i++)
cout << ' ' << post[i];
cout << endl;
} else puts("NO");
}
return 0;
}
1064. Complete Binary Search Tree
笔记
- 采用一维数组存储完全二叉树
- 根据中序遍历次序填入排好序的二叉搜索树元素构成的序列
- 按顺序输出一维数组即可得到层次遍历结果
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, a[N], b[N];
int cnt = 1;
void inorder(int u) {
if (2 * u <= n) inorder(2 * u);
b[u] = a[cnt++];
if (2 * u + 1 <= n) inorder(2 * u + 1);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
inorder(1); // 按中序遍历填入
cout << b[1];
for (int i = 2; i <= n; i++) cout << ' ' << b[i];
return 0;
}
1086. Tree Traversals Again
笔记
-
非递归后序遍历具有如下特点
- 首次一定是
push
,压入的元素一定是root
- 如果上一次操作是
push
,则当前结点是上一个结点的左孩子 - 如果上一次操作是
pop
,则当前结点是上一个结点的右孩子
- 首次一定是
- 记录所有结点的左右孩子后,即可用DFS从
root
开始输出其后序遍历次序
#include <iostream>
#include <stack>
using namespace std;
const int N = 35;
int n, l[N], r[N]; // 左右孩子
string output;
void post_order(int u) {
if (!u) return; // 空节点
post_order(l[u]);
post_order(r[u]);
output += to_string(u) + " ";
}
int main() {
cin >> n;
stack<int> s;
string op;
int root, x;
int last = 0; // 上一个结点
int type = 0; // 0表示push,1表示pop
for (int i = 0; i < 2 * n; i++) {
cin >> op;
if (op == "Push") {
cin >> x;
if (!i) root = x; // 首次压入是根节点
else {
if (type == 0) l[last] = x; // 上次是push,当前结点是上个结点的左孩子
else r[last] = x; // 上次是pop,当前结点是上个结点的左孩子
}
s.push(x); // 模拟栈压入
last = x;
type = 0;
} else {
// pop
last = s.top(); // 模拟栈弹出
s.pop();
type = 1;
}
}
post_order(root);
cout << output.substr(0, output.size() - 1) << endl; // 去掉末尾空格
return 0;
}
1099. Build A Binary Search Tree
笔记
- 先用中序遍历把排好序的数填入,然后再用层序遍历输出
-
~p
等价于p != -1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, ROOT = 0;
int n, l[N], r[N], w[N], a[N];
int cnt;
void inorder(int u) {
if (u == -1) return;
inorder(l[u]);
a[u] = w[cnt++]; // 填数
inorder(r[u]);
}
string res;
int q[N];
void bfs(int root) {
int hh = 0, tt = 0;
q[++tt] = root;
while(hh < tt) {
int x = q[++hh];
if (l[x] != -1) q[++tt] = l[x];
if (r[x] != -1) q[++tt] = r[x];
}
for (int i = 1; i <= n; i++) res += to_string(a[q[i]]) + " ";
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> l[i] >> r[i];
for (int i = 0; i < n; i++) cin >> w[i];
sort(w, w + n);
inorder(ROOT); // 中序遍历填数
bfs(ROOT);
cout << res.substr(0, res.size() - 1) << endl;
return 0;
}
1102. Invert a Binary Tree
笔记
- 两种方法
- 方法1 不翻转
- 实现层序遍历时,先加入右孩子,再加入左孩子
- 实现中序遍历时,先遍历右子树,再遍历左子树
- 方法2 翻转
- 采用后序遍历翻转,即访问时交换左右孩子
- 正常实现层序遍历和中序遍历即可
- 方法1 不翻转
方法1 不翻转
#include <iostream>
#include <cstring>
using namespace std;
const int N = 15;
int n, l[N], r[N];
bool has_father[N];
string res_1;
int q[N];
void bfs(int root) {
int hh = 0, tt = 0;
q[++tt] = root;
while(hh < tt) {
int x = q[++hh];
if (~r[x]) q[++tt] = r[x];
if (~l[x]) q[++tt] = l[x];
}
for (int i = 1; i <= n; i++) res_1 += to_string(q[i]) + " ";
}
string res_2;
void inorder(int u) {
if (u == -1) return;
inorder(r[u]);
res_2 += to_string(u) + " ";
inorder(l[u]);
}
int main() {
cin >> n;
memset(l, -1, sizeof l);
memset(r, -1, sizeof r);
string ls, rs;
for (int i = 0; i < n; i++) {
cin >> ls >> rs;
if (ls != "-") {
l[i] = ls[0] - '0';
has_father[l[i]] = true;
}
if (rs != "-") {
r[i] = rs[0] - '0';
has_father[r[i]] = true;
}
}
int root = -1;
for (int i = 0; i < n; i++)
if (!has_father[i]) {
root = i;
break;
}
bfs(root);
inorder(root);
cout << res_1.substr(0, res_1.size() - 1) << endl;
cout << res_2.substr(0, res_2.size() - 1) << endl;
return 0;
}
方法2 翻转
#include <iostream>
#include <cstring>
using namespace std;
const int N = 15;
int n, l[N], r[N];
bool has_father[N];
void reverse(int u) {
if (u == -1) return;
reverse(l[u]);
reverse(r[u]);
swap(l[u], r[u]);
}
string res_1;
int q[N];
void bfs(int root) {
int hh = 0, tt = 0;
q[++tt] = root;
while(hh < tt) {
int x = q[++hh];
if (~l[x]) q[++tt] = l[x];
if (~r[x]) q[++tt] = r[x];
}
for (int i = 1; i <= n; i++) res_1 += to_string(q[i]) + " ";
}
string res_2;
void inorder(int u) {
if (u == -1) return;
inorder(l[u]);
res_2 += to_string(u) + " ";
inorder(r[u]);
}
int main() {
cin >> n;
memset(l, -1, sizeof l);
memset(r, -1, sizeof r);
string ls, rs;
for (int i = 0; i < n; i++) {
cin >> ls >> rs;
if (ls != "-") {
l[i] = ls[0] - '0';
has_father[l[i]] = true;
}
if (rs != "-") {
r[i] = rs[0] - '0';
has_father[r[i]] = true;
}
}
int root = -1;
for (int i = 0; i < n; i++)
if (!has_father[i]) {
root = i;
break;
}
reverse(root);
bfs(root);
inorder(root);
cout << res_1.substr(0, res_1.size() - 1) << endl;
cout << res_2.substr(0, res_2.size() - 1) << endl;
return 0;
}
1110. Complete Binary Tree
笔记
- 模拟构造完全二叉树,如果所有结点的最大编号刚好为 n n n(从1开始编号),则是一个完全二叉树,否则不是
#include <iostream>
#include <cstring>
using namespace std;
const int N = 25;
int n, l[N], r[N];
bool has_father[N];
int max_u = -1, max_k = -1;
void dfs(int u, int k) {
if (u == -1) return;
if (k > max_k) {
max_u = u;
max_k = k;
}
dfs(l[u], 2 * k);
dfs(r[u], 2 * k + 1);
}
int main () {
cin >> n;
memset(l, -1, sizeof l);
memset(r, -1, sizeof r);
string ls, rs;
for (int i = 0; i < n; i++) {
cin >> ls >> rs;
if (ls != "-") {
l[i] = stoi(ls);
has_father[l[i]] = true;
}
if (rs != "-") {
r[i] = stoi(rs);
has_father[r[i]] = true;
}
}
int root = 0;
while(has_father[root]) root++;
dfs(root, 1); // 从1开始填完全二叉树
if (max_k == n) printf("YES %d\n", max_u);
else printf("NO %d\n", root);
return 0;
}
1115. Counting Nodes in a BST
笔记
- 构造二叉搜索树,注意从1开始编号,
insert(int& u, int x)
可自动更新左右子树指针 - 用DFS遍历所有结点,计算各层结点数,同时还要保存最大层数
#include <iostream>
using namespace std;
const int N = 1010;
int n, l[N], r[N], v[N], idx;
int cnt[N], max_depth;
void insert(int &u, int x) {
// 从结点u开始插入x
if (!u) {
// 首次插入
u = ++idx; // 计算可用结点位置(数组下标表示),并保存(引用可修改变量)
v[u] = x; // 保存该结点
} else if (x <= v[u]) insert(l[u], x); // 若插入成功,则会修改l[u]的值,指向新结点地址
else insert(r[u], x); // 若插入成功,则会修改r[u]的值,指向新结点地址
}
void dfs(int u, int depth) {
if (!u) return;
cnt[depth]++;
max_depth = max(max_depth, depth);
dfs(l[u], depth + 1);
dfs(r[u], depth + 1);
}
int main() {
cin >> n;
int x;
int root = 0; // 0表示首次插入
for (int i = 0; i < n; i++) {
cin >> x;
insert(root, x); // 自动更新当前root位置
}
dfs(root, 0);
int n1 = cnt[max_depth], n2 = cnt[max_depth - 1];
printf("%d + %d = %d\n", n1, n2, n1 + n2);
return 0;
}
1119. Pre- and Post-order Traversals
笔记
- 用DFS搜索,如果找到一种情况,则继续搜索,如果找到一种以上的情况,则可停止
- 可根据前序遍历序列划分左右子树,即根节点 + 左子树区间 + 右子树区间,可在左右边界穷举所有情况
#include <iostream>
using namespace std;
const int N = 40;
int pre[N], post[N], in[N];
// 返回合法方案数
int dfs(int pre_l, int pre_r, int post_l, int post_r, string& res) {
if (pre_l > pre_r) return 1; // 合法情况
if (pre[pre_l] != post[post_r]) return 0; // 非法情况
int cnt = 0;
for (int k = pre_l; k <= pre_r; k++) {
string l_res, r_res;
int l_cnt = dfs(pre_l + 1, k, post_l, post_l + k - pre_l - 1, l_res);
int r_cnt = dfs(k + 1, pre_r, post_l + k - pre_l - 1 + 1, post_r - 1, r_res);
if (l_cnt && r_cnt) {
cnt += l_cnt * r_cnt;
res = l_res + to_string(pre[pre_l]) + " " + r_res;
if (cnt > 1) break;
}
}
return cnt;
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> pre[i];
for (int i = 0; i < n; i++) cin >> post[i];
string res;
int cnt = dfs(0, n - 1, 0, n - 1, res);
if (cnt > 1) puts("No");
else puts("Yes");
cout << res.substr(0, res.size() - 1) << endl;
return 0;
}
1127. ZigZagging on a Tree
笔记
- 重建二叉树,再层序遍历
- 可在
BFS
中确定二叉树每一层结点的范围,内嵌一层while
即可做到
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 35;
unordered_map<int, int> l, r, pos; // 左右孩子
int n, in[N], post[N];
int build(int in_l, int in_r, int post_l, int post_r) {
int root = post[post_r];
int k = pos[root]; // 查找root在中序遍历的下标
if (in_l < k) l[root] = build(in_l, k - 1, post_l, post_l + k - 1 - in_l);
if (k < in_r) r[root] = build(k + 1, in_r, post_l + k - 1 - in_l + 1, post_r - 1);
return root;
}
string res;
int q[N];
void bfs(int root) {
int hh = 0, tt = 0;
q[0] = root;
bool flag = true;
while(hh <= tt) {
int head = hh, tail = tt;
while(hh <= tail) {
int u = q[hh++];
if (l.count(u)) q[++tt] = l[u];
if (r.count(u)) q[++tt] = r[u];
}
if (flag) reverse(q + head, q + tail + 1);
flag = !flag;
}
for (int i = 0; i < n; i++)
res += to_string(q[i]) + ' ';
res.pop_back();
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> in[i];
pos[in[i]] = i; // 哈希表,加快查找下标
}
for (int i = 0; i < n; i++) cin >> post[i];
int root = build(0, n - 1, 0, n - 1);
bfs(root);
cout << res << endl;
return 0;
}
1138. Postorder Traversal
笔记
- DFS后序构造二叉树,但不保存左右孩子位置
- 由于只需记录后序遍历首次遍历的结点,故只需判段用来保存的变量是否变更即可
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 5e4 + 10;
unordered_map<int, int> pos;
int n, in[N], pre[N];
int post; // 后序遍历第1个结点
void build(int in_l, int in_r, int pre_l, int pre_r) {
int root = pre[pre_l];
int k = pos[root];
if (in_l < k) build(in_l, k - 1, pre_l + 1, pre_l + 1 + k - 1 - in_l);
if (k < in_r) build(k + 1, in_r, pre_l + 1 + k - 1 - in_l + 1, pre_r);
if (!post) post = root; // 后序遍历第1个结点
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 0; i < n; i++) cin >> pre[i];
for (int i = 0; i < n; i++) {
cin >> in[i];
pos[in[i]] = i;
}
build(0, n - 1, 0, n - 1);
cout << post << endl;
return 0;
}
1066. Root of AVL Tree
笔记
- 插入一个新结点可分为三步:① 插入结点; ② 调整位置; ③ 更新高度
- 插入结点的过程与查找二叉树一样
- 调整位置共有4类情况(LL、LR、RR、RL),可通过计算左右孩子的高度差判断。每种情况可通过两个基本步骤左旋和右旋的组合来解决。在把孩子变成根节点前,需要把孩子多余的部分移到原来的根节点上
- 更新高度需要从下往上更新
- 性质:左旋和右旋只会改变平衡二叉树的位置和高度,但中序遍历次序不变
- 代码实现需要修改根节点指向,因此需要用引用型参数
&
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30;
int l[N], r[N], w[N], idx; // 左右儿子法实现平衡二叉树
int h[N]; // 树高
int get_balance(int u) {
return h[l[u]] - h[r[u]]; // 平衡度
}
void update(int u) {
h[u] = max(h[l[u]], h[r[u]]) + 1; // 更新结点高度
}
void R(int& u) {
int p = l[u];
l[u] = r[p]; // 不然左孩子的右孩子不为空,放不了根节点
r[p] = u;
update(u); // 从下往上更新
update(p);
u = p; // 更新根节点
}
void L(int& u) {
int p = r[u];
r[u] = l[p]; // 不然左孩子的右孩子不为空,放不了根节点
l[p] = u;
update(u); // 从下往上更新
update(p);
u = p; // 更新根节点
}
void insert(int &u, int v) {
if (!u) {
u = ++idx;
w[u] = v;
} else if (v < w[u]) {
// 先插入,再调整,最后更新
insert(l[u], v);
if (get_balance(u) == 2) {
if (get_balance(l[u]) == 1) R(u);
else {
L(l[u]);
R(u);
}
}
} else {
insert(r[u], v);
if (get_balance(u) == -2) {
if (get_balance(r[u]) == -1) L(u);
else {
R(r[u]);
L(u);
}
}
}
update(u);
}
int main() {
int n, v;
cin >> n;
int root = 0; // 从第0个位置开始插入
for (int i = 0; i < n; i++) {
cin >> v;
insert(root, v); // 插入到平衡二叉树中
}
cout << w[root] << endl;
return 0;
}
1123. Is It a Complete AVL Tree.
笔记
- 首先构造平衡二叉树,然后采用BFS边输出层序遍历,边判断是否是完全二叉树,如果插入结点的位置超过 n n n,则认为不是完全二叉树
#include <iostream>
using namespace std;
const int N = 25;
int n, l[N], r[N], w[N], h[N], idx;
int get_balance(int u) {
return h[l[u]] - h[r[u]];
}
void update(int u) {
h[u] = max(h[l[u]], h[r[u]]) + 1;
}
void R(int& u) {
int p = l[u];
l[u] = r[p];
r[p] = u;
update(u);
update(p);
u = p;
}
void L(int& u) {
int p = r[u];
r[u] = l[p];
l[p] = u;
update(u);
update(p);
u = p;
}
void insert(int& u, int x) {
if (!u) {
u = ++idx;
w[u] = x;
} else if (x < w[u]) {
insert(l[u], x);
if (get_balance(u) == 2) {
if (get_balance(l[u]) == 1) R(u);
else {
L(l[u]);
R(u);
}
}
} else {
insert(r[u], x);
if (get_balance(u) == -2) {
if (get_balance(r[u]) == -1) L(u);
else {
R(r[u]);
L(u);
}
}
}
update(u);
}
int q[N], pos[N];
void bfs(int root) {
int hh = 0, tt = -1;
q[++tt] = root;
pos[root] = 1; // 模拟完全二叉树
bool flag = true; // 是否是二叉树
while(hh <= tt) {
int u = q[hh++];
if (pos[u] > n) flag = false; // 超出完全二叉树的保存范围
if (l[u]) {
q[++tt] = l[u];
pos[l[u]] = 2 * pos[u];
}
if (r[u]) {
q[++tt] = r[u];
pos[r[u]] = 2 * pos[u] + 1;
}
}
string res;
for (int i = 0; i < n; i++) res += to_string(w[q[i]]) + ' ';
res.pop_back();
cout << res << endl;
if (flag) puts("YES");
else puts("NO");
}
int main() {
cin >> n;
int x, root = 0;
for (int i = 0; i < n; i++) {
cin >> x;
insert(root, x);
}
bfs(root);
}
1135. Is It A Red-Black Tree
笔记
- 实际上只需要检查三个条件
- 根节点为黑(在建树后检查)
- 红结点的孩子都为黑结点(建树中检查)
- 根节点到叶节点的路径具有相同的黑结点个数(建树中检查)
- 建树依据
- 红黑树是一个平衡二叉树,其中序遍历序列就是所有结点的升序排序序列,
- 把前序遍历序列排序后就能得到中序序列,从而有可能根据前序和中序序列构造唯一的二叉树
- 注意
- 红色是负权值,在排序获取中序遍历序列时需要去掉符号再排序
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 35;
int n, pre[N], in[N];
unordered_map<int, int> pos;
bool flag;
int build(int pre_l, int pre_r, int in_l, int in_r, int& sum) {
int root = pre[pre_l];
int k = pos[abs(root)];
if (k < in_l || k > in_r) {
flag = false;
return 0;
}
int left = 0, right = 0, ls = 0, rs = 0;
if (k > in_l) left = build(pre_l + 1, pre_l + 1 + k - 1 - in_l, in_l, k - 1, ls);
if (k < in_r) right = build(pre_l + 1 + k - 1 - in_l + 1, pre_r, k + 1, in_r, rs);
if (ls != rs) flag = false; // 黑结点个数不等
sum = ls; // 更新sum的值
if (root < 0) {
if (left < 0 || right < 0) flag = false; // 红色根节点存在黑结点孩子
} else sum++; // 根节点是黑结点,到叶节点的黑结点个数+1
return root;
}
int main() {
int t;
cin >> t;
while(t--) {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> pre[i];
in[i] = abs(pre[i]);
}
sort(in, in + n);
// for (int i = 0; i < n; i++) cout << pre[i] << endl;
for (int i = 0; i < n; i++) pos[in[i]] = i;
int sum = 0;
flag = true;
int root = build(0, n - 1, 0, n - 1, sum);
if (root < 0) flag = false;
if (flag) puts("Yes");
else puts("No");
pos.clear();
}
return 0;
}
1053. Path of Equal Weight
笔记
- 可采用邻接矩阵存储多叉树
- 可用额外的叶节点数组标记结点是否是叶节点,提高算法效率
- 采用DFS即可找到所有到叶节点的路径
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 110, ROOT = 0;
int n, m, target; // 结点个数,非叶子结点个数,目标权值
int w[N]; // 权重
bool g[N][N]; // 邻接矩阵存储多叉树
bool is_leaf[N]; // 是否是叶节点
vector<vector<int>> res;
void dfs(int u, int s, vector<int>& path) {
if (is_leaf[u] && s == target) res.push_back(path);
else {
for (int v = 0; v < n; v++)
if (g[u][v]) {
path.push_back(w[v]);
dfs(v, s + w[v], path);
path.pop_back();
}
}
}
int main() {
cin >> n >> m >> target;
for (int i = 0; i < n; i++) cin >> w[i];
while(m--) {
int u, k, v;
cin >> u >> k;
is_leaf[u] = true; // 先标记非叶节点,等会儿再处理
while(k--) {
cin >> v;
g[u][v] = true; // 构造邻接矩阵
}
}
for (int i = 0; i < n; i++) is_leaf[i] = !is_leaf[i]; // 变成正确含义
vector<int> path{w[ROOT]};
dfs(ROOT, w[ROOT], path);
sort(res.begin(), res.end(), greater<vector<int>>());
for (auto elem : res) {
string line;
for (int item : elem) line += to_string(item) + ' ';
line.pop_back();
cout << line << endl;
}
return 0;
}
1094. The Largest Generation
笔记
- 用BFS找到结点数最多的层
- BFS不仅能用
queue
实现,还可以用vector
实现
#include <iostream>
#include <vector>
using namespace std;
const int N = 110, ROOT = 1;
int n, m; // 结点个数,非叶子结点个数
bool g[N][N]; // 邻接矩阵
vector<int> level[N];
void bfs(int root) {
int l = 1;
level[1].push_back(root);
while(level[l].size()) {
for (auto u : level[l])
for (int v = 1; v <= n; v++)
if (g[u][v]) level[l + 1].push_back(v);
l++;
}
int max_l = 0, max_nodes = 0;
for (int i = 1; i < l; i++)
if (max_nodes < level[i].size()) {
max_l = i;
max_nodes = level[i].size();
}
cout << max_nodes << ' ' << max_l << endl;
}
int main() {
cin >> n >> m;
while(m--) {
int u, k, v;
cin >> u >> k;
while(k--) {
cin >> v;
g[u][v] = true;
}
}
bfs(ROOT);
return 0;
}