1. 程式人生 > >資料結構(十四)最短路問題

資料結構(十四)最短路問題

最短路徑問題

1. 概述

1. 抽象

在網路(帶權圖)中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那一條路徑

  • 這條路徑就是兩點之間的最短路徑(ShorttestPath)
  • 第一個頂點為源點(Source)
  • 最後一個頂點為終點(Destination)

2. 分類

  • 單源最短路徑問題:從某固定源點出發,求其到所有其他頂點的最短路徑
    • (有向)無權圖
    • (有向)有權圖
  • 多源最短路徑問題:求任意兩頂點間的最短路徑

2. 無權圖的單源最短路演算法

按照遞增(非遞減)的順序找出到各個頂點的最短路

void Unweighted( Vertex s){
    queue<Vertex> q;
    q.push(s);
    wile(!q.empty()){
        v = q.front(); q.pop();
        for( V 的每個臨界點 W){
            dist[W] = dist[v] + 1; // 當前距離上一距離 + 1
            path[W] = v;  // s 到 w 的必經頂點就是前一個頂點 v
            q.push(W);
        }
    }
}

3. 有權圖的單源最短路演算法

Dijkstra 演算法

  • 令 S = {源點s + 已經確定了最短路徑的頂點 v i _i }
  • 對任一未收錄的頂點 v,定義 dist[v] 為 s 到 v 的最短路徑長度,但該路徑僅經過 S 中的頂點。即路徑 {s→(v
    i _i
    ∈S)→v} 的最小長度
  • 若路徑是按照遞增(非遞減)的順序生成的,則
    • 真正的最短路必須只經過 S 中的頂點
    • 每次從未收錄的頂點中選一個 dist 最小的收錄
    • 增加一個 v 進入 S,可能影響另外一個 w 的 dist 值
      • dist[w] = min{dist[w],dist[v] + <v,w>的權重}
void Dijkstra( Vertex s ){
    while(1){
        V = 未收錄頂點中dist最小值;
        if( 這樣的V不存在 )
            break;
        collected[V] = true;
        for( V 的每個鄰接點 W )
            if( collected[W] == false )
                if(dist[V] + E<V,W> < dist[W]){
             		dist[W] = dist[V] + E<V,W>;
                    path[W] = V;
                }
    }
}

取出未收錄頂點中dist最小值 和 更新dist[W]的操作可以考慮兩種方法:

  1. 直接掃描所有未收錄頂點 ——O(|V|)

    T = O(|V| 2 ^2 + |E|) ——稠密圖效果更好

  2. 將dist存在最小堆中 ——O(log|V|)

    更新dist[w]的值 —O(log|V|)

    T = O(|E|log|V|) —— 稀疏圖效果更好

#include<iostream>
#include<stdlib.h>
#define Inf 1000000
#define Init -1
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex];  // 距離 
int path[MaxVertex];  // 路徑 
int collected[MaxVertex];  // 被收錄集合 
int Nv;   // 頂點 
int Ne;   // 邊 
using namespace std;

// 初始化圖資訊 
void build(){
	Vertex v1,v2;
	int w;
	cin>>Nv;
	// 初始化圖 
	for(int i=1;i<=Nv;i++)
		for(int j=1;j<=Nv;j++)
			G[i][j] = 0;
	// 初始化路徑 
	for(int i=1;i<=Nv;i++)
		path[i] = Init;
	// 初始化距離
	for(int i=0;i<=Nv;i++)
		dist[i] = Inf;
	// 初始化收錄情況 
	for(int i=1;i<=Nv;i++)
		collected[i] = false;
	cin>>Ne;
	// 初始化點
	for(int i=0;i<Ne;i++){
		cin>>v1>>v2>>w;
		G[v1][v2] = w;  // 有向圖 
	}
}

// 初始化距離和路徑資訊 
void crate(Vertex s){
	dist[s] = 0;
	collected[s] = true;
	for(int i=1;i<=Nv;i++)
		if(G[s][i]){
			dist[i] = G[s][i];
			path[i] = s;
		}
}

// 查詢未收錄頂點中dist最小者
Vertex FindMin(Vertex s){
	int min = 0;  // 之前特地把 dist[0] 初始化為正無窮 
	for(Vertex i=1;i<=Nv;i++)
		if(i != s && dist[i] < dist[min] && !collected[i])
			min = i;
	return min;
}


void Dijkstra(Vertex s){
	crate(s); 
	while(true){
		Vertex V = FindMin(s);   // 找到 
		if(!V)
			break;
		collected[V] = true;  //收錄
		for(Vertex W=1;W<=Nv;W++)
			if(!collected[W] && G[V][W]){  // 如果未被收錄
				if(dist[V] + G[V][W] < dist[W]){
					dist[W] = G[V][W] + dist[V];
					path[W] = V;
				}
			}
	}
}

void output(){
	for(int i=1;i<=Nv;i++)
		cout<<dist[i]<<" ";
	cout<<endl;
	for(int i=1;i<=Nv;i++)
		cout<<path[i]<<" ";
	cout<<endl;
}


int main(){
	build();
	Dijkstra(1);
	output();
	return 0;
}

4. 多源最短路演算法

  • 直接將單源最短路演算法呼叫|V|遍

    T = O(|V| 3 ^3 + |E|×|V|) ——對於稀疏圖效果好

  • Floyd 演算法

    T = O(|V| 3 ^3 ) ——對於稠密圖效果好

Floyd 演算法

  • D k [ i ] [ j ] ^k[i][j] = 路徑{ i →{ l l k k } → j } 的最小長度
  • D 0 ^0 ,D 1 ^1 ,…,D V 1 [ i ] [ j ] ^{|V|-1}[i][j] 即給出了 i 到 j 的真正最短距離
  • 最初的 D 1 ^{-1} 是全 0 的鄰接矩陣
  • 若 i 和 j 不直接相連,初始化為無窮大
  • 當 D k 1 ^{k-1} 已經完成,遞推到 D k ^k 時:
    • 或者 k k 不屬於 最短路徑 { i →{ l l k k } → j },則 D k ^k = D k + 1 ^{k+1}
    • 或者 k k 屬於最短路徑 { i →{ l l k k } → j },則該路徑必定由兩段最短路徑組成:D k [ i ] [ j ] ^k[i][j] = D k 1 [ i ] [ k ] ^{k-1}[i][k] + D k 1 [ k ] [ j ] ^{k-1}[k][j]
void Floyd(){
    for( i = 0; i < N; i++ )
        for( j = 0; j < N; j++ ){
            D[i][j] = G[i][j];
            path[i][j] = -1;
        }
    for( k = 0; k < N; k++ )
        for( i = 0; i< N; i++)
            for( j = 0; j < N; j++ )
            	if( D[i][k] + D[k][j] < D[i][j] ) {
            		D[i][j] = D[i][k] + D[k][j];
                    path[i][j] = k;
                }
}
#include<iostream>
#include<stdlib.h>
#define INF 1000000
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex][MaxVertex];  // 距離 
int path[MaxVertex][MaxVertex];  // 路徑 
int Nv;   // 頂點 
int Ne;   // 邊 
using namespace std;

// 初始化圖資訊 
void build(){
	Vertex v1,v2;
	int w;
	cin>>Nv;
	// 初始化圖 
	for(int i=1;i<=Nv;i++)
		for(int j=1;j<=Nv;j++)
			G[i][j] = INF;
	cin>>Ne;
	// 初始化點
	for(int i=0;i<Ne;i++){
		cin>>v1>>v2>>w;
		G[v1][v2] = w;  
		G[v2][v1] = w;
	}
}

void Floyd(){
	for(Vertex i=1;i<=Nv;i++)
		for(Vertex j=1;j<=Nv;j++){
			dist[i][j] = G[i][j];
			path[i][j] = -1;
		}
	for(Vertex k=1;k<=Nv;k++)
		for(Vertex i=1;i<=Nv;i++)
			for(Vertex j=1;j<=Nv;j++)
				if(dist[i][k] + dist[k][j] < dist[i][j]){
					dist[i][j] = dist[i][k] + dist[k][j];
					path[i][j] = k;
				}
} 

void output(){
	for(Vertex i=1;i<=Nv;i++){ 
		for(Vertex j=1;j<=Nv;j