Dijkstra算法
Dijkstra算法是对图求单元最短路径问题的常用算法。
基本思想就是对图G(v,e)设一个集合S,存放已被访问的顶点,然后每次在剩余未被访问的结点中找到一个距离起点最近的结点,将此结点放入集合S,并以此结点出发向各个与此结点连接的未被访问的结点访问,看以此结点为中介结点到下一结点的距离是否比当前起点到下一结点的距离要短,如果更短,则以此结点为从起点到下一结点的中介点,修改之前到下一结点的路径选择,更新到下一结点的最短路径长度。
Dijkstra算法适用于边权都是非负数的情况,若边权有负数,那么就要用SPFA算法。这里留一个我总结SPFA算法的连接:
根据对图的存储方式有以下两种模板
邻接矩阵代码模板:(适合图的点数不超过1000的情况)
//全局变量部分
const int MAXN = 1000; //图的最大顶点数
const int INF = 1000000000; //设立INF为一个极大数,
//这里这个数也可以设置为0x3f3f3f3f,这代表无穷大,即1061109567这一数值
int n; //当前图的点数
int vis[MAXN]; //标记数组用于记录图中各个点的被访问情况,0为未访问,1为已访问
int dis[MAXN]; //记录起点到各个顶点的最短路径长度
int m[MAXN][MAXN]; //用一个邻接矩阵来记录图中各个点的连接情况
void Dijkstra(int s){ //s为起点
//
fill(vis, vis+MAXN, 0); //将标记数组初始化为0即未被访问状态,此处可用memset
fill(dis, dis+MAXN, INF); //将最短路数组初始化为一个很大的数,此处要注意不可用memset
dis[s]=0; //首先将起点s到达自身的最短路设为0
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
int node = -1; //node记录当前能找到的从起点到此没被访问的最短的一个点
int minn = INF; //minn记录到达那个点的最短路径
for(int j=0;j<n;j++){ //每个点逐步寻找没被访问的最短的一个点
if(vis[j]==0 && dis[j]<minn){
minn = dis[j];
node = j;
}
}
if(node==-1){ //找不到小于INF的一个点,说明其他结点与定点不连通
return ;
}
vis[node] = 1; //将当前点标记为1
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
//如果结点未被访问且 node能到达此结点 并且从起点到此结点过node中介点更近
//在此标尺只有距离,如果有两条距离相等或多条相等,那么就只需在此进行修改
//如路径相同第二标尺为花费时,则可通过在增加花费数组,在此if后加elseif判断距离相等情况花费不同条件。
dis[j] = dis[node]+m[node][j];
}
}
}
}
邻接表代码模板:
//全局变量部分
const int MAXN = 1000; //图的最大顶点数
const int INF = 1000000000; //设立INF为一个极大数,
//这里这个数也可以设置为0x3f3f3f3f,这代表无穷大,即1061109567这一数值
struct NODE{
int v, d;
};
vector <NODE> Adj[MAXN];//图的邻接表表示,Adj[node]存放顶点u出发可以达到的所有顶点
int n; //当前图的点数
int vis[MAXN]; //标记数组用于记录图中各个点的被访问情况,0为未访问,1为已访问
int dis[MAXN]; //记录起点到各个顶点的最短路径长度
void Dijkstra(int s){ //s为起点
fill(vis, vis+MAXN, 0); //将标记数组初始化为0即未被访问状态,此处可用memset
fill(dis, dis+MAXN, INF); //将最短路数组初始化为一个很大的数,此处要注意不可用memset
dis[s]=0; //首先将起点s到达自身的最短路设为0
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
int node = -1; //node记录当前能找到的从起点到此没被访问的最短的一个点
int minn = INF; //minn记录到达那个点的最短路径
for(int j=0;j<n;j++){ //每个点逐步寻找没被访问的最短的一个点
if(vis[j]==0 && dis[j]<minn){
minn = dis[j];
node = j;
}
}
if(node==-1){ //找不到小于INF的一个点,说明其他结点与定点不连通
return ;
}
vis[node] = 1; //将当前点标记为1
for(int j=0;j<Adj[node].size();j++){
int v = Adj[node][j].v;
if(vis[v]==0 && dis[node]+Adj[node][v].d<dis[v]){
dis[v] = dis[node]+Adj[node][v].d;
}
}
}
}
通过上面的代码模板,我们可以得到起点到每个结点的最短距离,那么如果要得到从起点到某一结点的完整最短路径选择,要怎么做呢?因为在天梯赛和pat比赛中n的数量往往不是很大,那么在这里仍主要以邻接矩阵的方式来总结。
const int MAXN = 1000;
const int INF = 1000000000;
int n;
int vis[MAXN];
int dis[MAXN];
int m[MAXN][MAXN];
int pre[MAXN]; //pre这个数组当前结点在从起点到当前结点的最短路径中的上一结点是谁,类似并查集中的father数组
void Dijkstra(int s){
for(int i=0;i<n;i++){ //对pre数组初始化,每个结点的前驱结点初始化为自身
pre[i] = i;
}
fill(vis, vis+MAXN, 0);
fill(dis, dis+MAXN, INF);
dis[s]=0;
for(int i=0;i<n;i++){
int node = -1;
int minn = INF;
for(int j=0;j<n;j++){
if(vis[j]==0 && dis[j]<minn){
minn = dis[j];
node = j;
}
}
if(node==-1){
return ;
}
vis[node] = 1;
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
dis[j] = dis[node]+m[node][j];
pre[j] = node;//node到当前结点才是最短的,所以将当前结点的前驱结点设置为node
}
}
}
}
//那么各个结点的前驱结点求解完毕后,我们要将最短路径进行输出
void DFS(int s, int e){ //e在一开始传进来的是终点,然后在不断递归过程中为当前结点
if(e==s){
cout<<s;
return;
}
DFS(s, pre[e]);
cout<<e<<" ";
}
到这最最基本的Dijkstra算法模板就展示完了,我觉得我的算法思想可能将得不清楚,但是我觉得我这个代码模板以及注释还挺清楚的,接下来就要讲更真实的部分,那么就是真正的题怎么可能就只是上面简单的部分!不得多加几个限制来搞点事,让你头晕晕!?耐心点接着看吧
- 给每条边再加一个边权(如花费问题),然后在最短路径有多条相等时让你选择一个另一个边权和最大或最小(如花费最少)的情况
- 给每个点加一个点权(如pta紧急救援问题中的得到救援队最多问题)在最短路径有多条相等时让你选择一个另点权和最大或最小的情况
- 问能有多少条最短路径
以下针对不同情况的代码都是在之前模板基础上的增加或修改,请仔细对比理解!!
- 新增边权情况(这里以最小花费为例)
int c[MAXN][MAXN]; //记得全部初始化为INF
int cost[MAXN];
void Dijkstra(int s){ //s为起点
***
***
fill(cost, cost+MAXN, INF);
***
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
***
***
***
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
dis[j] = dis[node]+m[node][j];
cost[j] = cost[node]+c[node][j];
}else if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])==dis[j]){
if(cost[node]+c[node][j]<cost[j]){
cost[j] = cost[node]+c[node][j]
}
}
}
}
}
- 新增点权情况(这里以最大物资为例)
int value[MAXN]; //记录各个点的物资
int v[MAXN]; //记录从起点到终点各个点可以收录的最大物资
void Dijkstra(int s){ //s为起点
***
***
fill(v, v+MAXN, -1);
***
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
***
***
***
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
dis[j] = dis[node]+m[node][j];
v[j] = v[node]+value[j];
}else if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])==dis[j]){
if(v[node]+value[j]>v[j]){
v[j] = v[node]+value[j];
}
}
}
}
}
- 最短路径条数求解
int num[MAXN];
void Dijkstra(int s){ //s为起点
***
***
***
for(int i=0;i<n;i++){ //n次循环,遍历完n个点
***
***
***
for(int j=0;j<n;j++){
if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
dis[j] = dis[node]+m[node][j];
num[j] = num[node];
}else if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])==dis[j]){
num[j] = num[j] + num[node];
}
}
}
}