注:本文总结自《算法笔记》
介绍
“迪杰斯特拉算法”,解决单源最短路问题,即给定图G和起点s,计算s到达其他每个顶点的最短距离的问题。
基本思想:对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S(V减S)中选择与起点s距离最小的一个顶点(记为u),访问并加入集合S,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。这样操作n次,直到集合S包含所有顶点。
具体实现
伪码
Dijkstra(G,d[],s) { //d表示从起点到各点的最短路径的长度,s为起点
初始化;
for(循环n次) {
u=使d[u]最小的还未被访问的顶点的标号;
记u已被访问;
for(从u出发能到达的所有顶点v) {
if(v未被访问&&以u为中介点使s到顶点v的最短距离更优) {
优化d[v];
令v的前驱为u;
}
}
}
}
邻接矩阵实现
适用于点数不大(一般不超过1000)的情况。其中,邻接矩阵G存放两点间的距离。
const int MAXV = 1000;
const int INF = 1000000000;
int n, G[MAXV][MAXV], d[MAXV]; //d表示从起点到各点的最短路径的长度
int pre[MAXV]; //pre[v]表示从起点到顶点v的最短路径上v的前一个顶点
bool vis[MAXV] = { false };
void Dijkstra(int s) {
fill(d, d + MAXV, INF);
for(int i=0;i<n;i++) pre[i]=i;//初始化状态设每个点的前驱为自身
d[s] = 0;
for (int i = 0; i < n; i++) { //循环n次
int u = -1, MIN = INF;
for (int j = 0; j < n; j++) { //找到未访问顶点中d[]最小的
if (vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if (u == -1) return; //剩下的顶点与起点s不连通
vis[u] = true;
for (int v = 0; v < n; v++) {
if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v]=u; //记录v的前驱为u
}
}
}
}
//递归访问最短路径
void DFS(int s,int v){ //s为起点编号,v为当前访问的顶点编号
if(v==s){
printf("%d\n",s);
return;
}
DFS(s,pre[v]);
printf("%d\n",v);
}
加入其它条件
题目一般不会考得这么“裸”,更多时候会加入其它条件。
对于新增条件,只需要增加一个数组存放新增的边权或点权或最短路径条数,且只需要修改Dijkstra函数中优化d[v]的那一个步骤。
这种新增条件一般作为第二标尺。
新增边权
以新增的边权代表花费为例,cost[u][v]代表u->v的花费。
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
c[v]=c[u]+cost[u][v]; //c[v]表示从起点s到v的最小花费
}else if(d[u]+G[u][v]==d[v]&&c[u]+cost[u][v]<c[v]){
c[v]=c[u]+cosy[u][v];
}
}
}
新增点权
以新增点权代表城市中能收集到的物资为例,weight[u]表示城市u中的物资数目。
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
w[v]=w[u]+weight[v]; //w[v]表示从起点s到v收集到的最大物资数
}else if(d[u]+G[u][v]==d[v]&&w[u]+weight[v]>w[v]){
w[v]=w[u]+weight[v];
}
}
}
求最短路径条数
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
num[v]=num[u]; //从起点s到v的最短路径条数
}else if(d[u]+G[u][v]==d[v]){
num[v]+=num[u]; //最短距离相同时累加
}
}
}
Dijkstra+DFS
对于优化条件比较复杂,比如需要在Dijkstra中根据路径长度来优化,或者不满足最优子结构。这时先用Dijkstra算法求出所有路径最短的路径,再DFS算法结合第二、三标尺判断这些路径中最优路径(还可以统计路径最短的路径数量)。
最优子结构:即通过递推可以将问题的规模缩小但不影响最终结果的结构,有点像贪心算法的感觉。
模板
注意:
(1)一般不管什么问题Dijkstra代码都可以不改动,只需要根据判断最优的条件更改DFS函数即可。
(2)temppath中从第一个到最后一个存放的是从终点到起点。
(3)DFS传入参数为终点下标。
vector<int> pre[maxn] //Dijkstra得到的所有路径(每个点的前驱)
void Dijkstra(int s) {
fill(d, d + maxn, 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 v = 0; v < n; v++) {
if (vis[v] == false && G[u][v] != inf) {
if (d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v].clear(); //注意要clear
pre[v].push_back(u);
}
else if (d[u] + G[u][v] == d[v]) {
pre[v].push_back(u);
}
}
}
}
}
int optvalue, int num; //最优值,最短路径的数量
vector<int> path, tempath; //最优路径,临时路径
void dfs(int v) {
tempath.push_back(v);
//递归边界
if (v == s) {
num++; //到达起点则数量+1
int value;
计算路径tempath上的value值
if (value优于optvalue) {
optvalue = value;
path = tempath;
}
tempath.pop_back();
return;
}
//递归式
for (int i = 0; i < pre[v].size(); i++) {
dfs(pre[v][i]);
}
tempath.pop_back();
}