1. 程式人生 > 其它 >100天程式碼提升計劃-第14天

100天程式碼提升計劃-第14天

單源最短路

正權邊

Dijkstra演算法 O(n^2)

每次通過已知最短距離來更新到其他點的最短路
注意出現重邊要進行比較

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int g[N][N];//鄰接矩陣
int dist[N];//源點到其他點的距離
bool st[N];//判斷是否被認定為已經是最短
// 求1號點到n號點的最短路,如果不存在則返回-1
int dijkstra()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	for(int i = 0; i < n - 1; i ++ )
	{
		int t = -1;// 在還未確定最短路的點中,尋找距離最小的點
		for(int j = 1; j <= n; j ++ )
			if(!st[j] && (t == -1 || dist[j] < dist[t]))
				t = j;
		// 用t更新其他點的距離
		for(int j = 1; j <= n; j ++ )
			dist[j] = min(dist[j], dist[t] + g[t][j]);
		st[t] = true;
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

堆優化的Dijkstra演算法 O(mlogn)

如果是稀疏圖,點數量多,遍歷的時候就會超時,所以用鄰接表儲存,每次取最短距離的步驟可以用最小堆優化

#include <iostream>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n;
int h[N], w[N], ne[N], e[N], idx;
int dist[N];
int st[N];

int Dijkstra() {
	memset(h, -1, sizeof h);
	dist[1] = 0;
	priority_queue<PII, vector<PII>, greater<PII>> heap;
	heap.push({0, 1});
	while (heap.size()) {
		PII t = heap.top();
		heap.pop();

		int ver = t.second, distance = t.first;
		if (st[ver])
			continue;
		st[ver] = true;

		for (int i = h[ver]; i != -1; i = ne[i]) {
			int j = e[i];
			if (dist[j] > distance + w[i]) {
				dist[j] = distance + w[i];
				heap.push({dist[j], j});
			}
		}
	}
	if (dist[n] == 0x3f3f3f3f)
		return -1;
	return dist[n];
}

int main() {
	return 0;
}

負權邊

Bellman-ford演算法 O(nm)

Dijkstra演算法無法處理有負權邊的圖
一層一層的去更新
有限步數的條件下只能用Bellman-ford演算法

int n, m;       // n表示點數,m表示邊數
int dist[N];        // dist[x]儲存1到x的最短路距離

struct Edge     // 邊,a表示出點,b表示入點,w表示邊的權重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距離,如果無法從1走到n,則返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然會鬆弛三角不等式,就說明存在一條長度是n+1的最短路徑,由抽屜原理,路徑中至少存在兩個相同的點,說明圖中存在負權迴路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)//從源點開始更新,每次更新一層(這是因為正無窮更新之後還是正無窮,所以可以一層一層更新)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

spfa演算法 O(m)

佇列優化的Bellman_ford演算法
可以用來判斷是否有負權迴路,需要時只需要加一個cnt陣列來記錄點數,如果點數>n就說明存在負權環

int n;      // 總點數
int h[N], w[N], e[N], ne[N], idx;       // 鄰接表儲存所有邊
int dist[N];        // 儲存每個點到1號點的最短距離
bool st[N];     // 儲存每個點是否在佇列中

// 求1號點到n號點的最短路距離,如果從1號點無法走到n號點則返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果佇列中已存在j,則不需要將j重複插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

多源匯最短路

Floyd演算法

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 演算法結束後,d[a][b]表示a到b的最短距離
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}