注:本文總結自《算法筆記》
介紹
“迪傑斯特拉算法”,解決單源最短路問題,即給定圖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() {
fill(d, d + maxn, inf);
d[0] = 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();
}