如果,上面的图,如果用dij算法,那么dist[4] = 4, 是得不到正确的结果的, 这个因为dist[3]先被确定是最小,然后用来更新dist[4]
但是存在负权,使得dist[3]更小,但是我们已经把结点3标记为不可用了(vis[3] = true), 所以存在错误
如何使得使得结点3可用呢? 我们把判断的条件给改一下,如果结点u出队列之后,其权值为dist[u] 来得小, 那么就可以用它来更新其他的定点
这样子,每个结点都可以多次入队列, 使得dij可以处理负权
1 int dij(int x, int y, int n) 2 { 3 for (int i = 1; i <= n; ++i) 4 dist[i] = INF; 5 priority_queue<Edge> q; 6 Edge cur, tmp; 7 cur.dist = dist[x] = 0; 8 cur.v = x; 9 q.push(cur); 10 while (!q.empty()) 11 { 12 cur = q.top(); q.pop(); 13 int u = cur.v; 14 if (dist[u] < cur.dist)//如果cur.dist < dist[u], 那么可以继续更新其他顶点, 代替了条件vis[u] 15 continue; 16 for (int i = 0; i < g[u].size(); ++i) 17 { 18 int v = g[u][i].v; 19 if (dist[v] > dist[u] + g[u][i].dist) 20 { 21 tmp.dist = dist[v] = dist[u]+ g[u][i].dist; 22 tmp.v = v; 23 q.push(tmp); 24 } 25 } 26 } 27 return dist[y]; 28 }
bellaman__ford算法的无用操作
void relax(int u, int v,double weight) {if(dist[v] > dist[u] + weight)dist[v] = dist[u] + weight; } bool bellman_ford(int n, int m) {int i,j;for(i=0; i<n-1; ++i)//n-1循环for(j=0; j<m; ++j)//枚举所有的边去松弛最短路径 {relax(g[j].u,g[j].v,g[j].weight);}bool flag = false;for(i=0; i<m; ++i)if(dist[g[i].v] > dist[g[i].u] + g[i].weight){flag = true;break;}return flag; }
如上述代码所示, 我们枚举每条边, 看看能不能松弛最短路径, 但这么做,其实做了很多的无用功, 比如说如果 dist[g[j]] 没有被松弛过或者松弛不成功,
那么relax(g[j].u,g[j].v,g[j].weight) 做的就是无用功
对于这个, 我们可以用队列来优化bellman算法, (上交的大神发明的,时间复杂度是O(ke),k是常数,虽然后来被证实它证明的k是常数是错误的)
如果结点u被更新过了, 那么才可以用它来更新它所指向的定点
int spfa(int x, int y, int n) {for (int i = 1; i <= n; ++i)dist[i] = INF;queue<int> q;q.push(x);dist[x] = 0;while (!q.empty()){int u = q.front(); q.pop();vis[u] = false;for (int i = 0; i < g[u].size(); ++i){int v = g[u][i].v;if (dist[v] > dist[u] + g[u][i].dist){dist[v] = dist[u] + g[u][i].dist;//能更新就更新if (!vis[v])//如果结点在队里里面,就不用重复入队了 {q.push(v);vis[v] = true;}}}}return dist[y]; }