數據結構 第15講 一場說走就走的旅行——最短路徑

一場說走就走的旅行——最短路徑

本內容來源於《趣學算法》,在線章節:http://www.epubit.com.cn/book/details/4825

有一天,孩子回來對我說:“媽媽,聽說馬爾代夫很不錯,放假了我想去玩。”馬爾代夫?我也想去!沒有人不向往一場說走就走的旅行!“其實我想去的地方很多,呼倫貝爾大草原、玉龍雪山、布達拉宮、艾菲爾鐵塔……”小孩子還說着他感興趣的地方。於是我們拿出地圖,標出想去的地點,然後計算最短路線,估算大約所需的時間,有了這張祕製地圖,一場說走就走的旅行不是夢!

“哇,感覺我們像凡爾納的《環遊地球八十天》,好激動!可是老媽你也太out了,學計算機的最短路線你用手算?”

暴汗……,“小子你別牛,你知道怎麼算?”

“呃,好像是叫什麼迪科斯徹的人會算。”

哈哈,關鍵時刻還要老媽上場了!

圖2-8 一場說走就走的旅行

2.5.1 問題分析

根據題目描述可知,這是一個求單源最短路徑的問題。給定有向帶權圖G =(V),其中每條邊的權是非負實數。此外,給定V中的一個頂點,稱爲源點。現在要計算從源到所有其他各頂點的最短路徑長度,這裏路徑長度指路上各邊的權之和。

如何求源點到其他各點的最短路徑呢?

如圖2-9所示,艾茲格•W•迪科斯徹(Edsger Wybe Dijkstra),荷蘭人,計算機科學家。他早年鑽研物理及數學,後轉而研究計算學。他曾在1972年獲得過素有“計算機科學界的諾貝爾獎”之稱的圖靈獎,與Donald Ervin Knuth並稱爲我們這個時代最偉大的計算機科學家。

圖2-9 艾茲格•W•迪科斯徹

2.5.2 算法設計

Dijkstra 算法是解決單源最短路徑問題的貪心算法,它先求出長度最短的一條路徑,再參照該最短路徑求出長度次短的一條路徑,直到求出從源點到其他各個頂點的最短路徑。

Dijkstra算法的基本思想是首先假定源點爲u,頂點集合V被劃分爲兩部分:集合S和 VS。初始時 中僅含有源點 u,其中 中的頂點到源點的最短路徑已經確定。集合VS中所包含的頂點到源點的最短路徑的長度待定,稱從源點出發只經過S中的點到達VS中的點的路徑爲特殊路徑,並用數組dist[]記錄當前每個頂點所對應的最短特殊路徑長度。

Dijkstra算法採用的貪心策略是選擇特殊路徑長度最短的路徑,將其連接的VS中的頂點加入到集合S中,同時更新數組dist[]。一旦S包含了所有頂點,dist[]就是從源到所有其他頂點之間的最短路徑長度。

(1)數據結構。設置地圖的帶權鄰接矩陣爲map[][],即如果從源點u到頂點i有邊,就令 map[u][i]等於<ui>的權值,否則 map[u][i]=∞(無窮大);採用一維數組 dist[i]來記錄從源點到i頂點的最短路徑長度;採用一維數組p[i]來記錄最短路徑上i頂點的前驅。

(2)初始化。令集合S={u},對於集合VS中的所有頂點x,初始化dist[i]=map[u][i],如果源點u到頂點i有邊相連,初始化p[i]=u,否則p[i]= −1。

(3)找最小。在集合VS中依照貪心策略來尋找使得dist[j]具有最小值的頂點t,即dist[t]=min(dist[j]|j屬於VS集合),則頂點t就是集合VS中距離源點u最近的頂點。

(4)加入S戰隊。將頂點t加入集合S中,同時更新VS

(5)判結束。如果集合VS爲空,算法結束,否則轉(6)。

(6)借東風。在(3)中已經找到了源點到t的最短路徑,那麼對集合VS中所有與頂點t相鄰的頂點j,都可以藉助t走捷徑。如果dis[j]>dist[t]+map[t][j],則dist[j]=dist[t]+map[t][j],記錄頂點j的前驅爲t,有p[j]= t,轉(3)。

由此,可求得從源點u到圖G的其餘各個頂點的最短路徑及長度,也可通過數組p[]逆向找到最短路徑上經過的城市。

2.5.3 完美圖解

現在我們有一個景點地圖,如圖2-10所示,假設從1號結點出發,求到其他各個結點的最短路徑。

圖2-10 景點地圖

算法步驟如下。

(1)數據結構

設置地圖的帶權鄰接矩陣爲map[][],即如果從頂點i到頂點j有邊,則map[i][j]等於<ij>的權值,否則map[i][j]=∞(無窮大),如圖2-11所示。

圖2-11 鄰接矩陣map[][]

(2)初始化

令集合S={1},VS={2,3,4,5},對於集合VS中的所有頂點x,初始化最短距離數組dist[i]=map[1][i],dist[u]=0,如圖2-12所示。如果源點1到頂點i有邊相連,初始化前驅數組p[i]=1,否則p[i]= −1,如圖2-13所示。

圖2-12 最短距離數組dist[]

圖2-13 前驅數組p[]

(3)找最小

在集合VS={2,3,4,5}中,依照貪心策略來尋找VS集合中dist[]最小的頂點t,如圖2-14所示。

..\17-0245 圖\0217.tif

圖2-14 最短距離數組dist[]

找到最小值爲2,對應的結點t=2。

(4)加入S戰隊

將頂點t=2加入集合SS={1,2},同時更新VS={3,4,5},如圖2-15所示。

..\17-0245 改圖\0215b.tif

圖2-15 景點地圖

(5)借東風

剛剛找到了源點到t=2的最短路徑,那麼對集合VS中所有t的鄰接點j,都可以藉助t走捷徑。我們從圖或鄰接矩陣都可以看出,2號結點的鄰接點是3和4號結點,如圖2-16所示。

C:\Users\LL\Desktop\45957\45957-小田-製作489質檢587 48.jpg

圖2-16 鄰接矩陣map[][]

先看3號結點能否藉助2號走捷徑:dist[2]+map[2][3]=2+2=4,而當前dist[3]=5>4,因此可以走捷徑即2—3,更新dist[3]=4,記錄頂點3的前驅爲2,即p[3]= 2。

再看4號結點能否藉助2號走捷徑:如果dist[2]+map[2][4]=2+6=8,而當前dist[4]=∞>8,因此可以走捷徑即2—4,更新dist[4]=8,記錄頂點4的前驅爲2,即p[4]= 2。

更新後如圖2-17和圖2-18所示。

圖2-17 最短距離數組dist[]

圖2-18 前驅數組p[]

(6)找最小

在集合VS={3,4,5}中,依照貪心策略來尋找dist[]具有最小值的頂點t,依照貪心策略來尋找VS集合中dist[]最小的頂點t,如圖2-19所示。

..\17-0245 圖\0222.tif

圖2-19 最短距離數組dist[]

找到最小值爲4,對應的結點t=3。

(7)加入S戰隊

將頂點t=3加入集合SS={1,2,3},同時更新VS={4,5},如圖2-20所示。

圖2-20 景點地圖

(8)借東風

剛剛找到了源點到t =3的最短路徑,那麼對集合VS中所有t的鄰接點j,都可以藉助t走捷徑。我們從圖或鄰接矩陣可以看出,3號結點的鄰接點是4和5號結點。

先看4號結點能否藉助3號走捷徑:dist[3]+map[3][4]=4+7=11,而當前dist[4]=8<11,比當前路徑還長,因此不更新。

再看5號結點能否藉助3號走捷徑:dist[3]+map[3][5]=4+1=5,而當前dist[5]=∞>5,因此可以走捷徑即3—5,更新dist[5]=5,記錄頂點5的前驅爲3,即p[5]=3。

更新後如圖2-21和圖2-22所示。

圖2-21 最短距離數組dist[]

圖2-22 前驅數組p[]

(9)找最小

在集合VS={4,5}中,依照貪心策略來尋找VS集合中dist[]最小的頂點t,如圖2-23所示。

..\17-0245 圖\0226.tif

圖2-23 最短距離數組dist[]

找到最小值爲5,對應的結點t=5。

(10)加入S戰隊

將頂點t=5加入集合SS={1,2,3,5},同時更新VS={4},如圖2-24所示。

圖2-24 景點地圖

(11)借東風

剛剛找到了源點到t =5的最短路徑,那麼對集合VS中所有t的鄰接點j,都可以藉助t走捷徑。我們從圖或鄰接矩陣可以看出,5號結點沒有鄰接點,因此不更新,如圖2-25和圖2-26所示。

圖2-25 最短距離數組dist[]

圖2-26 前驅數組p[]

(12)找最小

在集合VS={4}中,依照貪心策略來尋找dist[]最小的頂點t,只有一個頂點,所以很容易找到,如圖2-27所示。

圖2-27 最短距離數組dist[]

找到最小值爲8,對應的結點t=4。

(13)加入S戰隊

將頂點t加入集合SS={1,2,3,5,4},同時更新VS={ },如圖2-28所示。

圖2-28 景點地圖

(14)算法結束

VS={ }爲空時,算法停止。

由此,可求得從源點u到圖G的其餘各個頂點的最短路徑及長度,也可通過前驅數組p[]逆向找到最短路徑上經過的城市,如圖2-29所示。

..\17-0245 圖\0232.tif

圖2-29 前驅數組p[]

例如,p[5]=3,即5的前驅是3;p[3]=2,即3的前驅是2;p[2]=1,即2的前驅是1;p[1]= −1,1沒有前驅,那麼從源點1到5的最短路徑爲1—2—3—5。

2.5.4 僞代碼詳解

(1)數據結構

n:城市頂點個數。m:城市間路線的條數。map[][]:地圖對應的帶權鄰接矩陣。dist[]:記錄源點u到某頂點的最短路徑長度。p[]:記錄源點到某頂點的最短路徑上的該頂點的前一個頂點(前驅)。flag[]:flag[i]等於true,說明頂點i已經加入到集合S,否則頂點i屬於集合VS

const int N = 100; //初始化城市的個數,可修改
const int INF = 1e7; //無窮大
int map[N][N],dist[N],p[N],n,m;
bool flag[N];

(2)初始化源點u到其他各個頂點的最短路徑長度,初始化源點u出邊鄰接點(t的出邊相關聯的頂點)的前驅爲u

bool flag[n];//如果flag[i]等於true,說明頂點i已經加入到集合S;否則i屬於集合V-S
for(int i = 1; i <= n; i ++)
    {
      dist[i] = map[u][i]; //初始化源點u到其他各個頂點的最短路徑長度
      flag[i]=false;
      if(dist[i]==INF)
         p[i]=-1;   //說明源點u到頂點i無邊相連,設置p[i]=-1
    else
         p[i]=u;   //說明源點u到頂點i有邊相連,設置p[i]=u
    }

(3)初始化集合S,令集合S={u},從源點到u的最短路徑爲0。

flag[u]=true;   //初始化集合S中,只有一個元素:源點 u 
dist[u] = 0;   //初始化源點 u的最短路徑爲0,自己到自己的最短路徑

(4)找最小

在集合VS中尋找距離源點u最近的頂點t,若找不到t,則跳出循環;否則,將t加入集合S

    int temp = INF,t = u ;
    for(int j = 1 ; j <= n ; j ++) //在集合V-S中尋找距離源點u最近的頂點t
      if( !flag[j] && dist[j] < temp)
      {
         t=j;   //記錄距離源點u最近的頂點
         temp=dist[j];
      }
    if(t == u) return ; //找不到t,跳出循環
    flag[t] = true;      //否則,將t加入集合S

(5)借東風

考查集合VS中源點ut的鄰接點j的距離,如果源點u經過t到達j的路徑更短,則更新dist[j] =dist[t]+map[t][j],即鬆弛操作,並記錄j的前驅爲t

for(int j = 1; j <= n; j ++)  //更新集合V-S中與t鄰接的頂點到源點u的距離
   if(!flag[j] && map[t][j]<INF) //!flag[j]表示j在V-S中,map[t][j]<INF表示t與j鄰接
      if(dist[j]>(dist[t]+map[t][j])) //經過t到達j的路徑更短
      {
         dist[j]=dist[t]+map[t][j] ;
         p[j]=t; //記錄j的前驅爲t 
      }

重複(4)~(5),直到源點u到所有頂點的最短路徑被找到。

2.5.5 實戰演練

//program 2-4
#include <cstdio>
#include <iostream>
#include<cstring>
#include<windows.h>
#include<stack>
using namespace std;
const int N = 100; // 城市的個數可修改
const int INF = 1e7; // 初始化無窮大爲10000000
int map[N][N],dist[N],p[N],n,m;//n城市的個數,m爲城市間路線的條數
bool flag[N]; //如果flag[i]等於true,說明頂點i已經加入到集合S;否則頂點i屬於集合V-S
void Dijkstra(int u)
{
   for(int i=1; i<=n; i++)//①
    {
     dist[i] =map[u][i]; //初始化源點u到其他各個頂點的最短路徑長度
     flag[i]=false;
     if(dist[i]==INF)
        p[i]=-1; //源點u到該頂點的路徑長度爲無窮大,說明頂點i與源點u不相鄰
     else
        p[i]=u; //說明頂點i與源點u相鄰,設置頂點i的前驅p[i]=u
     }
    dist[u] = 0;
    flag[u]=true;   //初始時,集合S中只有一個元素:源點u
    for(int i=1; i<=n; i++)//②
     {
         int temp = INF,t = u;
         for(int j=1; j<=n; j++) //③在集合V-S中尋找距離源點u最近的頂點t
           if(!flag[j]&&dist[j]<temp)
             {
              t=j;
              temp=dist[j];
             }
           if(t==u) return ; //找不到t,跳出循環
           flag[t]= true;  //否則,將t加入集合
           for(int j=1;j<=n;j++)//④//更新集合V-S中與t鄰接的頂點到源點u的距離
             if(!flag[j]&& map[t][j]<INF)//!flag[j]表示j在V-S中  
                if(dist[j]>(dist[t]+map[t][j]))
                 {
                   dist[j]=dist[t]+map[t][j] ;
                   p[j]=t ;
                 }
             }
     }
     int main()
     {
             int u,v,w,st;
             system("color 0d");
             cout << "請輸入城市的個數:"<<endl;
             cin >> n;
             cout << "請輸入城市之間的路線的個數:"<<endl;
             cin >>m;
             cout << "請輸入城市之間的路線以及距離:"<<endl;
             for(int i=1;i<=n;i++)//初始化圖的鄰接矩陣
               for(int j=1;j<=n;j++)
               {
                   map[i][j]=INF;//初始化鄰接矩陣爲無窮大
               }
             while(m--)
             {
               cin >> u >> v >> w;
               map[u][v] =min(map[u][v],w); //鄰接矩陣儲存,保留最小的距離
             }
             cout <<"請輸入小明所在的位置:"<<endl; ;
             cin >> st;
             Dijkstra(st);
             cout <<"小明所在的位置:"<<st<<endl;
             for(int i=1;i<=n;i++){
                   cout <<"小明:"<<st<<" - "<<"要去的位置:"<<i<<endl;
                   if(dist[i] == INF)
                      cout << "sorry,無路可達"<<endl;
                   else
                      cout << "最短距離爲:"<<dist[i]<<endl;
             }
             return 0;
   }

算法實現和測試

(1)運行環境

Code::Blocks

(2)輸入

請輸入城市的個數:
5
請輸入城市之間的路線的個數:
11
請輸入城市之間的路線以及距離:
1 5 12
5 1 8
1 2 16
2 1 29
5 2 32
2 4 13
4 2 27
1 3 15
3 1 21
3 4 7
4 3 19
請輸入小明所在的位置:
5

(3)輸出

小明所在的位置:5
小明:5 - 要去的位置:1 最短距離爲:8
小明:5 - 要去的位置:2 最短距離爲:24
小明:5 - 要去的位置:3 最短距離爲:23
小明:5 - 要去的位置:4 最短距離爲:30
小明:5 - 要去的位置:5 最短距離爲:0

想一想:因爲我們在程序中使用p[]數組記錄了最短路徑上每一個結點的前驅,因此除了顯示最短距離外,還可以顯示最短路徑上經過了哪些城市,可以增加一段程序逆向找到該最短路徑上的城市序列。

void findpath(int u)
{
  int x;
  stack<int>s;//利用C++自帶的函數創建一個棧s,需要程序頭部引入#include<stack>
  cout<<"源點爲:"<<u<<endl;
  for(int i=1;i<=n;i++)
  {
    x=p[i];
    while(x!=-1)
    {
      s.push(x);//將前驅依次壓入棧中
      x=p[x];
    }
    cout<<"源點到其他各頂點最短路徑爲:";
    while(!s.empty())
    {
      cout<<s.top()<<"--";//依次取棧頂元素
      s.pop();//出棧
    }
    cout<<i<<";最短距離爲:"<<dist[i]<<endl;
  }
}

只需要在主函數末尾調用該函數:

findpath(st);//主函數中st爲源點

輸出結果如下。

源點爲:5
源點到其他各頂點最短路徑爲:5--1;最短距離爲:8
源點到其他各頂點最短路徑爲:5--1--2;最短距離爲:24
源點到其他各頂點最短路徑爲:5--1--3;最短距離爲:23
源點到其他各頂點最短路徑爲:5--1--3--4;最短距離爲:30
源點到其他各頂點最短路徑爲:5;最短距離爲:0

2.5.6 算法解析及優化拓展

1.算法時間複雜度

(1)時間複雜度:在Dijkstra算法描述中,一共有4個for語句,第①個for語句的執行次數爲n,第②個for語句裏面嵌套了兩個for語句③、④,它們的執行次數均爲n,對算法的運行時間貢獻最大,當外層循環標號爲1時,③、④語句在內層循環的控制下均執行n次,外層循環②從1~n。因此,該語句的執行次數爲n*nn²,算法的時間複雜度爲O(n²)。

(2)空間複雜度:由以上算法可以得出,實現該算法所需要的輔助空間包含爲數組flag、變量ijttemp所分配的空間,因此,空間複雜度爲O(n)。

2.算法優化拓展

在for語句③中,即在集合VS中尋找距離源點u最近的頂點t,其時間複雜度爲O(n),如果我們使用優先隊列,則可以把時間複雜度降爲O(log n)。那麼如何使用優先隊列呢?

(1)優先隊列(見附錄C)

(2)數據結構

在上面的例子中,我們使用了一維數組dist[t]來記錄源點u到頂點t的最短路徑長度。在此爲了操作方便,我們使用結構體的形式來實現,定義一個結構體Node,裏面包含兩個成員:u爲頂點,step爲源點到頂點u的最短路徑。

struct  Node{
     int u,step; // u爲頂點,step爲源點到頂點u的最短路徑
     Node(){};
     Node(int a,int sp){
         u = a;   //參數傳遞,u爲頂點
         step = sp; //參數傳遞,step爲源點到頂點u的最短路徑
     }
     bool operator < (const  Node& a)const{ 
         return step > a.step; //重載 <,step(源點到頂點u的最短路徑)最小值優先
     }
};

上面的結構體中除了兩個成員變量外,還有一個構造函數和運算符優先級重載,下面詳細介紹其含義用途。

爲什麼要使用構造函數?

如果不使用構造函數也是可以的,只定義一般的結構體,裏面包含兩個參數:

struct  Node{
     int u,step; // u爲頂點,step爲源點到頂點u的最短路徑
};

那麼在變量參數賦值時,需要這樣賦值:

Node vs ; //先定義一個Node結點類型變量
vs.u =3 ,vs.step = 5; //分別對該變量的兩個成員進行賦值

採用構造函數的形式定義結構體:

struct  Node{
     int u,step;
     Node(){};
     Node(int a,int sp){
         u = a;   //參數傳遞u爲頂點
         step = sp; //參數傳遞step爲源點到頂點u的最短路徑
     }
};

則變量參數賦值就可以直接通過參數傳遞:

Node vs(3,5)

上面語句等價於:

vs.v =3 ,vs.step = 5;

很明顯通過構造函數的形式定義結構體,參數賦值更方便快捷,後面程序中會將結點壓入優先隊列:

priority_queue <Node> Q;  // 創建優先隊列,最小值優先
Q.push(Node(i,dist[i])); //將結點Node壓入優先隊列Q
                         //參數i傳遞給頂點u, dist[i]傳遞給step

(3)使用優先隊列優化的Dijkstra算法源代碼:

//program 2-5
#include <queue>
#include <iostream>
#include<cstring>
#include<windows.h>
using namespace std;
const int N = 100; // 城市的個數可修改
const int INF = 1e7; // 無窮大
int map[N][N],dist[N],n,m;
int flag[N];
struct  Node{
     int u,step;
     Node(){};
     Node(int a,int sp){
         u=a;step=sp;
     }
     bool operator < (const  Node& a)const{  // 重載 <
          return step>a.step;
     }
};
void Dijkstra(int st){
     priority_queue <Node> Q;  // 優先隊列優化
     Q.push(Node(st,0));
     memset(flag,0,sizeof(flag));//初始化flag數組爲0
     for(int i=1;i<=n;++i)
       dist[i]=INF; // 初始化所有距離爲,無窮大
     dist[st]=0;
     while(!Q.empty())
     {
          Node it=Q.top();//優先隊列隊頭元素爲最小值
          Q.pop();
          int t=it.u;
          if(flag[t])//說明已經找到了最短距離,該結點是隊列裏面的重複元素
               continue;
          flag[t]=1;
          for(int i=1;i<=n;i++)
          {
              if(!flag[i]&&map[t][i]<INF){ // 判斷與當前點有關係的點,並且自己不能到自己
                  if(dist[i]>dist[t]+map[t][i])
                  {   // 求距離當前點的每個點的最短距離,進行鬆弛操作
                      dist[i]=dist[t]+map[t][i];
                      Q.push(Node(i,dist[i]));// 把更新後的最短距離壓入優先隊列,注意:裏面的元素有重複
                  }
              }
          }
     }
}
int main()
{
          int u,v,w,st;
          system("color 0d");//設置背景及字體顏色
          cout << "請輸入城市的個數:"<<endl;
          cin >> n;
          cout << "請輸入城市之間的路線的個數:"<<endl;
          cin >>m;
          for(int i=1;i<=n;i++)//初始化圖的鄰接矩陣
             for(int j=1;j<=n;j++)
             {
                 map[i][j]=INF;//初始化鄰接矩陣爲無窮大
             }
          cout << "請輸入城市之間u,v的路線以及距離w:"<<endl;
          while(m--)
          {
               cin>>u>>v>>w;
               map[u][v]=min(map[u][v],w); //鄰接矩陣儲存,保留最小的距離
          }
          cout<<"請輸入小明所在的位置:"<<endl; ;
          cin>>st;
          Dijkstra(st);
          cout <<"小明所在的位置:"<<st<<endl;
          for(int i=1;i<=n;i++)
          {
               cout <<"小明:"<<st<<"--->"<<"要去的位置:"<<i;
               if(dist[i]==INF)
                   cout << "sorry,無路可達"<<endl;
               else
                   cout << " 最短距離爲:"<<dist[i]<<endl;
          }
     return 0;
}

算法實現和測試

(1)運行環境

Code::Blocks

(2)輸入

請輸入城市的個數:
5
請輸入城市之間的路線的個數:
7
請輸入城市之間的路線以及距離:
1 2 2
1 3 3 
2 3 5
2 4 6
3 4 7
3 5 1
4 5 4
請輸入小明所在的位置:
1

(3)輸出

小明所在的位置:1
小明:1 - 要去的位置:1 最短距離爲:0
小明:1 - 要去的位置:2 最短距離爲:2
小明:1 - 要去的位置:3 最短距離爲:3
小明:1 - 要去的位置:4 最短距離爲:8
小明:1 - 要去的位置:5 最短距離爲:4

在使用優先隊列的 Dijkstra 算法描述中,while (!Q.empty())語句執行的次數爲n,因爲要彈出n個最小值隊列纔會空;Q.pop()語句的時間複雜度爲logn,while語句中的for語句執行n次,for語句中的Q.push (Node(i,dist[i]))時間複雜度爲logn。因此,總的語句的執行次數爲n* logn+n²*logn,算法的時間複雜度爲O(n²logn)。

貌似時間複雜度又變大了?

這是因爲我們採用的鄰接矩陣存儲的,如果採用鄰接表存儲(見附錄D),那麼for語句④鬆弛操作就不用每次執行n次,而是執行t結點的鄰接邊數x,每個結點的鄰接邊加起來爲邊數E,那麼總的時間複雜度爲O(n*logn+E*logn),如果E>=n,則時間複雜度爲O(E*logn)。

注意:優先隊列中儘管有重複的結點,但重複結點最壞是n2,log n2=2 log n,並不改變時間複雜度的數量級。

想一想,還能不能把時間複雜度再降低呢?如果我們使用斐波那契堆,那麼鬆弛操作的時間複雜度O(1),總的時間複雜度爲O(n* logn+E)。

契堆,那麼鬆弛操作的時間複雜度O(1),總的時間複雜度爲O(n* logn+E)。

發佈了53 篇原創文章 · 獲贊 185 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章