一. Djikstra算法定义
形式:
用来解决单源最短路径的问题,即给出图G和起点s,通过算法到达每个顶点的最短距离。基本思想:
对图G(V, E)设置集合S, 存放已被访问的顶点,然后每次从集合V-S中选择与起点s的最短距离最小的一个顶点u,访问并加入集合S。之后,令顶点u为中介点, 优化起点和所有的从u能到达的顶点v之间的最短距离。这样的操作执行n(顶点的个数)次。伪代码:
//G为图, 一般设置为全局变量,数组d为源点到达各点的最短路径长度,s为起点 Djikstra(G, d[], s){ 初始化 for(循环n次){ u = 是d[u]最小的还未被访问的顶点的标号 记u已被访问 for(从u出发能到达的所有顶点v){ if(v未被访问&&以U为中介使得s到顶点v的最短距离d[v]更优){ 优化d[v] } } } }
二、具体实现
1. 邻接矩阵版
const int MAXV = 1000;//最大顶点数 const int INF = 10000000000;//设INF为一个很大数 //适用于点数不大的情况 int n, G[MAXV][MAXV]; int d[MAXV]; bool vis[MAXV]; void Dijkstra(int s){ fill(d, d+MAXV, INF); d[s] = 0; for(int i = 0; i < n; i++){ int u = -1. MIN = INF;//u使d[u]最小, MIN存放该最小d[u] for(int j = 0; j < n; j++){ if(vis[j] == false && d[j] < MIN){ u = j; MIN = d[j]; } } //找不到小于INF的d[u],说明剩下的顶点与s不连通 if(u == -1) return; vis[u] = true;//标记u为已访问 for(int v = 0; v < n; v++){ //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优 if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v];//优化d[v] } } } }
2. 邻接表版
struct node{ int v, dis;//v为边的目标顶点,dis为边权 }; vector<node> Adj[MAXV]; int n; int d[MAXn]; bool vis[MAXV] = {false}; void Dijkstra(int s){ fill(d, d+MAXV, INF); d[s] = 0; for(int i = 0; i < n; i++){ int u = -1, MIN = INF; for(int j = 0; j < n; j++){ if(vis[j] == false && d[j] < MIN){ u = j; MIN = d[j]; } } if(u == -1) return; vis[u] = true; //只有这个部分与邻接矩阵自而发不同 for(int j = 0; j < Adj[u].size(); j++){ int v = Adj[u][j].v//通过邻接表直接获得u能够到达的v if(vis[v] == false && d[u] + Adj[u][j].dis < d[v]){ d[v] = d[u] + Adj[u][j].dis; } } } }
3. 最短路径的求法(在上面的基础上)
- 如果题目中给出的是无向图,那么根据邻接表和邻接矩阵自行改变即可,如果是邻接矩阵可以在两边同时加上一样的权值;如果是邻接表那么使用push_back,也是同时交换输入。
<1>以邻接矩阵为例
const int MAXV = 1000;//最大顶点数 const int INF = 10000000000;//设INF为一个很大数 //适用于点数不大的情况 int n, G[MAXV][MAXV]; int d[MAXV]; bool vis[MAXV]; int pre[MAXV];//表示从起点到顶点v的最短路径上v的前一个顶点(新添加) void Dijkstra(int s){ fill(d, d+MAXV, INF); d[s] = 0; for(int i = 0; i < n; i++){ int u = -1. MIN = INF;//u使d[u]最小, MIN存放该最小d[u] for(int j = 0; j < n; j++){ if(vis[j] == false && d[j] < MIN){ u = j; MIN = d[j]; } } //找不到小于INF的d[u],说明剩下的顶点与s不连通 if(u == -1) return; vis[u] = true;//标记u为已访问 for(int v = 0; v < n; v++){ //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优 if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v];//优化d[v] //就是在这里改变,添加了一条语句 pre[v] = u;//新添加 } } } } //如何打印,就是递归打印 void dfs(int s, int v){ if(v == s){ printf("%d\n", s); return; } dfs(s, pre[v]); printf("%d\n", v); }
4. 会出现的其他情况,即附加条件
- 即出现除了第一个条件最短路径外,可能还会有其他的条件限制,如有多条路径
- 每条边增加一个边权(花费);
- 每个点增加一个点权(如每个城市可以收到的物资);
- 直接问有多少条最短路径。
<1>新增边权。以新增边权花费为例,都是在最后判断出进行修改,其余均不需改动
for(int v = 0; v < n; v++){ //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优 if(vis[v] == false && G[u][v] != INF){ if(d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v];//优化d[v] c[v] = c[u] + cost[u][v]; }else if(d[u] + G[u][v] == d[v] && c[u] + cost[u][v] < c[v]){ c[v] = c[u] + cost[u][v]; } } }
<2>新增点权,以新增的点权代表城市中能收集到的物资为例,用weight[u]表示城市u中物资数目,并增加一个数组w[],令其为起点s到到达顶点u可以收集的最大物资w[u].
for(int v = 0; v < n; v++){ //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优 if(vis[v] == false && G[u][v] != INF){ if(d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v];//优化d[v] w[v] = w[u] + weight[v]; }else if(d[u] + G[u][v] == d[v] && w[u] + weight[v] > w[v]){ w[v] = w[u] + weight[v]; } } }
<3>求最短路径的条数。只需要增加一个数组num[],初始时num[s] = 1,其余都为num[u] = 0.
for(int v = 0; v < n; v++){ //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优 if(vis[v] == false && G[u][v] != INF){ if(d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v];//优化d[v] num[v] = num[u]; }else if(d[u] + G[u][v] == d[v]){ num[v] += num[u]; } } }
来源:https://www.cnblogs.com/tsruixi/p/12380430.html