【数据结构1-4】图的基本应用

 一、【P5318】查找文献(邻接表+DFS+BFS)​​​​​​

本题是图的遍历模板题,需要使用DFS和BFS遍历方法。

由于本题最多有1e5个顶点,如果采用邻接矩阵存储图的话需要4*1e10 Byte空间,显然会超内存,因此这里采用邻接表的方法存储。但需要注意的是,本题并没有使用广义上的邻接表,而是使用vector和array结合的方式存储,先用Edge数组存储边,并赋予每条边一个序号,然后用二维vector数组模拟邻接表,一维空间存储顶点的编号,二维空间存储该点所连接的边的序号。 

1.1 图的邻接表存储

8 9
1 2 //0号边(由于vector的下标是从0开始的,咱就“入乡随俗”,从0开始)
1 3 //1号边
1 4 //2号边
2 5 //3号边
2 6 //4号边
3 7 //5号边
4 7 //6号边
4 8 //7号边
7 8 //8号边

最后二维vector中的存储会如下所示:

0 1 2 //1号顶点连着0、1、2号边
3 4	//2号顶点连着3、4号边
5	//3号顶点连着5号边
6 7 //4号顶点连着6、7号边
	//5号顶点没有边
	//6号顶点没有边
8	//7号顶点连着8号边
	//8号顶点没有边

1.2 DFS深搜

void DFS(int cur)
{
	visit[cur] = 1;
	cout << cur << " ";
	for (int i = 0; i < arr[cur].size(); i++)
	{
		int dot = Edge[arr[cur][i]].v;
		if (!visit[dot])
			DFS(dot);
	}
}

1.3 BFS广搜

void BFS(int cur)
{
	queue<int> q;
	q.push(cur);
	cout << cur << " ";
	visit[cur] = 1;
	while (!q.empty())
	{
		int f = q.front();
		for (int i = 0; i < arr[f].size(); i++)
		{
			int dot = Edge[arr[f][i]].v;
			if (!visit[dot])
			{
				q.push(dot);
				cout << dot << " ";
				visit[dot] = 1;
			}
		}
		q.pop();
	}
}

1.4 AC代码

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <cstring>
#include <set>
#include <queue>

using namespace std;

vector<int> arr[100005];
struct edge
{
	int u, v;
};
vector<edge> Edge;

bool visit[100005] = { 0 };
void DFS(int cur)
{
	visit[cur] = 1;
	cout << cur << " ";
	for (int i = 0; i < arr[cur].size(); i++)
	{
		int dot = Edge[arr[cur][i]].v;
		if (!visit[dot])
			DFS(dot);
	}
}

void BFS(int cur)
{
	queue<int> q;
	q.push(cur);
	cout << cur << " ";
	visit[cur] = 1;
	while (!q.empty())
	{
		int f = q.front();
		for (int i = 0; i < arr[f].size(); i++)
		{
			int dot = Edge[arr[f][i]].v;
			if (!visit[dot])
			{
				q.push(dot);
				cout << dot << " ";
				visit[dot] = 1;
			}
		}
		q.pop();
	}
}

bool cmp(edge x, edge y)
{
	return x.v < y.v;
}
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int a, b;
		cin >> a >> b;
		edge s; s.u = a; s.v = b;
		Edge.push_back(s);
	}
	sort(Edge.begin(), Edge.end(), cmp); //容器无法用指针,只能用迭代器
	for (int i = 0; i < m; i++)
	{
		arr[Edge[i].u].push_back(i);
	}
	DFS(1);
	cout << endl;
	memset(visit, 0, sizeof(visit));
	BFS(1);

}

二、【P1113】杂物(拓扑排序+动态规划)

本题显然是一个拓扑排序的模板题,需要先完成某个任务的先决任务后才能再完成该任务。

那么如何统计总的耗时呢?可以使用记忆化搜索(动态规划)的方法,完成一个任务后,更新所有后继任务的耗时,需要注意的是更新时要选择最长的时间,因为完成所有先决条件的时间一定比完成部分先决条件的用时更长。 

2.1 拓扑排序 

总结:

  1. 初始化队列,将入度为 00 的节点放入队列。
  2. 取出队首,遍历其出边,将能够到达的点入度减一,同时维护答案数组。
  3. 若在此时一个点的入度变为 11,那么将其加入队列。
  4. 回到第二步,直到队列为空。

2.2 AC代码

1.输入杂物耗时,并用vector存下它的后继杂物。并用数组ind[i]存下第 i 个杂物的先决杂物个数(即入度)。

2.将所有入度为0的点 i 加入队列,并将dp[i](表示第 i 个杂物的最早完成时间)的值置为第 i 个杂物的耗时stuff[i].len。

3.只要队列不空,拿出队列顶端的杂物cur,并用cur更新以cur为先决杂物的杂物nxt的dp[nxt]的值。使dp[nxt]=max(dp[nxt],dp[cur]+stuff[nxt].len)。再将ind[nex]−−。如果ind[nex]为0,将点nxt加入队列。

4.求出所有dp[i]的最大值ans,就是答案。

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <cstring>
#include <set>
#include <queue>

using namespace std;

struct node
{
	int len;
	vector<int> next;
}stuff[10005];

queue<int> q;
int dp[10005] = { 0 };
int ind[10005] = { 0 };

int main()
{
	int n;	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int No;	cin >> No;
		cin >> stuff[i].len;
		int tmp;	cin >> tmp;
		while (tmp != 0)
		{
			ind[i] += 1;
			stuff[tmp].next.push_back(i);
			cin >> tmp;
		}
	}
	//1.初始化队列
	for (int i = 1; i <= n; i++)
	{
		if (ind[i] == 0)
		{
			q.push(i);
			dp[i] = stuff[i].len;
		}
	}

	//2.不断取出队首,删除出度边
	while (!q.empty())
	{
		int cur = q.front();
		q.pop();
		for (int i = 0; i < stuff[cur].next.size(); i++)
		{
			int nxt = stuff[cur].next[i];
			ind[nxt]--;
			//3.当入度为0时加入队列
			if (ind[nxt] == 0) q.push(nxt);
			dp[nxt] = max(dp[nxt], dp[cur] + stuff[nxt].len);//为什么是max?如果dp值小说明有其他先决条件没有完成
		}
	}
	int ans = -1;
	for (int i = 1; i <= n; i++)
		ans = max(ans, dp[i]);
	cout << ans << endl;
}

三、【P1363】幻象迷宫(DFS)

DFS或者BFS均可实现,问题在于这里变成了无限大的迷宫,出现了什么样的状况就是无限大了呢?一开始想比如穿越了边界是不是就能走到无限远了,但显然不是,因为穿越边界之后有可能仍然是死路,比如说上面的样例2 。
正确的条件应该是,当走到(modx,mody)时,仍然记录到达此点的真实的x和y,当再一次走到(x mod n,y mod m)时,如果此时的真实的x和y不同,说明已经穿越了若干个完整的地图了,接下来只需和刚才走一样的路线就一定能走无限远了。

3.1 DFS注意事项

  1. 由于内存限制,想要记住所有走过的点是不现实的,所以我们只能记住最近一次走到迷宫上某点时的真实位置。
  2. 需要注意的是cur_x和cur_y可能会是一个很大的负数,所以导致越界访问,需要确保得到的pos_x和pos_y是正数。

3.2 AC代码

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;

int n, m;
bool mp[1505][1505];


int dx[4] = { -1,1,0,0 };
int dy[4] = { 0,0,-1,1 };
bool flag = false;
void DFS(map<pair<int, int>, pair<int, int>> &visit,int pos_x,int pos_y,int cur_x,int cur_y)
{
	if (flag) return;
	if (visit.count({ pos_x,pos_y }) && (visit[{pos_x, pos_y}].first != cur_x || visit[{pos_x, pos_y}].second != cur_y))
	{
		flag = true;
		return;
	}
	if (visit.count({ pos_x,pos_y }) && visit[{pos_x, pos_y}].first == cur_x && visit[{pos_x, pos_y}].second == cur_y) return;
	
	visit[{pos_x, pos_y}] = { cur_x,cur_y };
	
	for (int i = 0; i < 4; i++)
	{
		int next_x = (pos_x + dx[i] + n) % n;//如果cur_x是一个较大负数,那么就会导致越界
		int next_y = (pos_y + dy[i] + m) % m;//同理,必须用pos_y保证是正数
		if (mp[next_x][next_y])
			DFS(visit, next_x,next_y,cur_x + dx[i], cur_y + dy[i]);
	}
}

int main()
{
	while (cin >> n >> m)
	{
		int Si = 0, Sj = 0;
		memset(mp, false, sizeof(mp));
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < m; j++)
			{
				char tmp; cin >> tmp;
				if (tmp == 'S')
				{
					Si = i;	Sj = j;
					mp[i][j] = true;
				}
				else if (tmp=='.')
				{
					mp[i][j] = true;
				}	
			}
		}
		map<pair<int, int>, pair<int, int>> visit;
		flag = false;
		DFS(visit, Si, Sj, Si, Sj);
		if (flag) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值