數據結構-王道-圖


圖的基本概念

       圖\(G\)由頂點集\(V\)和邊集\(E\)組成,記為\(G=(V,E)\),其中\(V(G)\)表示圖\(G\)中頂點的有限非空集;\(E(G)\)表示圖\(G\)中頂點之間的關系(邊)集合。若\(V={v_1,v_2,v_3,\ldots,v_n}\),用\(|V|\)表示圖\(G\)中頂點的個數,也稱為圖\(G\)的階,\(E={(u,v)|u\in V,v\in V}\),用\(E\)表示圖\(G\)中邊的條數。

注意:線性表可以是空表,樹可以是空樹,但圖不可以是空圖。也就是說,圖中不能一個頂點也沒有,圖中頂點集\(V\)一定非空,但是邊集\(E\)可以為空,此時圖中只有頂點而沒有邊。

有向圖

       若E是有向邊(也稱為弧)的有限集合時,則圖G為有向圖。弧是頂點的有序對,記為\(<v,w>\),其中v,w是頂點。w稱為弧頭,v稱為弧尾,稱為從頂點v到頂點w的弧,也稱為v鄰接到w,或w鄰接自v。

![Alt text](./1537362493863.png)
       上圖$(a)$所示的有向圖$G_1$可表示為: > $G_1=(V_1,E_1)$ > $V_1={1,2,3}$ > $E_1={<1,2>,<2,1>,<2,3>}$ ####
無向圖
       若$E$是無向邊(簡稱邊)的有限集合時,則圖$G$為無向圖。邊是頂點的無序對,記為$(v,w)$或$(w,v)$,以為$(v,w)=(w,v)$,其中$v$和$w$是頂點。可以說頂點$w$和頂點$v$互為鄰接點。邊$ $依附於頂點$w$和$v$,或者說邊$(v,w)$和頂點$v,w$相關聯。        上圖$(b)$所示的無向圖$G_2$可表示為: > $G_2=(V_2,E_2)$ > $V_2={1,2,3,4}$ > $G_2={(1,2),(1,3),(1,4),(2,3),(2,4)(3,4)}$

簡單圖

       一個圖G如果滿足:

  1. 不存在重復邊。
  2. 不存在頂點到自身的邊。
    則可以稱為簡單圖。上圖\((a),(b)\),都是簡單圖,並且數據結構中只討論簡單圖。

多重圖

       若圖\(G\)中,某兩點之間的邊數多於一條,又允許頂點通過一條邊和自己關聯,則G為多重圖。多重圖的定義和簡單圖是相對的。

完全圖

       在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。含有n個頂點的無向完全圖有\(\frac{n(n-1)}{2}\)條邊。在有向圖中,如果任意兩個頂點之間都存在方向相反的兩條弧,則稱該圖為有向完全圖。含有n個頂點的有向完全圖有\(n(n-1)\)條有向邊。
       上圖中\((b)\)為無向完全圖,\((c)\)為有向完全圖。

子圖

       設有兩個圖\(G=(V,E)\)\(G^`=(V^`,E^`)\),若\(E^`\)\(E\)的子集,\(V^`\)\(V\)的子集,則稱\(G`\)\(G\)的子圖。
       上圖中\((c)\)\((a)\)的子圖。
注意:並非\(V\)\(E\)的任何子集都能構成\(G\)的子圖,因為這樣的子集可能不是圖,也就是說,\(E\)的子集中的某些邊關聯的頂點可能不再這個\(V\)的子集中。

連通,連通圖和連通分量

       在無向圖中,若從頂點v到頂點w有路徑存在,則稱v和w是連通的。若圖G中任意兩個頂點都是連通的,則稱G為連通圖,否則稱為非連通圖。無向圖中極大連通子圖稱為連通分量。如果一個圖中有n個頂點,並且有小於n-1條邊,則此圖必是非連通圖,如下圖所示有三個連通分量(極大連通子圖)。

![Alt text](./1537365280231.png)
注意:弄清連通(若頂點v到頂點w是有路徑的則稱:v和w是連通的),連通圖(任意兩個頂點之間都是連通的,則稱該圖為連通圖)。連通分量(又稱為:**極大連通子圖**。極大連通子圖是無向圖的連通分量,極大要求該連同子圖包含其所有的邊;極小連通子圖即要保持圖連通,又要使邊數最少的子圖。) ####
強連通圖,強連通分量
       在**有向圖**中,若從**頂點v到頂點w**和從**頂點w到頂點v**之間**都有路徑**,則稱這兩個頂點是強連同的。若圖中任何一對頂點都是強連同的,則稱此圖為強連通圖。有向圖中的極大連通子圖稱為有向圖的強連通分量。 ####
生成樹,生成森林
       連通圖的生成樹是包含圖中全部頂點的一個極小連通圖。如圖中頂點數為n,則他生成的樹含有n-1條邊。對於生成樹而言,若砍去她的一條邊,則會編程非連通圖,若加上一條邊則會編程一個回路。在非連通圖中,連同分量的生成樹構成了非連通圖的生成森林。 [上圖$(G_2)$](#pic3)的一個生成樹如圖所示.
![Alt text](./1537367307076.png)
####
頂點的度,入度和出度
       圖中每個頂點的度定義為以該頂點為一個端點的邊得數目。        對於無向圖,頂點v的度是指依附於該頂點的邊的條數,記為$TD(v)$。        在具有n個頂點e條邊的無向圖中,有$\sum_{i=1}^nTD(V_i)=2e$。即無向圖的全部頂點的度之和等於邊數的兩倍,這是因為每條邊和兩個頂點相關聯。具體可以參考上一個圖。        對於有向圖,頂點v的度分為入度和出度,入度是以頂點v為終點的的有向邊的數目,記為$ID(v)$;而出度是以v為起點的有向邊的數目,記為$OD(v)$。**頂點v的度等於其入度和出度之和**。        在具有n個頂點和e條邊的有向圖中,有**每個點的入度之和=每個點的出度之和=e**,這是因為每條邊都有一個起點和終點。 ####
邊的權和網
       在一個圖中,每條邊可以表上具有某種意義的數值,該數值稱為改邊的權值。這種邊上帶有權值的圖稱為帶全圖,也稱作網。 ####
稠密圖,稀疏圖
       邊數很少的圖稱為稀疏圖,反之稱為稠密圖。稀疏和稠密本身是模糊的概念,稀疏圖和稠密圖本身是模糊的概念,稀疏圖和稠密圖一般是相對而言的。一般圖G滿足$|E|<|V|*\log|V|$時,可以將G看做是稀疏圖。 ####
路徑,路徑長度和回路
       頂點$v_p$到頂點$v_q$之間的一條路徑是指頂點序列$v_p,v_{i1},v_{i2},\ldots,v_{im},v_q$。路徑上邊的數目稱為路徑長度。第一個頂點和最后一個頂點相同的路徑稱為回路或者環。如果一個圖有n個頂點,並且有大於n-1條邊,則此圖一定有環。 ####
簡單路徑,簡單回路
       在路徑序列中,頂點不重復重現的路徑稱為簡單路徑。**除了第一個頂點和最后一個頂點之外**,其余頂點不重復出現的**回路**稱為簡單回路。 ####
距離
       從頂點u出發到頂點v的最短路徑若存在,則此路徑的長度稱作從u到v的距離。若從u到v不存在路徑,則記距離為$\infty$。 ####
有向樹
       有一個頂點的入度為0,其余頂點的入度均為1的有向圖稱為有向樹。 ###
圖的存儲和基本操作
       圖的存儲必須要完整,准確的反應頂點集和邊集的信息。根據不同圖的結構和算法,可以采用不同的存儲方式,但不同的存儲方式對程序的效率影響相當的大。因此所選的數據結構應該適合於待求解的問題。不論是有向圖還是無向圖,主要的存儲方式都有兩種:鄰接矩陣和鄰接表。前者屬於圖的順序存儲結構,后者屬於圖的連接存儲結構。 ####
鄰接矩陣法
       所謂鄰接矩陣存儲,就是用一個一維數組存儲圖中頂點的信息,用一個二維數組存儲圖中邊的信息(即各頂點之間的鄰接關系),存儲頂點之間的鄰接關系的二維數組稱為鄰接矩陣。        結點數為n的圖$G=(V,E)$,的鄰接矩陣A是$n*n$的。將G的頂點編號為$v_1,v_2,v_3,\ldots,v_n$。若$(V_i,V_j)\in E$,則$A[i][j]=1$,否則$A[i][j]=0$。
![Alt text](./1537403009719.png)
       對於帶權圖而言,若頂點$v_i$和$v_j$之間有邊連接,則鄰接矩陣中對應項存放着改變對應的權值,若頂點$v_i$和$v_j$不相連,則用$\infty$來報表時這兩個頂點之間不存在邊。
![Alt text](./1537403165489.png)
       圖的鄰接矩陣存儲結構定義如下: ```cpp #define MaxVertexNum 100 typedef char VertexType; typedef int EdgeType; typedef struct { VertexType Vex[MaxVertexNum]; // 頂點表,用於存儲途中的所有頂點 EdgeType EDge[MaxVertexNum][MaxVertexNum];//鄰接矩陣,邊表。 int vexnum,arcnum;//圖中當前頂點數和弧數。 }MGragh; ``` > 注意:在簡單應用中,可以直接用二維數組作為圖的鄰接矩陣(頂點等信息均可忽略)。 > 當鄰接矩陣中的元素僅表示相應的邊是否存在時,EdgeType可定義值為0和1的枚舉類型。 > 無向圖的鄰接矩陣是對稱矩陣,對規模特大的鄰接矩陣可采用壓縮存儲。 > 鄰接矩陣表示法的空間復雜度為$O(n^2)$,其中n為圖的頂點數$|V|$。
圖的鄰接矩陣存儲表示法具有以下特點:
  1. 無向圖的鄰接矩陣一定是一個對稱矩陣(並且唯一)。因此在實際存儲鄰接矩陣時只需要存儲上(或下)三交矩陣的元素即可。
  2. 對於無向圖,鄰接矩陣的第i行(或第i列)非零元素的個數正好是第i個頂點的度\(TD(V_i)\)
  3. 對於有向圖,鄰接矩陣的第i行(或第i列)非零元素的個數正好是第i個頂點的出度\(OD(V_i)\)(或入度\(ID(V_i)\))。
  4. 用鄰接矩陣法存儲圖,很容易確定圖中任意兩個頂點之間是否有邊相連。但是,要確定圖中有多少條邊,則必須按行,按列對每個元素進行監測,所花費的時間代價很大。這是用鄰接矩陣存儲圖的局限性。
  5. 稠密圖適合用鄰接矩陣存儲表示。
  6. 設圖G的鄰接矩陣為A,\(A^n\)的元素\(A^n[i][j]\)等於由頂點i到頂點j的長度為n的路徑的數目,該結論了解即可,證明方法在離散數學中。

鄰接表法

       當一個圖為稀疏圖時,是用鄰接矩陣表示法顯然浪費了大量的存儲空間。而圖的鄰接表法結合了順序存儲和鏈式存儲的方法,大大的減少了這種不必要的浪費。
       所謂鄰接表就是對圖G中的每個頂點\(V_i\)建立一個單鏈表,第i個單鏈表中的結點表示依附於頂點\(V_i\)的邊(對於有向圖則是以頂點\(V_i\)為尾的弧),這個單鏈表就成為頂點\(V_i\)的邊表(對有向圖來說是出邊表)。邊表的頭指針和頂點的數據信息采用順序存儲(稱為頂點表),所以在鄰接表中存在兩種結點;頂點表結點和邊表結點。

![Alt text](./1537405474163.png)
       頂點表結點由頂點域(data)和指向第一條鄰接邊的指針構成,邊表結點由臨接點域和指向下一條臨界邊的指針域構成。
![Alt text](./1537405691430.png)
圖的鄰接表存儲結構定義如下:
#define MaxVertexNum 100 
typedef char VertexType;
typedef struct ArcNode // 邊表結點
{
    int adjvex;        // 該弧所指向的頂點的位置。
    struct ArcNode *next;// 指向下一條依附於該頂點的弧的指針。
}ArcNode;
typedef struct VNode   // 頂點表結點
{
    VertexType data;   // 頂點信息
    ArcNode *first;    // 指向依附於該頂點的弧的指針
}VNode,AdjList[MaxVertexNum];
typedef struct
{
    AdjList vertices;  // 鄰接表
    int vexnum,arcnum; // 圖的頂點數和弧數
}ALGraph;

       圖的鄰接表存儲方法具有一下特點:

  1. 如果G為無向圖,則所需的存儲空間為\(O(|V|+2|E|)\);如果G為有向圖,則所需的存儲空間為\(O(|V|+|E|)\)。前者的倍數2是由於無向圖中,每條邊在鄰接表中出現了兩次。
  2. 對於稀疏圖,采用鄰接表表示將極大的節省存儲空間。
  3. 鄰接表中,給定一頂點,能很容易的找到它的所有臨邊,因為只需要讀取它的鄰接表就可以了。在鄰接矩陣中,相同的操作則需要掃描一行,花費的時間是\(O(n)\)。但是如果要確定給定的兩個頂點間是否存在邊,則在鄰接矩陣里可以立即查到在鄰接表中則需要在相應結點對應的邊表中查找另一節點,效率較低
  4. 在有向圖的鄰接表表示中,求一個給定頂點的出度只需計算其鄰接表中結點個數即可;但求其頂點的入度,則需要遍歷全部的鄰接表。因此也有人采用逆鄰接表的存儲方式來加速求解給定頂點的入度。當然這實際上與鄰接表的存儲方式是類似的。
  5. 圖鄰接表表示並不唯一,這是因為在每個頂點對應的單鏈表中,各邊結點的鏈接次序可以任意,取決於建立鄰接表的算法以及邊的輸入次序。

十字鏈表

       十字鏈表是有向圖的一種鏈式存儲結果。在十字鏈表中,對應於有向圖中的每條弧有一個結點,對應於每個頂點也有一個結點。這些節點的結構如下:

弧結點
|tailvex|headvex|hlink|tlink|info| |:-|
頂點結點
|data|fitstin|firstout| |:-|

弧結點中有5個域:其中尾域(tailvex)和頭域(headvex)分別只是弧尾和弧頭這兩個頂點在圖中的位置,鏈域hlink指向弧頭相同的下一條弧,鏈域tlink指向弧尾相同的下一條弧,info域指向該弧的相關信息。這樣弧頭相同的弧在同一個鏈表上,弧尾相同的弧也在同一個鏈表上。
頂點域中有三個域:data域存放頂點相關的數據信息,如頂點名稱,firstin和firstout兩個域分別指向以該頂點為弧頭和弧尾的第一個弧結點。

![Alt text](./1537409221825.png)
```cpp #define MaxVertexNum 100 typedef char VertexType; typedef struct ArcNode { int tailvex,headvex; struct ArcNode *hlink,*tlink; }ArcNode; typedef struct VNode { VertexType data; ArcNode *firstin,*firstout; }VNode; typedef struct { VNode xlist[MaxVertexNum]; int vexnum,arcnum; }GLGraph; ``` ####
鄰接多重表
       鄰接多重表是**無向圖**的另一種鏈式存儲方式。        在鄰接表中,容易求得頂點和邊的各種信息,但在鄰接表中求兩個頂點之間是否存在邊,或需要對邊執行刪除等操作時,需要分別在兩個頂點的邊表中遍歷,效率比較低。        與十字鏈表類似,在鄰接多重表中,每條邊用一個結點表示,其結構如下圖所示。        與十字鏈表類似,在鄰接多重表中每一條邊用一個結點表示,其結構如下圖。 |mark|ivex|ilink|jvex|jlink|info| |:-|

       其中,mark為標志域,可用以標記該條邊是否被搜索過;ivex和jvex為該邊衣服的兩個頂點在圖中的位置;ilink指向下一條依附於頂點ivex的邊;jlink指向下一條依附於頂點jvex的邊,info為指向和邊相關的各種信息的指針域。

十字鏈表

       每個頂點也有一個結點表示,它由如下所示的兩個域組成。

data firstedge

       其中,data域存儲該頂點的相關信息,firstedge域指示第一跳依附於該頂點的邊。
       在鄰接多重表中,所有依附於同一頂點的邊串聯在同一鏈表中,由於每條邊依附於兩個頂點,則每個邊結點同時連接在兩個鏈表中。

![Alt text](./1537410315557.png)
```cpp #define MaxVertexNum 100 typedef char VertexType; //圖中頂點數目最大值 typedef struct ArcNode //邊表結點 { bool mark; //訪問標記 int ivex,jvex; //分別指向該弧的兩個結點 struct ArcNode *ilink,*jlink;//分別指向兩個頂點的下一條邊 }ArcNode; typedef struct VNode //頂點表結點 { VertexType data; //頂點信息 ArcNode *firstedge; //指向第一跳依附該頂點的邊 }VNode; typedef struct { VNode adjmulist[MaxVertexNum];//鄰接表 int vexnum,arcnum; //圖中的頂點數和弧數 }AMLGraph; //AMLGraph ``` ###
圖的遍歷
       圖的遍歷是指從圖中的某一頂點出發,按照某種搜索方式沿着途中的邊對圖中所有頂點訪問一次且僅訪問一次。注意到樹是一種特殊的圖,所以樹的遍歷實際上也可以看做是一種特殊的圖的遍歷。圖的遍歷是圖的一種基本的操作,其他許多操作都建立在圖的遍歷操作基礎之上。        圖的遍歷主要有兩種算法:廣度優先搜索和深度優先搜索。 ####
廣度優先搜索(Breadth-First-Search)
       廣度優先搜索(BFS)類似於二叉樹的層序遍歷算法,它的基本思想是:首項訪問起始頂點v,接着由v出發,依次訪問v的各個未訪問過的鄰接頂點$w_1,w_2,w_3,\ldots,w_i$,然后依次訪問它們所有未被訪問過的鄰接頂點……以此類推,知道所有頂點都被訪問過位置。類似的思想還將應用於Dijkstra單源最短路徑算法和prime最小生成樹算法。        廣度優先搜索是一種分層的查找過程,每向前走一步可能訪問一批結點,不想深度優先搜索那樣有回退的情況。因此他不是一個遞歸的算法,為了實現逐層的訪問,算法必須借助一個輔助隊列,以記憶正在訪問的頂點的下一層頂點。
廣度優先搜索偽代碼
```cpp #define MaxVertexNum 100 void BFSTraverse(Graph G) { for(int i=0;i =0;w=NextNeighbor(G,v,w)) if(!visited[w]) { visit(w); visited[w]=true; EnQueue(Q,w); } } } ``` BFS復雜度分析: 1. 不論是鄰接表還是鄰接矩陣的存儲方式,BFS算法都需要借助一個輔助隊列Q,n個頂點均需入隊一次,在最壞的情況下,空間復雜度為$O(|V|)$。 2. 當采用鄰接表存儲方式時,每個頂點均需要搜索一次(或者入隊一次)姑時間復雜度為$O(|V|)$,在搜索任意一頂點的臨接點時,每條邊需要訪問一次,故時間復雜度為$O(|E|)$。算法的總時間復雜度為$O(|V|+|E|)$。當采用鄰接矩陣存儲方式時,查找每個頂點的臨接點所需的時間為$O(|V|)$,故算法的時間復雜度為$O(|V|^2)$。

BFS算法求解單源最短路徑問題

       如果圖\(G=(V,E)\)為非帶權圖,定義從頂點u到頂點v的最短路徑\(d(u,v)\)為從u到v的任何路徑中最少的邊數;如果沒有通路,則為\(d(u,v)=\infty\)
       使用BFS,我們可以求解一個滿足上述定義的非帶權路徑的單源最短路徑問題,這是由廣度優先搜索總是按照距離由近到遠來遍歷圖中每個頂點的性質決定的。
       BFS算法求解單源最短路徑問題的算法如下:

void BFS_MIN_Distance(Graph G,int u)
{
    for(int i=0;i<G.vexnum;i++)
        d[i]=INT_MAX;
    visited[u]=true;
    d[u]=0;
    EnQueue(Q,u);
    while(!IsEmpty(Q))
    {
        DeQueue(Q,u);
        for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
        {
            if(!visited[w])
            {
                visited[w]=true;
                d[w]=d[u]+1;
                EnQueue(Q,w);
            }
        }
    }
}

       與廣度優先搜索不同,深度優先搜索\((DFS)\)類似於樹的先序遍歷。正如其名稱中所暗含的意思一樣,這種搜索算法所遵循的策略是盡可能“深”的搜索一個圖。它的基本思想如下:首先訪問圖中某一起始頂點v,然后從v出發,訪問與v鄰接且未被訪問的任一定點\(w_1\),再訪問與\(w_1\)鄰接且未被訪問的任意頂點\(w_2\),……重復上述過程。當不能再繼續向下訪問時,一次退回到最近被訪問的頂點,若他還有鄰接頂點未被訪問過,則從該點開始繼續上述搜索過程,知道搜索頂點均被訪問過為止。
       一般情況下,其遞歸形式的算法非常簡潔。下面描述其算法過程。

#define MAX_VERTEX_NUM 100
bool visited[MAX_VERTEX_NUM];
void DFSTraverse(Graph G)
{
    for(v=0;v<G.vexnum;i++)
        visited[v]=false;
    for(v=0;v<G.vexnum;i++)
        if(!visited[v])
            DFS(G,v);
}
void DFS(Graph G,int v)
{
    visit(v);
    visited[v]=true;
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighor(G,v,w))
        if(!visited[w])
            DFS(G,w);
}

DFS算法性能分析

  1. DFS算法是一個遞歸算法,需要借助一個遞歸工作棧,故她的空間復雜度為\(O(|V|)\)

  2. 遍歷圖的過程實質上是對每個頂點查找其臨接點的過程,其耗費的時間取決於所采用的存儲結構。當以鄰接表進行表示時,查找每個頂點的臨接點所需時間為\(O(|V|)\),故總的時間復雜度為\(O(|V|^2)\)。當以鄰接表表示時,查找所有頂點的臨接點所需時間為\(O(|E|)\),訪問頂點所需時間為\(O(V)\),此時,總的時間復雜度為\(O(|V|+|E|)\)

    上面的代碼BFSTraverse和DFSTraverse中添加了第二個for循環,再選取初始點,繼續進行遍歷,以防止一次無法遍歷圖中的所有頂點。

圖的應用

       本節是歷年考察的重點。圖的應用主要包括:最小生成樹,最短路徑,拓撲排序和關鍵路徑。一般而言,這部分內容直接以算法設計題形式考查的可能性很小,而更多的是結合圖的實例來考查算法的具體執行過程。此外,還需要掌握對於給定的模型建立相應的圖去解決問題。

最小生成樹(Minimum-Spaning-Tree)

       一個連通圖的生成樹是圖的極小連通子圖,它包含圖中所有頂點,並且只包含極可能少的邊。這意味着對於生成樹來說,若砍去它的一條邊,就會是生成樹變成非連通圖;若給它增加一條邊,就會形成圖中的一條回路。
       對於一個帶權連通無向圖\(G=(V,E)\),生成樹不同,每棵樹的權(即樹中所有邊上的權值之和)也可能同。
       設R為G的所有生成樹的集合,若T為R中邊的權值之和最小的那棵生成樹,則稱T為G的最小生成樹。

不難看出,最小生成樹具有如下性質:
1. 最小生成樹不是唯一的,即最小生成樹的樹形不唯一,R中可能有多個最小生成樹。當圖G中各邊權值互不相等時,G的最小生成樹是唯一的;若無向連通圖G的邊比頂點數少1,即G本身就是一棵樹,G的最小生成樹就是其本身。
2. 最小生成樹的邊的權值之和總是唯一的,雖然最小生成樹不唯一,但其對應的邊的權值之和是唯一的,而且是最小的。
3. 最小生成樹的邊數為頂點數減 1。

       構造最小生成樹有多種算法,但大多數算法都利用了最小生成樹的下列性質:
       假設\(G=(V,E)\)是一個帶權連通無向圖,U是頂點集V的一個非空子集。若\((U,V)\)是一條具有最小權值的邊,其中\(u\in U,v\in {V-U}\),則必存在一棵包含邊(u,v)的最下生成樹。

Prime算法

隨意選一個點,開始建立該點集,和其他點的路徑長度關系,有路徑就有沒有的話 設置一個\(\infty\),然后開始在路徑的集合里面尋找最短的到達一個點的路徑,將這個點加到該點集之中。然后因為加入了新的點,這個時候點集就改變了,我們需要更新一下新的點集到其余點的路徑,然后再次選出一個該點集到沒有加入該點集的點的最短的路徑的一個點。然后就這樣一直搞。

KruSkal算法

按照路徑的長度進行從小到大的排序,排序完畢之后,選出最小的一條邊,作為當前長度。然后選出第二小的邊,檢查是否會成環,不會的話把長度加起來,然后選第三小的邊,檢查是否會成環,不會的話把長度加起來,然后選第四小的邊。。。。知道跳出來 頂點-1條邊。
以前做的《布線問題》作為例題,來解釋Prime和Kruskal。

最短路徑

Dijkstra算法

和Prime是一樣的,不過Prime是最小生成樹,計算的是將這些點連起來花費的最小代價。而Dijkstra計算的是,從某點開始到其他點花費的最小代價。
假設從1開始出發,選取一個目前1到那個點最近的點,將該點標記為已訪問,然后從1頭過這個點我到達其他點的距離會不會更近,如果更近的話,更新一下距離數組。然后從距離數組中選取出另一個未被加入的點,並且是距離1距離最小的點,假設讓1通過該點到達其他點會不會更近,如果更近的話更新一下距離數組。

Floyd算法

多源最短路徑:核心代碼如下
	for(k=1;k<=n;k++)    //Floyd核心算法...
    {             
        for(i=1;i<=n;i++)       //  所有的 路 都讓   k  加進去試試  
        {
            for(j=1;j<=n;j++)      //如果  從  i到j的路上 有k 走的會更輕松的話 , 那就讓 k 去吧 
            {
                if(e[i][j]>e[i][k]+e[k][j])      //   判斷 是否會 更加輕松       
                    e[i][j]=e[i][k]+e[k][j];
             }
        }
     }
三層for循環,如果從i到j路過k的話更快就更新一下距離數組。

拓撲排序

       有向無環圖:一個有向圖中不存在環,則稱為有向無環圖,簡稱DAG圖。
       AOV網:如果用DAG圖表示一個工程,其頂點表示活動,用有向邊\(<V_i,V_j>\)表示活動\(V_i\)必須先於活動\(V_j\)進行的這樣一種關系,則這種有向圖稱為頂點表示活動的網絡記為AOV網。在AOV網中,活動\(V_i\)\(V_j\)的直接前驅,活動\(V_j\)\(V_i\)的直接后繼,這種前驅和后繼關系具有傳遞性,且任何活動\(V_i\)不能以它自己作為自己的前驅或后繼。
       拓撲排序:在圖論中,由一個有向無環圖的頂點組成的序列,當且僅當滿足下列條件時,稱為該圖的一個拓撲排序。

  1. 從DAG圖中選擇一個沒有前驅的頂點並輸出。
  2. 從圖中刪除該頂點和所有以它為為起點的有向邊。
  3. 重復1和2直到當前的DAG圖為空或當前圖中不存在無前驅的頂點為止。而后一種情況則說明有向圖中必然存在環。
           拓撲序:如果圖中有從v到w有一條有向路徑,則v一定排在w之前。滿足此條件的頂點序列稱為一個拓撲序。
           獲得一個拓撲序的過程就是拓撲排序。
           \(AOV\)如果有合理的拓撲序,則必定是有向無環圖\((Directed Acyclic Graph,DAG)\)
![Alt text](./1537509990454.png)

這個東西就意味着V在開始之前就必須結束。

void TopSort()
{
    for(cnt=0;cnt<|V|;cnt++)
    {
        V=未輸出的入度為0的頂點;
        if(這樣的V不存在)
        {
            Error("圖中有回路");
            break;
        }
        輸出V,或者記錄V的輸出序號;
        for(V的每個臨接點W)
        {
            Indegree[W]--;// 每個臨接點的入度-1。
        }
    }
}
void TopSort()
{
    for(圖中每個頂點V)
        if(InDegree[V]==0)
            EnQueue(V,Q);
    while(!IsEmpty(Q))
    {
        V=DeQueue(Q);
        輸出V,或者記錄V的輸出序號;
        cnt++;
        for(V的每個臨接點W)
            if(--Indegree[W]==0)
                EnQueue(W,Q);
    }
    if(cnt!=|V|)
        Error("圖中有回路");
}

關鍵路徑

       在帶權有向圖中,以頂點表示事件,有向邊表示活動,邊上的權值表示完成活動需要的開銷,則這種圖稱為\(AOE\)網。
       \(AOE\)網具有以下兩個性質:

  1. 只有在某頂點所代表的時間發生后,從該頂點出發的各有向邊所代表的活動才可以進行;
  2. 只有在進入某一頂點的各有向邊,所代表的活動都已經結束時,該頂點所代表的事件才可以發生。
           在AOE網中僅有一個入度為0的頂點,稱為開始頂點(源點),它表示整個工程的開始;網中也僅存在一個出度為0的頂點,稱為結束頂點(匯點),它表示整個工程的結束。
           在AOE網中有些活動是可以並行進行的,從源點到匯點的有向路徑可能有多條,並且這些路徑長度可能不同。完成不同路徑上的活動所需時間雖然不同,但是只有所有路徑上的活動都完成了,整個工程才能算結束了。因此,從源點到匯點的所有路徑中,具有最大路徑長度的路徑稱為關鍵路徑,我們將關鍵路徑上的活動稱為關鍵活動
           完成整個工程的最短時間就是關鍵路徑的長度,也就是關鍵路徑上各活動花費開銷的總和。這是因為關鍵活動影響了整個工程的時間,即如果關鍵活動不能按時完成的話,整個工程的完成時間就會增長。因此只要找到了關鍵活動就找到了關鍵路徑也就可以得出最短完成時間

事件\(V_k\)的最早發生時間VE(K)
       他是指從最早開始頂點V到\(V_k\)的最長路徑長度。事件的最早發生時間決定了所有人從\(V_k\)開始的活動最早能夠開工的最早時間。
時間\(V_k\)的最早發生時間\(VE(k)\)
       它是指從開始頂點V到\(V(k)\)的最長路徑長度。時間的最早發生時間決定了所有從\(V_k\)開始的活動能夠開工的最早時間。可以用下面的遞推公式進行計算。

\(ve(源點)=0\)
\(ve(k)=Max\{ve(j)+Weight(v_j,v_k)\}\)\(Weight(v_j,v_k)\)表示\(<v_j,v_k>\)上的權值。

時間\(v_k\)的最遲發生時間\(vl(k)\)
       它是指在不推遲整個工程完成的前提下,即保證他所指向的事件\(v_i\)\(ve(i)\)時刻能夠發生時,該事件最遲必須發生的時間。


活動\(a_i\)最早開始時間\(e(i)\)
它是指該活動的七點所表示的事件最早發生時間。如果邊\(<v_k,v_j>\)表示活動\(a_i\),則有\(e(i)=ve(k)\)


活動\(a_i\)的最遲開始時間。
它是指該活動的終點所表示的事件最遲發生時間與該活動所需時間之差。


一個活動\(a_i\)的最遲開始時間\(l(i)\)和其最早開始時間\(e(i)\)的差額\(d(i)=l(i)-e(i)\)
它是指該活動完成的時間余量,是在不增加整個工程所需的總時間的情況下,活動\(a_i\)可以拖延的時間。如果一個活動的時間余量為0時,說明該活動必須要如期完成,否則就會拖延完成整個工程的進度,所以成\(l(i)-e(i)=0\),即\(l(i)=e(i)\)的活動\(a_i\)是關鍵活動。

求關鍵路徑的算法步驟如下:(事件是結點,活動是邊。)

  1. 求AOE網中所有事件的最早發生時間\(ve()\)
  2. 求AOE網中所有事件的最遲發生時間\(vl()\)
  3. 求AOE網中所有活動的最早開始時間\(e()\)
  4. 求AOE網中所有活動的最遲開始時間\(l()\)
  5. 求AOE網中所有活動的差額\(d()\),找出所有\(d()=0\)的活動構成關鍵路徑。
![Alt text](./1537528542874.png)
> 上圖中的關鍵路徑就是$v_1,v_3,v_4,v_6$。
![Alt text](./1537528557139.png)
       對關鍵路徑,我們需要注意以下幾點。 1. 關鍵路徑上的所有活動都是關鍵活動,它是決定整個工程的關鍵因素,因此可以通過加快關鍵活動來縮短整個工程的工期。但也不能任意縮短關鍵活動,因為一旦縮短到一定程度,該關鍵活動就可能變成非關鍵活動了。 2. 網中的關鍵路徑並不唯一。且對於有幾條關鍵路徑的網,只提高一條關鍵路徑上的關鍵活動速度並不能縮短整個工程的工期,只有加快那些包括在所有關鍵路徑上的關鍵活動才能達到縮短工期的目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM