dijkstra由于是贪心的,每次都找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短路径。但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点(dmin'),再通过这个负权边L(L<0),使得路径之和更小(dmin'+L<dmin),则dmin'+L成为最短路径,并不是dmin,这样dijkstra就被囧掉了。
比如n=3,邻接矩阵:
0,3,4
3,0,-2
4,-2,0
用dijkstra求得d[1,2]=3,事实上d[1,2]=2,就是通过了1-3-2使得路径减小。
寻找单源最短路,且边权为正(SSSP)(一个源点到各个顶点)
每次找到离源点最近的一个顶点(除了已经被标记过之外的),然后以该顶点为中心进行扩展,最终得到远点到其余所有店的最短路径。
for(int i = 1; i < n; i++){//n-1次
min = inf;
for(int j = 1; j <= n; j++){
if(book[j] == 0 && dis[j] < min){
min = dis[j];//dis保存距离(离源点)
u = j;//u为离源点最近的顶点
}
book[u] = 1;//标记,走过的点不会再是后面点的中转
for(int k = 1; k <= n; k++){
if(dis[k] < inf){//概念上防止无穷与无穷相比较
if(dis[k] > dis[u] + e[u][k])//如果遍历到的点原来的路程大于中转路程
dis[k] = dis[u] + e[u][k];//将该点原来的路程更新
}
}
}
}
两版的区别在于是否先为d数组赋初值
d保存最短路径(源点到各个点各个点)
v保存标记
const int INF = 999999999;
int main(){
memset( v , 0 , sizeof(v)); //初始化标记数组
for(int i = 0; i < n; i++) d[i] = (i == 0? 0 : INF); //初始化路径大小,设源点到其他点的路长均为INF
for(int i = 0; i < n; i++){//一次循环找到一个点
int x, m = INF; // 声明x,为m设置INF初始值
for(int y = 0; y < n ; y++ ) //在所有未标记的节点中
if(!v[y] && d[y] <= m) m = d[x=y]; //找到离源点最近的点
v[x] = 1; //标记走过的点
for(int y = 0; y < n; y++) //对于从x出发的所有边(x,y)
d[y] = min(d[y] , d[x] + w[x][y]); //更新源点到该点的距离
}
return 0;
}
对于这个算法,一开始理解得很模糊,后来又以为是按照一维一维的顺序一直搜索下去,但实际上算法很简单,只要按照d的大小进行遍历就可以,并不存在其他的因素,最外层循环仅仅确定层数,并不作为索引
下面是用优先队列优化后的算法:
const int INF = 0x3f3f3f3f;
const int MAXN=1000010;
struct qnode //优先队列的创建(储存节点v以及到源点的距离C)
{
int v;//节点v
int c;//源点的距离(用来排序)
qnode(int _v=0, int _c=0):v(_v),c(_c){} //初始化为0???
bool operator<(const qnode &r)const//定义优先队列的排序方式(实际上可以用pair进行替代),先比较第一维,相等时比较第二维
{
return c>r.c;
}
};
struct Edge//边结构
{
int v,cost; //储存边以及权
Edge(int _v=0,int _cost=0):v(_v),cost(_cost){}//初始化???
};
vector<Edge>E[MAXN];//看作二维数组,每个i数组储存着从i边到下一边的所有数据
bool vis[MAXN];//标记数组
int dist[MAXN];//离源点的距离
void Dijkstra(int n, int strat) //点的编号从1开始,n是点的个数,start是源点
{
memset(vis, false, sizeof(vis));//初始化标记数组
for(int i = 1; i <= n;i++)dist[i] = INF;//初始化距离数组
priority_queue<qnode>que;//创建定义的优先队列,类型为qnote
while(!que.empty()) que.pop();//清空队列(这里不明白为什么要清空,是赋了初值吗?那为什么要赋初值)
dist[start]=0;//源点到源点的距离初始化为0,以便于得出后续
que.push(qnode(start,0));//源点入队,距离为0
qnode tmp;//qnode类型的中转变量
while(!que.empty()){//只要队列不为空,也可以看作每次出队都是一轮松弛
tmp=que.top();//将队列中d最小的赋给tmp
que.pop();//出队
int u = tmp.v;//方便后续的操作,令u=该点的序号
if(vis[u])continue;//如果已被标记,那么跳过
vis[u]=true;//标记
for(int i=0;i<E[u].size();i++){//循环u的下一点的个数次
int v=E[tmp.v][i].v;//v=(点u数组中的下一点)
int cost=E[u][i].cost; //cost = (点u数组中到下一点的距离)
if(!vis[v]&&dist[v]>dist[u]+cost){ //松弛操作
dist[v]=dist[u]+cost;
que.push(qnode(v,dist[v]));//入队
}
}
}
}
void addedge(int u,int v,int w){
E[u].push_back(Edge(v,w));
}
Edge(int u, int v, int c, int f):from(u),to(v),cap(c),flow(f){}
这句话的作用是:直接可以使用Edge(1,2,3,4)这样的用法为结构体赋初值(构造函数)
网友评论