PAT グレード A 質問レコード-(AcWing)-(Day06 ツリー 8 質問)
このコースは AcWing から提供されており、
AcWing のタイトルは中国語のタイトルに翻訳されています。
今日の質問リスト
- 1110 完全なバイナリ ツリー
- 1115 二分探索ツリー内のノードの数を数える
- 1119 注文前および注文後のトラバーサル
- 1127 木の上をジグザグに歩く
- 1138 郵便為替トラバーサル
- 1066 AVL ツリーのルート
- 1123 それは完全な AVL ツリーですか
- 1135 それは赤黒の木ですか
1110 完全なバイナリ ツリー
注意点
- 最初はノードを読み込むときに、タイトルにあるノード番号より大きいノード番号があるとは思わなかったので、 と を
char
使用して保存していましたが、次のように変更しました。a-'0'
10
string
stoi()
string
型の文字列を次のように変換すると非常に便利ですint
#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 30;
using namespace std;
int l[N], r[N];
int n, last_idx, last_node, root = 0;
bool is_father[N];
void dfs(int u, int k) {
// u代表当前节点,k代表完全二叉树中的节点
if (u == -1) return;
if (k > last_idx) {
last_idx = k;
last_node = u;
}
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));
for (int i = 0; i < n; ++i) {
string a, b;
cin >> a >> b;
if (a != "-") l[i] = stoi(a), is_father[l[i]] = true;
if (b != "-") r[i] = stoi(b), is_father[r[i]] = true;
}
while (is_father[root]) root++;
dfs(root, 1);
if (last_idx == n) cout << "YES " << last_node;
else cout << "NO " << root;
return 0;
}
1115 二分探索ツリー内のノードの数を数える
注意点
- これは挿入時に使用され、
&u
最初に渡されたroot
値を更新したり、後で再帰でl[u],r[u]
値を変更したりできます。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int l[N], r[N], val[N], idx = 0;
int cnt[N];
int max_depth;
void insert(int &u, int w) {
if (u == 0) {
u = ++idx;
cout << "u = " << u << endl;
val[u] = w;
} else if (w <= val[u]) insert(l[u], w);
else insert(r[u], w);
}
void dfs(int root, int depth) {
if (root == 0) return;
max_depth = max(depth, max_depth);
cnt[depth]++;
dfs(l[root], depth + 1);
dfs(r[root], depth + 1);
}
int main() {
int n;
cin >> n;
int root = 0;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
insert(root, x);
cout << root << endl;
cout << "*****************" << endl;
}
dfs(root, 0);
int n1 = cnt[max_depth];
int n2 = cnt[max_depth - 1];
printf("%d + %d = %d\n", n1, n2, n1 + n2);
return 0;
}
!!! 1119 注文前および注文後のトラバーサル
英単語
解析の
難しさは、再帰的かつ暴力的な事前順序走査と、左右の分割点を見つけるという一連の手順にあります。
#include <cstring>
#include <iostream>
const int N = 40;
using namespace std;
int pre[N], post[N];
int dfs(int prel, int prer, int postl, int postr, string &s) {
if (prel > prer) return 1;
if (pre[prel] != post[postr]) return 0;
int cnt = 0;
for (int i = prel; i <= prer; ++i) {
string lin, rin;
int lcnt = dfs(prel + 1, i, postl, postl + i - prel - 1, lin);
int rcnt = dfs(i + 1, prer, postl + i - prel, postr - 1, rin);
if (lcnt && rcnt) {
s = lin + to_string(pre[prel]) + " " + rin;
cnt += lcnt * rcnt;
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("Yes");
else puts("No");
res.pop_back();
puts(res.c_str());
return 0;
}
1127 木の上をジグザグに歩く
英単語
- ナイーブ
- 交互 対話型の代替
分析は
、質問 1020 と似ていますが、レイヤー シーケンスをトラバースするときに、現在のトラバース シーケンスを記録するためにレイヤーの数に応じてこの質問を逆にする必要がある点が異なります。キーはハッシュ テーブル内のキーの数であることに注意
してください
l.count(t)
。、存在する場合はキューに追加しますl
t
l[t]
#include <cstring>
#include <iostream>
#include <unordered_map>
#include <algorithm>
const int N = 40;
using namespace std;
unordered_map<int, int> l, r, pos;
int inorder[N], post[N];
int q[N];
int n;
int build(int il, int ir, int pl, int pr) {
int root = post[pr];
int k = pos[root];
if (il < k) l[root] = build(il, k - 1, pl, pl + k - 1 - il);
if (k < ir) r[root] = build(k + 1, ir, pl + k - il, pr - 1);
return root;
}
void bfs(int root) {
q[0] = root;
int head = 0, tail = 0;
int step = 0;
while (head <= tail) {
int cur_head = head, cur_tail = tail;
while (head <= cur_tail) {
int t = q[head++];
if (l.count(t)) q[++tail] = l[t];
if (r.count(t)) q[++tail] = r[t];
}
if (++step % 2 == 1) reverse(q + cur_head, q + cur_tail + 1);
}
cout << q[0];
for (int i = 1; i < n; ++i) {
cout << " " << q[i];
}
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i) cin >> inorder[i], pos[inorder[i]] = i;
for (int i = 0; i < n; ++i) cin >> post[i];
int root = build(0, n - 1, 0, n - 1);
bfs(root);
return 0;
}
1138 郵便為替トラバーサル
注意点
ハッシュテーブル書き込み方式を使用しなくてもテストポイントは通過できますが、テストポイント4は341msかかりました
#include <iostream>
const int N = 50010;
using namespace std;
int pre[N], in[N];
int l[N], r[N];
int n;
int post = 0, flag = 1;
void build(int il, int ir, int pl, int pr) {
if (flag == 0) return;
int root = pre[pl];
int k;
for (int i = il; i <= ir; ++i) {
if (in[i] == root) {
k = i;
break;
}
}
if (il < k) build(il, k - 1, pl + 1, pl + 1 + k - 1 - il);
if (k < ir) build(k + 1, ir, pl + 1 + k - 1 - il + 1, pr);
if (post == 0) {
post = root;
cout << post;
flag = 0;
return;
}
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i)cin >> pre[i];
for (int i = 0; i < n; ++i)cin >> in[i];
build(0, n - 1, 0, n - 1);
return 0;
}
ハッシュ テーブルの書き込み方法を使用すると、テスト ポイント 4 にかかる時間はわずか 34 ミリ秒でした
#include <iostream>
#include <unordered_map>
const int N = 50010;
using namespace std;
int pre[N], in[N];
unordered_map<int, int> l, r, pos;
int n;
int post = 0, flag = 1;
void build(int il, int ir, int pl, int pr) {
if (flag == 0) return;
int root = pre[pl];
int k = pos[root];
if (il < k) build(il, k - 1, pl + 1, pl + 1 + k - 1 - il);
if (k < ir) build(k + 1, ir, pl + 1 + k - 1 - il + 1, pr);
if (post == 0) {
post = root;
cout << post;
flag = 0;
return;
}
}
int main() {
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);
return 0;
}
1066 AVL ツリーのルート
英単語
- 違いによって異なります
分析
下の図では、二分木の右手系のプロセスを描画していますが、左手系のプロセスを逆にしているだけです。
注意点
height_differ(r[u]) == -1
右の部分木を左巻きにするかどうかを計算するときに、和を一つ間違えてr
、l
長い間探しましたbug
- テストの主なポイントは、回転するかどうかです。上の図を理解してください。
#include <iostream>
const int N = 30;
using namespace std;
int l[N], r[N], v[N], h[N], idx;
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;
}
int height_differ(int u) {
// 计算左右子树的差
return h[l[u]] - h[r[u]];
}
void insert(int &u, int w) {
if (!u) {
u = ++idx;
v[u] = w;
} else if (w < v[u]) {
// 插入左子树
insert(l[u], w);
// 检查是否要旋
if (height_differ(u) == 2) {
// 插入左子树之后可能造成左子树高度多一些
if (height_differ(l[u]) == 1) {
// 左子树的左子树高于右子树, 直接右旋即可
R(u);
} else {
// 左子树的右子树高于左子树的左子树,先左旋再右旋
L(l[u]);
R(u);
}
}
} else {
// 插入右子树
insert(r[u], w);
// 检查是否要旋
if (height_differ(u) == -2) {
// 插入右子树之后可能造成右子树高度多一些
if (height_differ(r[u]) == -1) {
// 右子树的右子树高于左子树, 直接左旋即可
L(u);
} else {
// 右子树的左子树高于右子树的右子树,先右旋再左旋
R(r[u]);
L(u);
}
}
}
update(u);
}
int main() {
int n, root = 0;
cin >> n;
for (int i = 0; i < n; ++i) {
int w;
cin >> w;
insert(root, w);
}
cout << v[root] << endl;
return 0;
}
1123 それは完全な AVL ツリーですか
分析する
- バランスのとれた二分木を構築するプロセスは前の質問とまったく同じである、包括的な基本的な質問
- 階層トラバーサルの実装プロセスでは、各ノード (またはその親ノード)
pos
の添え字を記録して、次のテーブルが n を超えているかどうかを判断します。n ノードの添え字が n を超えていない場合、それは完全なバイナリ ツリーである必要があります。 。两倍
两倍+1
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
const int N = 30;
using namespace std;
int n;
int l[N], r[N], v[N], h[N], idx;
int q[N], pos[N];
void update(int u) {
h[u] = max(h[l[u]], h[r[u]]) + 1;
}
void L(int &u) {
// 左旋
int p = r[u];
r[u] = l[p];
l[p] = u;
update(u);
update(p);
u = p;
}
void R(int &u) {
// 左旋
int p = l[u];
l[u] = r[p];
r[p] = u;
update(u);
update(p);
u = p;
}
int differ_height(int u) {
// 返回左右子树高度差
return h[l[u]] - h[r[u]];
}
void insert(int &u, int w) {
if (!u) {
u = ++idx;
v[u] = w;
} else if (w < v[u]) {
//插入左
insert(l[u], w);
if (differ_height(u) == 2) {
if (differ_height(l[u]) == 1) R(u);
else L(l[u]), R(u);
}
} else {
insert(r[u], w);
if (differ_height(u) == -2) {
if (differ_height(r[u]) == -1) L(u);
else R(r[u]), L(u);
}
}
update(u);
}
bool bfs(int root) {
q[0] = root;
int k = 1;
pos[root] = 1;
int head = 0, tail = 0;
while (head <= tail) {
int t = q[head++];
if (pos[t] > n) return false;
if (l[t]) q[++tail] = l[t], pos[l[t]] = 2 * pos[t];
if (r[t]) q[++tail] = r[t], pos[r[t]] = 2 * pos[t] + 1;
}
return true;
}
int main() {
cin >> n;
memset(h, -1, sizeof(h));
int root = 0;
for (int i = 0; i < n; ++i) {
int w;
cin >> w;
insert(root, w);
}
bool res = bfs(root);
cout << v[q[0]];
for (int i = 1; i < n; ++i) {
cout << ' ' << v[q[i]];
}
cout << endl;
if (res) cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
!!! 1135 それは赤黒の木ですか
英単語
- シンプルなパス シンプルなパス
- 子孫は子孫ノードを残します
注意点
- 赤黒木かどうかを判断するためのバランスの取れた二分木を構築する過程中
#include <iostream>
#include <algorithm>
#include <unordered_map>
const int N = 40;
using namespace std;
int n, k;
int pre[N], in[N];
unordered_map<int, int> pos;
bool res;
int build(int il, int ir, int pl, int pr, int &sum) {
int root = pre[pl];
int index = pos[abs(root)];
if (index > ir || index < il) {
res = false;
return 0;
}
int left = 0, right = 0, left_sum = 0, right_sum = 0;
if (il < index) left = build(il, index - 1, pl + 1, pl + 1 + index - 1 - il, left_sum);
if (index < ir) right = build(index + 1, ir, pl + 1 + index - 1 - il + 1, pr, right_sum);
if (left_sum != right_sum) res = false;
sum = left_sum;
if (root < 0) {
if (left < 0 || right < 0) res = false;
} else sum++;
return root;
}
int main() {
cin >> k;
while (k--) {
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> pre[i];
in[i] = abs(pre[i]);
}
sort(in, in + n);
pos.clear();
for (int i = 0; i < n; ++i) {
pos[in[i]] = i;
}
res = true;
int sum = 0;
int root = build(0, n - 1, 0, n - 1, sum);
if (root < 0) res = false;
if (res) puts("Yes");
else puts("No");
}
return 0;
}
レンプレート
英単語
分析する
注意点