P8603 [蓝桥杯 2013 国 C] 横向打印二叉树 题解
P8603 [蓝桥杯 2013 国 C] 横向打印二叉树 题解
分析
吐槽:题目其实很不清晰,横向打印的定义都没讲,即使要求很显然,也不应让选手必须结合样例理解。
根据题意模拟即可。
我们把问题拆解开来:
- 建树。
- 确定节点纵坐标。
- 确定节点横坐标。
- 连接节点。
建树
建树很简单,相信学过二叉搜索树(Binary Search Tree, BST)的都会,但这里我还是再讲一下。
根据 BST 的性质(题目里也讲了),左儿子
:::info[C++]
// BST 节点
// 这里采用指针式写法
struct TreeNode {
int val; // 权值
TreeNode* l{nullptr}; // 左儿子
TreeNode* r{nullptr}; // 右儿子
} tree[105];
TreeNode* root; // 根节点
// 递归插入节点
TreeNode* insert(TreeNode* root, int x) {
if (root == nullptr) // 找到空位
return new TreeNode{x};
if (x < root->val) // 待插入节点小于根节点则往左子树找
root->l = insert(root->l, x);
else // 否则往右子树找
root->r = insert(root->r, x);
return root;
}
:::
(其实我一开始也不知道怎么写,还是翻了以前写的 BST 模板才写出来的,就当是复习基础算法了)
确定节点纵坐标
观察样例:
...|-12
10-|
...|-8-|
.......|...|-7
.......|-5-|
...........|-4
去除多余信息:
12
10
8
7
5
4
不难发现,节点的纵坐标实际上就是中序遍历序号,也就是按权值排序,自下至上递增,而且输出结果的行数正好是节点数量,所以我们绘图的时候就需要使用中序遍历。
:::info[简易的中序遍历]
void dfs(TreeNode* root) {
if (root == nullptr)
return;
dfs(root->l);
cout << root->val << ' ';
dfs(root->r);
}
:::
确定节点横坐标
依旧观察样例,可以发现,节点的横坐标其实就类似深度,是由父节点决定的。
因此,我们计算当前节点的宽度,即前缀 |-、节点权值、后缀 -| 的长度和,在进入下一层 dfs 时,我们将深度加上当前节点的宽度。
连线
还是观察样例,我们会发现,权值为 |。
不难想到,我们让 dfs 函数返回一个整数——当前节点所处的列,这样我们只要在相应深度连线就行了。
完整代码
Talk is cheap, show me the code.
:::info[C++]
#include <iostream>
using namespace std;
// BST 节点
// 这里采用指针式写法
struct TreeNode {
int val; // 权值
TreeNode* l{nullptr}; // 左儿子
TreeNode* r{nullptr}; // 右儿子
} tree[105];
TreeNode* root; // 根节点
// 递归插入节点
TreeNode* insert(TreeNode* root, int x) {
if (root == nullptr) // 找到空位
return new TreeNode{x};
if (x < root->val) // 待插入节点小于根节点则往左子树找
root->l = insert(root->l, x);
else // 否则往右子树找
root->r = insert(root->r, x);
return root;
}
int row = 0;
string ans[105];
// 将 pattern 插入在 main 的下标 pos 位置,不足部分用'.'补足
void insert_string(string& main, string pattern, int pos) {
if (main.size() < pos + pattern.size())
main += string(pos + pattern.size() - main.size(), '.');
for (int i = 0; i < pattern.size(); i++)
main[pos + i] = pattern[i];
}
int dfs(TreeNode* root, int depth = 0) {
if (root == nullptr)
return row;
// 生成当前节点标签
string label;
if (depth) // 根节点以外的节点都有父节点
label += "|-";
label += to_string(root->val); // 权值
if (root->l || root->r) // 检查是否有子节点
label += "-|";
depth += label.size() - 1; // 缩进
// 左子树
int left_row = dfs(root->l, depth);
for (int i = left_row + 1; i < row; i++)
insert_string(ans[i], "|", depth);
// 绘制节点
insert_string(ans[row], label, depth - label.size() + 1); // 需要暂时取消缩进
int current_row = row++;
// 右子树
int right_row = dfs(root->r, depth);
for (int i = current_row + 1; i < right_row; i++)
insert_string(ans[i], "|", depth);
return current_row;
}
int main() {
int x;
while (cin >> x)
root = insert(root, x);
dfs(root);
// 倒序输出
for (int i = row - 1; i >= 0; i--)
cout << ans[i] << endl;
return 0;
}
:::