CSP-迪杰斯特拉算法和变形求解最短路问题
知识简述
迪杰斯特拉(Dijkstra)算法,是一种求解正权边单源最短路的常用算法,稍有了解的人都比较熟悉,这里稍作简述Dij算法的实现过程。
1、首先我们要明确在进行迪杰斯特拉算法的过程中要维护的数据:一个小根堆用来存储当前已经更新过的点,一个dis[n]数组用来存储源点s到其他点的最短距离,一个vis[n]记录每个点是否被更新过。
2、现在开始算法的具体步骤:将dis[n]全部置为inf(极大值),dis[s]=0;将s点放入空的小根堆,并置vis[s]=1。
3、每次从小根堆中取出根元素a(最小值),并遍历该点的邻接边,如果对某个点y和边w(a,y),有dis[y]>dis[a]+w(a,y),则说明该点可以更新,一般在迪杰斯特拉算法中将更新的操作成为松弛,某个点被松弛要完成两个操作:更新dis[y]和将y加入小根堆中。
4、重复操作3,直至小根堆为空。遍历dis[n]数组,其值即为最短路距离,如果有点其dis[]值仍为inf,说明该点不可达。
迪杰斯特拉的算法特点:由于所有边均为正边,由此有如果堆先后弹出了a和b点,则一定有dis[a]<=dis[b],因此不可能存在dis[b]+正边<dis[a]。这个特点可以总结为Dij算法最显著的特征:所有点只会被弹出一次,且只要某个点被弹出,说明到该点的距离已经是最小值。
具体实现:在使用Dij过程中,用到了小根堆数据结构,但考虑到小根堆的初始化比较复杂,可以使用大根堆,将(-dis[i])传入大根堆即可起到与小根堆相同的效果。
拓展:单源正值最长路Dij做法
Dij由于其松弛的性质,也可以用来计算单源最长路,但有几个tips需要注意:
1、计算最长路,松弛操作应该变为dis[y]<dis[a]+w(a,y),且存储点集使用大根堆
2、在最长路的计算时,已经不再满足只要某个点被弹出,说明到该点的距离已经是最大值这条性质,每个点可以被多次弹出。
3、由于在计算最长路时,每个点会被多次弹出,这时有可能出现无意义的更新,为了提高Dij的性能,在出现堆顶x距离<dis[x],说明该点已经被其他更新过,不是最长路了,因此更新无意义,可以直接剪枝去除。
Dijkstra基本实现(C++)
const int inf=1e8;
const int M=1e6;
const int N=1e5;
struct Edge
{
int to,nxt,value;
}e[M];
int head[N];
int tot=1;
int dis[N];
int n;//点数
int m;//边数
void init(int num)
{
for(int i=1;i<=num;i++)
head[i]=-1;
}
void add(int x,int y,int value)
{
tot++;
e[tot].to=y;
e[tot].value=value;
e[tot].nxt=head[x];
head[x]=tot;
}
priority_queue<pair<int,int>> q;
void dijkstra(int s)
{
while(q.size()) q.pop();
//初始化
for(int i=1;i<=n;I++)
vis[i]=0,dis[i]=inf;
dis[s]=0;
q.push(make_pair(0,s));
//初始化堆和访问数组
while(q.size())
{
int x=q.top().second;
q.pop();
//每个点只能被弹出一次
//在dijkstra的最