题意
给出了一个树(即没有循环的连通图),其顶点由整数1、2,…,n编号。这样的树的“ Prufer”代码构建如下:提取具有最小数目的叶子(仅入射到一个边缘的顶点)。将该叶子及其入射边缘从图形中删除,同时将与该叶子相邻的顶点的数目记下来。在获得的图形中,重复此过程,直到只剩下一个顶点(顺便说一句,该顶点始终具有数字n)。写下的n-1个数字的序列称为树的Prufer码。
给定一棵树,您的任务将计算其Prufer代码。用以下语法指定的语言单词来表示树:
T ::= “(” N S “)”
S ::= " " T S
| empty
N ::= number
也就是说,树周围有括号,并有一个数字表示根顶点的标识符,然后是由单个空格字符分隔的任意多个(可能没有)子树。例如,看一下下图中的树,该树在样本输入的第一行中表示。
注意,根据以上给出的定义,树的根也可以是叶。仅为了便于说明,我们将某些顶点指定为根。通常,我们在这里处理的被称为“无根树”。
输入
输入包含几个测试用例。每个测试用例在输入文件的一行上指定一棵如上所述的树。输入由EOF终止。您可以假设1 <= n <= 50。
输出
为每个测试用例生成一行,其中包含指定树的Prufer代码。用一个空格分隔数字。不要在行尾打印任何空格。
样例
Sample Input
(2 (6 (7)) (3) (5 (1) (4)) (8))
(1 (2 (3)))
(6 (1 (4)) (2 (3) (5)))
Sample Output
5 2 5 2 6 2 8
2 3
2 1 6 2 6
解题思路
处理输入
由题意和样例一我们可以知道,所给树的根节点,也是可以被当作叶子节点处理的,同时,我们需要知道的是每一个结点的度即可,并不需要知道结点位于树的位置。所以,我们本题应当将之当作一个无向无环图来处理而不是树。那么,处理时,我们只要记录图中具有了哪些边即可。
本题的输入是每一个括号表示一棵树,括号内首先由一个数字表示结点编号,若该树由子树(或子结点)存在,则在结点编号空格后,同样以括号表示一棵树。
那么这道题便可采用递归获取所有边,不过在本题中,遇到右括号后,就代表一棵树的结束,也就是说,这颗树对应的编号将不会再用到,且这颗树的所有子树都会在该右括号之前列出,于是我便采用数组记录每一层树的编号,以样例一为例,我所开设的数组将会记录如下:
下标 | 0 | 1 | 2 |
---|---|---|---|
可能记录的编号 | 2 | 6 3 5 8 | 7 1 4 |
即树的同一层内的所有结点都由一个数组内一个元素表示,这是由于本题输入的特殊性,使得当遇到新的同层结点时,必然是在原本的同层结点已经使用完毕以后,那么我们便可无所顾虑的将之覆盖。而通过所记录的编号,可以轻易知晓这个结点所相连的另一个结点的编号,并用集合数组记录每一个结点所相连的结点都有哪些。
寻找仅有一个度的最小结点
我们利用集合的 size() 方法可以轻易知晓每个结点的度,那么我们只要将所有度为1的结点装入优先队列里,便可快速从中拿出度为1的最小结点 A。将这个结点 A 相连的结点 B 的编号输出,再将结点 B 集合内的 A 删除即完成了一次删边操作,若删除后结点 B 的度剩余1,那么就将结点 B入队。
注:本题有点卡常数
AC代码
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
using namespace std;
priority_queue<int>Q;//优先队列,用于快速取出度为1的最小结点
const int maxn = 105;
set<int>S[maxn];//存放每一个结点的相连结点
int n = 0;//结点总数
int floors;//当前结点所在的层数
void init() {
for (int i = 0; i < maxn; i++)
S[i].clear();
while (!Q.empty())
Q.pop();
n = 0;
floors = 0;
}
int main() {
char str;
int t[maxn];//记录每一层当前的编号(遇到同层结点则被覆盖)
while (scanf("%c", &str) != EOF) {
init();
//处理输入
while (str != '\n') {
if (str == '(')
floors++;//左右括号决定树层数的增减
else if (str == ')')
floors--;
else if (str >= '0' && str <= '9') {
int a = str - '0'; // 结点编号
while (true) {
scanf("%c", &str);
if (str >= '0' && str <= '9')
a = a * 10 + str - '0';
else
break;
}
n++;
t[floors] = a;//将当前层数的结点编号覆盖
if (floors > 1) {//若层数大于1,则与上一次的结点构成边
S[a].insert(t[floors - 1]);
S[t[floors - 1]].insert(a);
}
continue;
}
scanf("%c", &str);
}
//将度为1的结点入队
for (int i = 1; i <= n; i++) {
if (S[i].size() == 1)
Q.push(-i);
//将所有度为1的结点入队,由于我采用的是最大堆优先队列,为了能构成最小堆效果,故将编号的相反数入库
}
//开始计算Prufer序列
for (int k = 1; k < n; k++) {
int now = -Q.top();
Q.pop();
int b = *S[now].begin();//由于只有一个元素,则不使用迭代器,可直接利用begin找到元素所在位置
S[b].erase(now);//将结点b里的now元素删除
cout << b;
if (k < n - 1)
cout << " ";
if (S[b].size() == 1)
Q.push(-b); //若度为1,入队
}
cout << endl;
}
return 0;
}