前言:
vector相关知识:点此查看
queue相关知识:暂未写
题目描述
小 K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干个(也有可能没有)参考文献的链接指向别的博客文章。小 K 求知欲旺盛,如果他看了某篇文章,那么他一定会去看这篇文章的参考文献(如果他之前已经看过这篇参考文献的话就不用再看它了)。
假设洛谷博客里面一共有 𝑛(𝑛≤105)篇文章(编号为 1 到 𝑛)以及 𝑚(𝑚≤106)条参考文献引用关系。目前小 K 已经打开了编号为 1 的一篇文章,请帮助小 K 设计一种方法,使小 K 可以不重复、不遗漏的看完所有他能看到的文章。
这边是已经整理好的参考文献关系图,其中,文献 X → Y 表示文章 X 有参考文献 Y。不保证编号为 1 的文章没有被其他文章引用。
请对这个图分别进行 DFS 和 BFS,并输出遍历结果。如果有很多篇文章可以参阅,请先看编号较小的那篇(因此你可能需要先排序)。
输入格式
共 𝑚+1 行,第 1 行为 2 个数,n 和 m,分别表示一共有 𝑛(𝑛≤105) 篇文章(编号为 1 到 n)以及𝑚(𝑚≤106) 条参考文献引用关系。
接下来 m 行,每行有两个整数 𝑋,𝑌 表示文章 X 有参考文献 Y。
输出格式
共 2 行。
第一行为 DFS 遍历结果,第二行为 BFS 遍历结果。样例
样例输入
8 9 1 2 1 3 1 4 2 5 2 6 3 7 4 7 4 8 7 8
样例输出
1 2 5 6 3 7 8 4 1 2 3 4 5 6 7 8
解题:
题目略微分析以及注意点:
本题目需要注意我在题目中标记的红色部分,红色部分要求我们要从小到大的顺序,因此我们可能需要排序。
思路1及分析(超时,略写):
思路:
1)利用矩阵(1表示通路,0表示不通),直接避免了排序,从前往后遍历即可。
2)由于我们数量最大是1e5,所以我们要压成1e4,及各个数字都依次减去1。
3)DFS思路为以0(1的下标记为0)为起点、每个节点为结尾,遇到通路则转至以结尾为起点、每个节点为结尾,直到死路时回溯到上一个分支未遍历完的节点,访问另一条线路。
4)BFS思路为,将0(1的下标记为0)放入队列当中,如果队列不为空,取出队首,以队首为起点、每个节点为结尾,遇到通路则将对应的节点放入队列当中,重复绿色字部分,直到队列为空
注:以上BFS/DFS部分都需要判断元素是否访问,以防止重复遍历到。
总代码:
#include<iostream> #include<queue> using namespace std; const int MAX = 1e4 + 100; int n, m; bool visit[MAX];//false为未访问,true为已访问 int map[MAX][MAX];//初始化为0,1表示通路,0表示不通 void Creat(int m) { while (m--) { int tail, head; cin >> tail >> head; map[tail - 1][head - 1] = 1; } } void DFS(int start) { cout << start + 1 << " "; visit[start] = true; for (int i = 0; i < n; i++) { if (map[start][i] == 1 && visit[i] == false) { DFS(i); } } } //start表示下表,对应结果需要+1 void BFS(int start) { visit[start] = true; queue<int> a; a.push(start); while (!a.empty()) { int ans = a.front(); a.pop(); cout << ans+1<<" "; for (int i = 0; i < n; i++) { if (map[ans][i] == 1&&visit[i]==false) { visit[i] = true; a.push(i); } } } } int main() { cin >> n >> m; //全部初始化为未访问状态 for (int i = 0; i < n; i++) { visit[i] = false; } Creat(m);//建立矩阵 // 第一行为 DFS 遍历结果,第二行为 BFS 遍历结果 DFS(0); cout << endl; //重置访问状态 for (int i = 0; i < n; i++) { visit[i] = false; } BFS(0); return 0; }
结果:
问题分析:
由于我们在访问遍历时每遇到一层都会将所有的节点全部访问一遍,大大地降低了效率,导致超时。
思路2及分析(AC,详写):
逐层分析:
1)数据类型创建
说明:
利用vector的二维数组来形成类似于邻接表的结构(即省去了指针等),以一维数组的下标作为一段弧的起点,其中存放的内容为弧的终点【存放的内容有可能是无序的状态,因此我们要对每一层进行从小到大的排序】。
代码:
void Creat(int m) { while (m--) { int tail, head;//tail 弧尾, head 弧头,【即:tail—>head】 cin >> tail >> head; map[tail].push_back(head); } // 保证每个点遍历其邻居节点的时候从小到大,对应上方的注意点 for (int i = 1; i <= n; i++) { sort(map[i].begin(), map[i].end()); } }
2)DFS
说明:
进行DFS的过程,首先从节点1开始,搜索它所有的通路中所有的通路,直到全部遍历完成。
代码:
void DFS(int start) { cout << start << " "; visit[start] = true; for (auto it = map[start].begin(); it != map[start].end(); it++) { if (visit[(*it)] == false) { DFS((*it)); } } }
3)BFS
说明:
进行BFS的过程,将节点1压入队列中,如果队列不为空,取出队首,以队首为起点、每个节点为结尾,遇到通路则将对应的节点放入队列当中,重复绿色字部分,直到队列为空。
代码:
void BFS(int start) { visit[start] = true; queue<int> a; a.push(start); while (!a.empty()) { int ans = a.front(); a.pop(); cout << ans << " "; for (auto it = map[ans].begin(); it != map[ans].end(); it++) { if (visit[(*it)] == false) { visit[(*it)] = true; a.push((*it)); } } } }
注:以上BFS/DFS部分都需要判断元素是否访问,以防止重复遍历到。
总代码:
#include<iostream> #include<queue> #include<vector> #include<algorithm> using namespace std; const int MAX = 1e6 + 100; int n, m; bool visit[MAX];//false为未访问,true为已访问 vector<int> map[MAX];//[]内表示我们边起点(弧尾),map[]中的内容表示我们边的终点(弧头) void Creat(int m) { while (m--) { int tail, head;//tail 弧尾, head 弧头,【即:tail—>head】 cin >> tail >> head; map[tail].push_back(head); } // 保证每个点遍历其邻居节点的时候从小到大,对应上方的注意点 for (int i = 1; i <= n; i++) { sort(map[i].begin(), map[i].end()); } } void DFS(int start) { cout << start << " "; visit[start] = true; for (auto it = map[start].begin(); it != map[start].end(); it++) { if (visit[(*it)] == false) { DFS((*it)); } } } void BFS(int start) { visit[start] = true; queue<int> a; a.push(start); while (!a.empty()) { int ans = a.front(); a.pop(); cout << ans << " "; for (auto it = map[ans].begin(); it != map[ans].end(); it++) { if (visit[(*it)] == false) { visit[(*it)] = true; a.push((*it)); } } } } int main() { cin >> n >> m; //全部初始化为未访问状态 for (int i = 1; i <= n; i++) { visit[i] = false; } Creat(m); // 第一行为 DFS 遍历结果,第二行为 BFS 遍历结果 DFS(1); cout << endl; //重置访问状态 for (int i = 1; i <= n; i++) { visit[i] = false; } BFS(1); return 0; }
结果:
总结:
当我们数据量过大时,我们可以采用visit数组和省去已知无用部分来减少访问次数来提高算法效率。