UVa 10000 Longest Paths (单源最长路 - floyd or 拓扑排序)

本文介绍使用Floyd算法和拓扑排序解决有向无环图(DAG)中寻找从特定起点到其它各点最长路径的问题,并对比两种算法的效率。通过具体实例展示了算法实现过程。

http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=941


由于n很小,floyd算法写起来方便,先用这个A了一下:

/*0.162s*/

#include<bits/stdc++.h>
using namespace std;
const int mx = 105;

int d[mx][mx];

void floyd(int n)
{
	int k, i, j;
	for (k = 1; k <= n; ++k)
		for (i = 1; i <= n; ++i)
			for (j = 1; j <= n; ++j)
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
	int cas = 0, n, s, i, a, b, dis, to;
	while (scanf("%d%d", &n, &s), n)
	{
		memset(d, 0x3f, sizeof(d));
		for (i = 1; i <= n; ++i) d[i][i] = 0;
		while (scanf("%d%d", &a, &b), a)
			d[a][b] = -1; ///取负值
		floyd(n);
		dis = 0;
		for (i = 1; i <= n; ++i)
		{
			if (d[s][i] < dis)
			{
				dis = d[s][i];
				to = i;
			}
		}
		printf("Case %d: The longest path from %d has length %d, finishing at %d.\n\n", ++cas, s, -d[s][to], to);
	}
	return 0;
}

然后由于是DAG,于是用拓扑排序A了一下,快的不是一点半点:

/*0.038s*/

#include<bits/stdc++.h>
using namespace std;
const int mx = 105;

vector<int> G[mx];
bool vis[mx];
int topo[mx], disTo[mx], cnt;

void dfs(int i)
{
	vis[i] = true;
	for (int j = 0; j < G[i].size(); ++j) ///对于vector,[0,size())
		if (!vis[G[i][j]]) dfs(G[i][j]);
	topo[cnt++] = i;
}

void dagSP(int s)
{
	int i = cnt, j, v;
	while (topo[--i] != s);
	memset(disTo, 0x3f, sizeof(disTo)); /// 初始化
	disTo[s] = 0;
	for (; i >= 0; --i)
	{
		v = topo[i];
		for (j = 0; j < G[v].size(); ++j) ///注意是以v为主
			disTo[G[v][j]] = min(disTo[G[v][j]], disTo[v] - 1);
	}
}

int main()
{
	int cas = 0, n, s, i, a, b, dis, to;
	while (scanf("%d%d", &n, &s), n)
	{
		for (i = 1; i <= n; ++i) G[i].clear();
		while (scanf("%d%d", &a, &b), a) G[a].push_back(b);
		cnt = 0; /// 最重要的初始化!
		memset(vis, 0, sizeof(vis));
		for (i = 1; i <= n; ++i)
			if (!vis[i]) dfs(i);
		dagSP(s);
		dis = 0;
		for (i = 1; i <= n; ++i)
		{
			if (disTo[i] < dis)
			{
				dis = disTo[i];
				to = i;
			}
		}
		printf("Case %d: The longest path from %d has length %d, finishing at %d.\n\n", ++cas, s, -disTo[to], to);
	}
	return 0;
}

使用拓扑排序解有向无环图(DAG)最长路的算法是一种有效的方法。在面对数据量大的情况时,SPFA 或者 Dijkstra + heap 可能会超时,而对于 DAG 图,可以通过拓扑排序(topsort)来优化算法,进而通过 DAG 最短路径(DAGShortestPath)的思路最长路径 [^1]。 拓扑排序解 DAG 最长路的基本步骤如下: 1. **拓扑排序**:对 DAG 进行拓扑排序,得到顶点的拓扑序列。拓扑排序可以确保对于图中的每条有向边 (u, v),顶点 u 在拓扑序列中都出现在顶点 v 之前。 2. **初始化距离数组**:创建一个距离数组 dist,用于记录从源点到每个顶点的最长路径长度。将所有顶点的初始距离设为负无穷(通常设为负的一个很大的值),源点的距离设为 0。 3. **按拓扑顺序更新距离**:按照拓扑序列依次遍历每个顶点 u,对于每个顶点 u 的所有出边 (u, v),更新顶点 v 的最长路径长度。更新公式为:`dist[v] = max(dist[v], dist[u] + weight(u, v))`,其中 `weight(u, v)` 是边 (u, v) 的权重。 以下是使用 Python 实现的示例代码: ```python from collections import defaultdict, deque def topological_sort(graph, num_vertices): in_degree = [0] * num_vertices for u in graph: for v in graph[u]: in_degree[v] += 1 queue = deque([i for i in range(num_vertices) if in_degree[i] == 0]) top_order = [] while queue: u = queue.popleft() top_order.append(u) for v in graph[u]: in_degree[v] -= 1 if in_degree[v] == 0: queue.append(v) return top_order def dag_longest_path(graph, num_vertices, source): top_order = topological_sort(graph, num_vertices) dist = [-float('inf')] * num_vertices dist[source] = 0 for u in top_order: if dist[u] != -float('inf'): for v, weight in graph[u]: dist[v] = max(dist[v], dist[u] + weight) return dist # 示例图的邻接表表示 graph = defaultdict(list) graph[0] = [(1, 3), (2, 2)] graph[1] = [(2, 4), (3, 2)] graph[2] = [(3, 1)] graph[3] = [] num_vertices = 4 source = 0 longest_paths = dag_longest_path(graph, num_vertices, source) print("从源点", source, "到各顶点的最长路径长度:", longest_paths) ``` ### 复杂度分析 - **时间复杂度**:拓扑排序的时间复杂度为 $O(V + E)$,其中 $V$ 是顶点数,$E$ 是边数。更新距离的过程也需要 $O(V + E)$ 的时间,因此总的时间复杂度为 $O(V + E)$。 - **空间复杂度**:主要用于存储拓扑序列和距离数组,空间复杂度为 $O(V)$。 ### 正确性证明 由于拓扑排序保证了对于每条有向边 (u, v),顶点 u 在拓扑序列中先于顶点 v 被处理,因此在更新顶点 v 的最长路径长度时,顶点 u 的最长路径长度已经是最优的。所以,按照拓扑顺序依次更新距离可以得到从源点到每个顶点的最长路径。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值