P5318 【深基18.例3】查找文献

前言:

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数组省去已知无用部分来减少访问次数来提高算法效率。

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木兮xg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值