一,基础知识
图的前向星和链式前向星表示(参考这里)
前向星是一种特殊的边集数组,我们把边集数组中每一条边的起点按照从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了。用len[i]和head[i]分别表示以点i为起点的边的个数和起始位置。因为前向星有排序动作,所以最快是nlogn级别的。但是用链式前向星就可以避免排序。
可以建立边结构体:
struct Edge {
int to, w, next;
}edge[M];
其中edge[i].to是第i条边的终点,edge[i].next表示与第i条边起点相同的边的下一个存储位置,edge[i].w为这条边的权值。
这样,添加边的函数就可以写成:
void addEdge(int u, int v, int w)
{
edge[ecnt].to = v;
edge[ecnt].w = w;
edge[ecnt].next = head[u];
head[u] = ecnt++;
}
其中ecnt是边的个数,这个函数将这一条边加入到之前的起点相同边的开头,head数组一般初始化为-1,ecnt初始化为0。这样加入的和读出来的顺序是相反的,但是不影响结果的正确性。
下边的例子全部都使用链式前向星。
二,最短路算法
最短路算法一般都分为初始化和松弛两个步骤。
a. Dijkstra算法
dijkstra适用于边权为正的情况,在边权为负的情况下,不能正确求出最短路。算法同时适用于有向图和无向图。下边是伪代码:
清除所有点的标号
设d[start] = 0,其他d[i] = INF 循环n次 { 在所有未标号的结点中选出d值最小的结点x 给结点x标记 对于从x出发的所有边(x,y),更新d[y] = min(d[y], d[x] + w[x][y]) }
如果需要记录路径,那么加上一个指向父节点的数组,每次松弛的时候,更新父节点就行了。
实际的代码如下:
void dijkstra(int start)
{
clr(vis, 0);
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
for (int i = 1; i <= n; ++i) {
int x = 0;
int mnx = INF;
for (int j = 1; j <= n; ++j) {
if (!vis[j] && mnx >= dist[j]) { // 保证可以选一个点
x = j;
mnx = dist[j];
}
}
vis[x] = true;
int p = rhead[x];
while (p >= 0) {
int v = redge[p].to;
int w = redge[p].value;
dist[v] = min(dist[x] + w, dist[v]);
p = redge[p].next;
}
}
}
上边的代码复杂度是O(n^2),在实际应用中可以使用优先队列每次选取没有标号的d值最小的点,来进行松弛,复杂度为O(mlogn),每次出队列的时候,如果已经标记了的话,直接忽略这个结点不再扩展了,代码如下:
void heap_dijkstra(int start)
{
clr(vis, 0);
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
priority_queue<Node> pq;
pq.push(Node(start, 0, 0));
while (!pq.empty()) {
Node x = pq.top();
pq.pop();
if (vis[x.u]) {//松弛过就不再松弛了
continue;
}
vis[x.u] = true;
int p = rhead[x.u];
while (p >= 0) {
int a = redge[p].from;
int b = redge[p].to;
int w = redge[p].value;
if (dist[b] > dist[a] + w) {
dist[b] = dist[a] + w;
pq.push(Node(b, dist[b], 0));
}
p = redge[p].next;
}
}
}
b. Bellman-ford算法
dijkstra不能处理带有负权的图,需要注意,当负权存在时,连最短路都不一定存在了,但是还是有办法在最短路存在的情况下把它求出来。如果最短路存在,一定存在一个不含环的最短路。既然不含环,最短路最多只经过n-1个结点(不含起点),可以通过n-1轮松弛操作得到。代码如下:
void bellman_ford(int start)
{
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
for (int i = 0; i < n - 1; ++i) {//松弛n-1次
for (int j = 0; j < m; ++j) {//每次对m条边松弛
int u = redge[j].from;
int v = redge[j].to;
if (dist[u] < INF) {
dist[v] = min(dist[v], dist[u] + redge[j].value);
}
}
}
}
可见上述算法的复杂度是O(mn),spfa(Shortest Path Faster Algorithm)算法可以讲其优化到O(kn),一般来说k小于等于2。
需要注意,在每次入队时将标记置为真,出队时将标记置为假,只有能更新的时候才更新,当不在队列中加入队列,同时记录出队的次数,如果出队的次数大于等于n(每次出队都要松弛与他相连的点,那么大于等于n次就说明有了环),那么表明有负环。同样bellman-ford算法如果松弛n-1次之后,还可以再松弛,说明有负环。
下面代码在发现负环时及时退出,但只是说明s可以到达一个负环,并不代表s到每个点的最短路都不存在。另外如果图中有一个负环但是s无法到达,这个负环,那么上面的算法也无法找到。这时可以增加一个虚拟的点,这个点到所有的点的长度都是0,从这个点开始搜索一次,就可以找到图中是不是有负环了。这需要在具体的题目中实践。
代码如下:
void spfa(int start)
{
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
clr(c, 0);
clr(vis, 0);
queue<int> q;
q.push(start);
vis[start] = true;
while (!q.empty()) {
int x = q.front();
q.pop();
int p = rhead[x];
vis[x] = false;;
while (p >= 0) {
int u = redge[p].from;
int v = redge[p].to;
int w = redge[p].value;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!vis[v]) {
vis[v] = true;
++c[v];
if (c[v] >= n) {
return;
}
q.push(v);
}
}
p = redge[p].next;
}
}
}
三,k短路算法(参考这里)
求k短路其实最容易想到的方法是利用广度优先搜索的思想,扩展点,当这个点第k次出队列的时候,当前找到的长度就是第k短的路,但是,这时会产生比较多的状态,当图比较小,k值比较小的时候还可以接受。
目前使用比较多的是单源最短路径配合A*算法,其中A*所使用的启发函数h是当前结点到目标结点的最短路,这时,就需要预先反向求出从目标结点到其他结点的最短路长度,总体的启发函数f(u)=g(u)+h(u),g(u)是从起始结点到当前结点的实际路径长度,h(u)如前所述。
使用一个优先队列来处理结点,先将起始结点加入优先队列,从优先队列中取出f值最小的结点,如果是目标结点那么计算已经到达的次数,如果是k,那么就退出,当前的g值就是答案,否则,将以当前结点为起点的边的终点计算f值,加入队列。这里需要注意的是当s与t相等的时候,需要求k+1短路,因为第1短路是0,算作没有走。
代码如下:
int bfs(void)
{
priority_queue<Node> pq;
pq.push(Node(s, 0, dist[s]));
if (dist[s] == INF) { // 不可达
return -1;
}
int cnt = 0;
while (!pq.empty()) {
Node u = pq.top();
pq.pop();
if (u.u == t) {
++cnt;
}
if (u.u == t && cnt == k) {
return u.g;
}
int p = head[u.u];
while (p >= 0) {
pq.push(Node(edge[p].to, u.g + edge[p].value, dist[edge[p].to]));
p = edge[p].next;
}
}
return -1;
}
下边是poj 2449和uvaoj10740的题目代码,求最短路使用了多种方法,就是裸的求第k短路:
/************************************************************************* > File Name: 2449.cpp > Author: gwq > Mail: gwq5210@qq.com > Created Time: 2015年08月28日 星期五 09时53分34秒 ************************************************************************/
#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>
#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())
using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef pair<int, int> pii;
typedef long long ll;
const double esp = 1e-5;
#define N 1010
#define M 100010
int n, m, s, t, k;
int head[N], ecnt, rhead[N], recnt, dist[N], c[N];
bool vis[N];
struct Edge {
int from, to, value, next;
}edge[M], redge[M];
struct Node {
int u, g, h;
Node() {}
Node(int uu, int gg, int hh): u(uu), g(gg), h(hh) {}
};
bool operator <(Node u, Node v)
{
return u.g + u.h > v.g + v.h;
}
void addEdge(int u, int v, int x)
{
edge[ecnt].from = u;
edge[ecnt].to = v;
edge[ecnt].value = x;
edge[ecnt].next = head[u];
head[u] = ecnt++;
}
void addREdge(int u, int v, int x)
{
redge[recnt].from = u;
redge[recnt].to = v;
redge[recnt].value = x;
redge[recnt].next = rhead[u];
rhead[u] = recnt++;
}
void dijkstra(int start)
{
clr(vis, 0);
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
for (int i = 1; i <= n; ++i) {
int x = 0;
int mnx = INF;
for (int j = 1; j <= n; ++j) {
if (!vis[j] && mnx >= dist[j]) { // 保证可以选一个点
x = j;
mnx = dist[j];
}
}
vis[x] = true;
int p = rhead[x];
while (p >= 0) {
int v = redge[p].to;
int w = redge[p].value;
dist[v] = min(dist[x] + w, dist[v]);
p = redge[p].next;
}
}
}
void heap_dijkstra(int start)
{
clr(vis, 0);
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
priority_queue<Node> pq;
pq.push(Node(start, 0, 0));
while (!pq.empty()) {
Node x = pq.top();
pq.pop();
if (vis[x.u]) {
continue;
}
vis[x.u] = true;
int p = rhead[x.u];
while (p >= 0) {
int a = redge[p].from;
int b = redge[p].to;
int w = redge[p].value;
if (dist[b] > dist[a] + w) {
dist[b] = dist[a] + w;
pq.push(Node(b, dist[b], 0));
}
p = redge[p].next;
}
}
}
void bellman_ford(int start)
{
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < m; ++j) {
int u = redge[j].from;
int v = redge[j].to;
if (dist[u] < INF) {
dist[v] = min(dist[v], dist[u] + redge[j].value);
}
}
}
}
void spfa(int start)
{
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
}
dist[start] = 0;
clr(c, 0);
clr(vis, 0);
queue<int> q;
q.push(start);
vis[start] = true;
while (!q.empty()) {
int x = q.front();
q.pop();
int p = rhead[x];
vis[x] = false;;
while (p >= 0) {
int u = redge[p].from;
int v = redge[p].to;
int w = redge[p].value;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!vis[v]) {
vis[v] = true;
++c[v];
if (c[v] >= n) {
return;
}
q.push(v);
}
}
p = redge[p].next;
}
}
}
int bfs(void)
{
priority_queue<Node> pq;
pq.push(Node(s, 0, dist[s]));
if (dist[s] == INF) { // 不可达
return -1;
}
int cnt = 0;
while (!pq.empty()) {
Node u = pq.top();
pq.pop();
if (u.u == t) {
++cnt;
}
if (u.u == t && cnt == k) {
return u.g;
}
int p = head[u.u];
while (p >= 0) {
pq.push(Node(edge[p].to, u.g + edge[p].value, dist[edge[p].to]));
p = edge[p].next;
}
}
return -1;
}
int main(int argc, char *argv[])
{
while (scanf("%d%d", &n, &m) != EOF) {
int u, v, x;
clr(head, -1);
clr(rhead, -1);
ecnt = 0;
recnt = 0;
for (int i = 0; i < m; ++i) {
scanf("%d%d%d", &u, &v, &x);
addEdge(u, v, x);
addREdge(v, u, x);
}
scanf("%d%d%d", &s, &t, &k);
if (s == t) {
++k;
}
//dijkstra(t);
//bellman_ford(t);
//spfa(t);
heap_dijkstra(t);
printf("%d\n", bfs());
}
return 0;
}