最短路問題彙總
author: jzh
date: 2021/9/20
site: https://akynazh.site
之前寫的幾個有點亂而且有些地方寫的不準确,這裡借着CSP比賽複習重新整理一下。
注意,這裡為了友善描述算法,是以都用了最易了解的鄰接矩陣來寫,比賽中為了追求效率,一般将鄰接矩陣改為鍊式前向星或者鄰接表。
迪傑斯特拉算法 O(V^2)
const int MAXN = 100;
const int INF = 0x3f3f3f3f;
// 有向無環圖 DAG
int V, E; // 頂點數和邊數
int graph[MAXN][MAXN]; // DAG鄰接矩陣,初始值為INF,不可達為INF,否則為cost值
int d[MAXN]; // 從某點s出發到其它任意結點的最短路徑長度,初始值為INF
int visited[MAXN]; // 某點是否通路過,通路過則為1否則為0
// 初始化圖
void init() {
memset(graph, 0x3f, sizeof(graph));
cin >> V >> E;
int from, to, cost;
for (int i = 0 ; i < E; i++) {
cin >> from >> to >> cost;
graph[from][to] = cost;
}
}
// 迪傑斯特拉算法求解最短路,針對點展開
void Dijkstra(int s) {
memset(d, 0x3f, sizeof(d));
memset(visited, 0, sizeof(visited));
visited[s] = 1;
for(int i = 0; i < V; i++) d[i] = graph[s][i];
d[s] = 0;
int k, min_cost;
// 無負邊時最多更新n-1(其他結點數)次
for(int i = 0; i < V - 1; i++){
min_cost = INF;
// 尋找最未被通路的且權值最小的路徑,需要優化的地方
for(int j = 0; j < V; j++){
if(!visited[j] && d[j] < min_cost){
k = j;
min_cost = d[j];
}
}
visited[k] = 1;
// 利用找到的結點更新最短路徑
for(int j = 0; j < V; j++){
if(!visited[j] && min_cost + graph[k][j] < d[j]){
d[j] = min_cost + graph[k][j];
}
}
}
}
迪傑斯特拉算法的堆優化 O(ElogV) 不含負權就用它 ~
const int MAXN = 100;
const int INF = 0x3f3f3f3f;
// 有向無環圖 DAG
int V, E; // 頂點數和邊數
int graph[MAXN][MAXN]; // DAG鄰接矩陣,初始值為INF,不可達為INF,否則為cost值
int d[MAXN]; // 從某點s出發到其它任意結點的最短路徑長度,初始值為INF
int visited[MAXN]; // 某點是否通路過,通路過則為1否則為0
typedef pair<int, int> P; // first: 最短距離 second:通往的頂點編号
priority_queue<P, vector<P>, greater<P> > que; // greater<T> 從小到大取出
// 初始化圖
void init() {
memset(graph, 0x3f, sizeof(graph));
cin >> V >> E;
int from, to, cost;
for (int i = 0 ; i < E; i++) {
cin >> from >> to >> cost;
graph[from][to] = cost;
}
}
// 迪傑斯特拉算法堆優化求解最短路,針對點展開
void Dijkstra_optimise(int s) {
memset(d, 0x3f, sizeof(d));
memset(visited, 0, sizeof(visited));
// for(int i = 0; i < V; i++) d[i] = graph[s][i];
// 開始min_cost為0,若加上這一步,0 + graph[k][i] == d[i] == graph[k][i],導緻無法更新隊列
d[s] = 0;
int k, min_cost;
que.push(P(0, s));
while (!que.empty()) {
P p = que.top(); que.pop();
// 通過堆優化直接獲得了目标
min_cost = p.first;
k = p.second;
// 若已經有更短的路徑則不更新
if (min_cost > d[k]) continue;
visited[k] = 1;
for (int i = 0; i < V; i++) {
if (!visited[i] && min_cost + graph[k][i] < d[i]) {
d[i] = graph[k][i] + min_cost;
que.push(P(d[i], i));
}
}
}
}
貝爾曼福德算法 O(VE)
using namespace std;
const int MAXN = 100;
const int MAXE = 100;
const int INF = 0x3f3f3f3f;
// 有向無環圖 DAG
struct Edge {
int from, to, cost;
};
int V, E; // 頂點數和邊數
int d[MAXN]; // 從某點s出發到其它任意結點的最短路徑長度,初始值為INF
Edge edge[MAXE]; // 邊集
// 初始化圖
void init() {
cin >> V >> E;
int from, to, cost;
for (int i = 0 ; i < E; i++)
cin >> edge[i].from >> edge[i].to >> edge[i].cost;
}
// 貝爾曼福德算法求解最短路,針對邊展開
void Bellman_Ford(int s)
{
memset(d, 0x3f, sizeof(d));
d[s] = 0;
while (1) {
int flag = 0;
// DAG下最少更新E次
for (int i = 0; i < E; i++) {
Edge e = edge[i];
if (d[e.from] != INF && d[e.to] > d[e.from] + e.cost) {
d[e.to] = d[e.from] + e.cost;
flag = 1;
}
}
// 如果沒出現更新則退出
if (!flag) break;
}
}
貝爾曼福德算法優化(SPFA + 優先隊列)含負邊就用它~
雖然大家都說SPFA已死,但其實是說笑罷了,含負權邊或者負環的題還真離不開這東西~
const int MAXN = 100;
const int INF = 0x3f3f3f3f;
// 有向無環圖 DAG
int V, E; // 頂點數和邊數
int graph[MAXN][MAXN]; // DAG鄰接矩陣,初始值為INF,不可達為INF,否則為cost值
int d[MAXN]; // 從某點s出發到其它任意結點的最短路徑長度,初始值為INF
int vis[MAXN]; // 結點是否已入隊
int push_count[MAXN]; // 結點入隊次數,若大于等于V,則證明存在負環
priority_queue<int, vector<int>, greater<int> > que; // greater<T> 從小到大取出
// 初始化圖
void init() {
memset(graph, 0x3f, sizeof(graph));
cin >> V >> E;
int from, to, cost;
for (int i = 0 ; i < E; i++) {
cin >> from >> to >> cost;
graph[from][to] = cost;
}
}
// SPFA算法求解最短路
void SPFA(int s) {
memset(d, 0x3f, sizeof(d));
memset(vis, 0, sizeof(vis));
memset(push_count, 0, sizeof(push_count));
d[s] = 0;
que.push(s);
vis[s] = 1;
push_count[s]++;
while (!que.empty()) {
int flag = 0; // 負環判斷
int v = que.top(); que.pop(); vis[v] = 0;
for (int i = 0; i < V; i++) {
if (d[v] + graph[v][i] < d[i]) {
d[i] = d[v] + graph[v][i];
if (!vis[i]) {
que.push(i);
vis[i] = 1;
push_count[i]++;
if (push_count[i] >= V) {
flag = 1;
break;
}
}
}
}
if (flag) {
cout << "有負環" << endl;
break;
}
}
}
弗洛伊德算法 O(V^3) 多源問題可以考慮用它~
const int MAXN = 100;
const int INF = 0x3f3f3f3f;
// 有向無環圖 DAG
int V, E; // 頂點數和邊數
int graph[MAXN][MAXN]; // DAG鄰接矩陣,初始值為INF,不可達為INF,否則為cost值
int d[MAXN][MAXN]; // 從任意結點出發到其它任意結點的最短路徑長度,初始值為INF
// 初始化圖
void init() {
memset(graph, 0x3f, sizeof(graph));
cin >> V >> E;
int from, to, cost;
for (int i = 0 ; i < E; i++) {
cin >> from >> to >> cost;
graph[from][to] = cost;
}
}
// 弗洛伊德算法求解最短路
void Floyd()
{
// 初始化矩陣d
memset(d, 0x3f, sizeof(d));
for(int i = 0; i < V; i++)
for(int j = 0; j < V; j++)
d[i][j] = graph[i][j];
for(int k = 0; k < V; k++){//以頂點k為中轉
for(int i = 0; i < V; i++){//從頂點i到頂點j
for(int j = 0; j < V; j++){
if (d[i][j] > d[i][k] + d[k][j])
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
附:
鍊式前向星
const int MAXN = 100;
struct Edge {
int to; // 邊的終點
int next; // 與該邊同起點的下一條邊的編号
int w; // 邊的權值
};
Edge edge[MAXN]; // 所有邊的集合
int head[MAXN]; // 所有起點的第一條邊的編号,初始化為-1
int V, E;
// 建立圖
void build() {
int from, to, w, cnt = 0;
for (int i = 0; i < E; i++) {
cin >> from >> to >> w;
edge[cnt].to = to;
edge[cnt].w = w;
edge[cnt].next = head[from];
// 是以可将head初始化為-1,當next為-1時代表以該點為起點的邊通路結束
head[from] = cnt++;
}
}
// 周遊整個圖
void traverse() {
for (int i = 0; i < V; i++) {
for (int j = head[i]; j != -1; j = edge[j].next) {
cout << " from " << i << " to " << edge[j].to << " weight: " << edge[j].w << endl;
}
}
}
// 周遊某個結點的鄰接點
void traverse_v(int v) {
for (int i = head[v]; i != -1; i = edge[i].next) {
cout << " from " << v << " to " << edge[i].to << " weight: " << edge[i].w << endl;
}
}
over~
author: jzh
date: 2021/9/20
site: https://akynazh.site