本题来自于《算法设计与分析》课程实验的计蒜客题目,未经授权请勿转载

1 本题涉及到的知识点

树的先序遍历、图的邻接表

2 题目描述

在蒜头君的国度里有蒜头君的国度 - 图1个城市,这蒜头君的国度 - 图2个城市间只有蒜头君的国度 - 图3条路把这个蒜头君的国度 - 图4个城市连接起来。现在,蒜头君在第蒜头君的国度 - 图5号城市,他有张该国地图,他想知道如果自己要去参观第蒜头君的国度 - 图6号城市,必须经过的前一个城市是几号城市(假设你不走重复的路)。

3 输入与输出格式

3.1 输入格式

第一行输入一个整数蒜头君的国度 - 图7表示测试数据共有 蒜头君的国度 - 图8#card=math&code=M%281%5Cle%20M%5Cle%205%29&id=EqXS7)组。
每组测试数据的第一行输入一个正整数蒜头君的国度 - 图9#card=math&code=N%281%5Cle%20N%5Cle%20100000%29&id=i7wWd)和一个正整数蒜头君的国度 - 图10#card=math&code=S%281%5Cle%20S%5Cle%20N%29&id=dvHtw),蒜头君的国度 - 图11表示城市的总个数,蒜头君的国度 - 图12表示蒜头君所在城市的编号。
随后的蒜头君的国度 - 图13行,每行有两个正整数蒜头君的国度 - 图14#card=math&code=a%2Cb%281%5Cle%20a%2Cb%5Cle%20N%29&id=HUFeg),表示第蒜头君的国度 - 图15号城市和第蒜头君的国度 - 图16号城市之间有一条路连通。

3.2 输出格式

每组测试数据输蒜头君的国度 - 图17个正整数,其中,第蒜头君的国度 - 图18个数表示从蒜头君的国度 - 图19走到蒜头君的国度 - 图20号城市,必须要经过的上一个城市的编号。(其中蒜头君的国度 - 图21时,请输出蒜头君的国度 - 图22)。

4 样例

4.1 样例输入

  1. 1
  2. 10 1
  3. 1 9
  4. 1 8
  5. 8 10
  6. 10 3
  7. 8 6
  8. 1 2
  9. 10 4
  10. 9 5
  11. 3 7

4.2 样例输出

  1. -1 1 10 10 9 8 3 1 1 8

5 分析

5.1 问题的转化

本题目提到蒜头君的国度 - 图23个城市间只有蒜头君的国度 - 图24条路把这个蒜头君的国度 - 图25个城市连接起来,因此整个地图将构成树,蒜头君所在的城市即为树的根节点。要前往一个城市,必须要经过的城市就是这个城市的父节点。因此,问题转化为:已知一颗树,如何能求出每个结点的父结点?

5.2 求父节点的方法

我们可以使用树的先序遍历对整个树进行遍历,在这个过程中对所有结点的父结点进行记录,从而得出问题的答案。

5.3 树的存储方法

由于本题在输入树的时候,还不知道根结点与其他结点的连接关系,因此只能采用无向图的存储方式来存储,可选的方式为邻接矩阵或者邻接表。
由于本题数据规模较大:蒜头君的国度 - 图26#card=math&code=N%281%5Cle%20N%5Cle%20100000%29&id=QwSdo),使用邻接矩阵存储很可能会导致空间超限。因此,使用邻接表的方式最为合适,我们在这里使用vector数组来作为邻接表,记录每个结点相连接的其他所有结点的序号:vector<int> t[100001]

5.4 避免重复访问的方法

由于我们在存储树的时候,是按照无向图的方式来存储,并不是只存储了该结点的子结点,而是把所有具有连接关系的结点都进行了存储。所以,如果直接进行先序遍历的话,会导致重复访问节点(A访问B,B访问A……以此循环),出现死循环。因此,我们需要用visit数组来记录是否访问过了某个结点,如果访问过了,则不对它进行遍历。

6 源代码

  1. #include<iostream>
  2. #include<vector>
  3. using namespace std;
  4. int father[100001];
  5. void travel(int head, int lasthead, vector<int>* t, int* visit)
  6. /*函数功能:递归形式的树的先序遍历,在遍历途中,把每个结点的父节点记录到father数组中。
  7. 四个参数的含义:
  8. head:当前正在遍历的结点序号
  9. lasthead:当前正在遍历的结点的父节点序号
  10. t:被遍历的树(vector数组,记录了相邻两个结点的连接关系)
  11. visit:避免重复访问而设置的数组。由于vector数组t记录的连接关系的双向的,因此,为了避免重复访问,需要使用visit数组检测该结点是否已经遍历过了。
  12. */
  13. {
  14. father[head] = lasthead;//设置当前结点的父节点是lasthead
  15. visit[head] = 1;//表示该结点已经访问过了
  16. for (int i = 0; i < t[head].size(); i++)//遍历所有与当前结点连接的结点
  17. {
  18. if (!visit[t[head][i]])//如果这个结点还没有被访问过
  19. travel(t[head][i], head, t, visit);//使用先序遍历对其进行访问
  20. }
  21. }
  22. int main()
  23. {
  24. int m;
  25. cin >> m;//输入测试数据的个数
  26. for (int i = 0; i < m; i++)//遍历每次的测试数据
  27. {
  28. int n, s;
  29. cin >> n >> s;//输入城市个数、蒜头君在哪个城市
  30. father[s] = -1;//定义蒜头君所在城市的父结点为-1(无父节点)
  31. int visit[100001] = { 0 };//清除访问过的城市
  32. vector<int> t[100001];//定义树vector数组
  33. for (int j = 0; j < n - 1; j++)//输入n-1组数据
  34. {
  35. int a, b;
  36. cin >> a >> b;//输入连接的两个结点号
  37. t[a].push_back(b);//把相应结点号加入到对应vector结点中
  38. t[b].push_back(a);//表示a与b连接,b也与a连接
  39. }
  40. travel(s, -1, t, visit);//对该树进行先序遍历,遍历途中记录每个结点的父节点,存到father数组中。这里传入-1参数的原因是,根节点的父结点为-1
  41. for (int j = 1; j <= n; j++)//遍历father数组
  42. cout << father[j]<<" ";//打印输出每个结点的父节点
  43. }
  44. }

7 主函数流程图

蒜头君的国度 - 图27