日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

王道408数据结构——第六章 图

發布時間:2023/12/4 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 王道408数据结构——第六章 图 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 一、圖的基本概念
  • 二、圖的儲存
    • 鄰接矩陣
    • 鄰接表
    • 十字鏈表
    • 鄰接多重表
  • 三、圖的基本操作
  • 四、圖的遍歷
    • 廣度優先搜索(BFS)
    • 深度優先搜索(DFS)
    • 圖的遍歷和圖的連通性
  • 五、最小生成樹
    • Prim算法
    • Kruskal算法
  • 六、最短路徑
    • Dijkstra求單源最短路徑
    • Floyd算法求解各頂點間的最短路徑問題
  • 七、有向無環圖(DAG圖)描述表達式
  • 八、拓撲排序
  • 九、關鍵路徑

一、圖的基本概念

圖G有定點集V和邊集E組成,記為G=(V,E),其中V(G)表示圖G中頂點的有限非空集;E(G)表示圖G中頂點之間的關系(邊)集合。用∣V∣|V|V表示圖G中頂點個數,用∣E∣|E|E表示圖G中邊的條數。圖不能是空圖,最少要有一個頂點。

對于無向圖,|E|的取值范圍為0到n(n?1)/2n(n-1)/2n(n?1)/2,有n(n?1)/2n(n-1)/2n(n?1)/2條邊的無向圖稱為完全圖
對于有向圖,|E|的取值范圍為0到n(n?1)n(n-1)n(n?1),有n(n?1)n(n-1)n(n?1)條弧的有向圖稱為完全圖

若圖G和圖G’頂點相同,E(G’)是E(G)的子集,則成G’是G的生成子圖

無向圖中,任意兩個頂點都是連通的。稱為連通圖。無向圖中的極大連通子圖稱為連通分量。假設一個圖有n個頂點,如果邊數小于n-1,其必定是非連通圖。

有向圖中,如果有一對頂點v、w,從v到w和從w到v之間都有路徑,稱這兩個頂點是強連通的。若圖中任何一對頂點都是強連通的,稱此圖為前連通圖。有向圖的極大強連通子圖稱為強連通分量。假設一個圖有n個頂點,至少需要n-1條邊,構成強連通分量,此時為一個環路。

連通圖的生成樹是包含圖中全部頂點的一個極小連通子圖。若圖中頂點數為n,則它的生成樹有n-1條邊。

無向圖的全部頂點之和等于邊數的兩倍,;
有向圖全部頂點的入度之和與出度之和相等,并且等于邊數。有向圖頂點的度為入度和出度之和。

一個頂點的入度為0、其余頂點入度均為1的有向圖,稱為有向樹

二、圖的儲存

鄰接矩陣

用一個一維數組儲存圖中頂點的信息,有一個二維數組(鄰接矩陣)儲存圖中邊的信息。
無向圖的鄰接矩陣是對稱矩陣,實際儲存時只需儲存三角矩陣元素。

鄰接矩陣的空間復雜度為O(∣V∣2)O(|V|^2)O(V2)

可以很方便地確定兩個頂點之間是否有邊相連,但要確定圖中有多少條邊,必須遍歷每個元素。

適合表示稠密圖

鄰接矩陣儲存結構定義如下

typedef struct{vertexType vex[MaxVertexNum]; // 頂點表edgeType edge[MaxVertexNum][MaxVertexNum]; // 鄰接矩陣(邊表)int vexNum, edgeNum; // 當前頂點數和邊數 }MGraph;

鄰接表

對圖中每個頂點建立一個單鏈表(邊表、出邊表),每個單鏈表中的結點表示依附與該頂點的邊(對于有向圖,表示以該頂點為尾的弧)。邊表的頭指針和頂點的數據采用順序存儲(頂點表)。

對于無向圖,所需儲存空間為O(2∣E∣+∣V∣)O(2|E|+|V|)O(2E+V)
對于有向圖,所需存儲空間為O(∣E∣+∣V∣)O(|E|+|V|)O(E+V)

可以很方便找到一個頂點的所有臨邊。
在有向圖中,求頂點的出度只需計算鄰接表的結點個數,但求入度需要遍歷所有鄰接表。

適合表示稀疏圖,能極大節省存儲空間。

鄰接表儲存結構定義如下

typedef struct{ // 頂點結點vertexType data;ArcNode *first; // 指向第一個依附該頂點的弧 }VerNode, VerList[MaxVertexNum];struct ArcNode{ // 邊結點int vertex; // 該邊指向的頂點序號edgeType data;struct ArcNode *next; // 指向下一個邊 }typedef struct{VerList VerList; // 鄰接表int vexNum, edgeNum; }AlGraph;

十字鏈表

是有向圖的一種鏈式儲存結構。每條弧和每個頂點都有一個結點表示。頂點結點順序存儲。

弧結點結構tailvexheadveshlinktlinkinfo
作用尾域,標識弧尾頂點頭域,標識弧頭頂點指向弧頭相同的下一條弧指向弧尾相同的下一條弧攜帶弧的相關信息
頂點結點結構datafirstinfirstout
作用存放頂底數據信息指向以該頂點為弧頭的第一個弧結點指向以該頂點為弧尾的第一個弧結點


在十字鏈表中,既容易找到以一個頂點為尾的弧,又容易找到以一個頂點尾頭的弧,因而容易求得入度和出度。

鄰接多重表

是無向圖的一種鏈式存儲結構。每條邊和每個頂點也各用一個結點表示。

頂點結點結構markivexilinkjvexjlinkinfo
作用標志域,記錄該邊是否被搜索過標識邊依附的第一個頂點指向下一條依附ivex的邊標識邊依附的另一個頂點指向下一條依附jvex的邊攜帶相關信息


在鄰接多重表中,所有依附域同一頂點的邊串聯在同一鏈表中。由于每條邊依附于兩個頂點,所以每個邊結點同時鏈接在兩個鏈表中。
對于無向圖,其鄰接多重表和鄰接表的差別僅在于,同一條表在鄰接表中用兩個結點表示,而在鄰接多重表中只有一個結點。

三、圖的基本操作

  • adjacent(G, x, y):判斷圖G中是否存在邊<x, y>
  • neighbor(G, x):列出圖G中于頂點x相鄰的邊
  • insertVertex(G, x):在圖中插入頂點x
  • deleteVertex(G, x):在圖中刪除頂點x
  • addEdge(G, x, y):在圖中添加邊<x, y>
  • removeEdge(G, x, y):在圖中刪除邊<x, y>
  • firstNerghbor(G, x):求頂點x的第一個鄰接點
  • nextNerghbor(G, x, y):返回除y以外的頂點x的下一個鄰接點
  • getEdgeValue(G, x, y)
  • setEdgeValue(G, x, y, v)

四、圖的遍歷

在遍歷圖的過程中,必須記下每個已訪問過的頂點。

廣度優先搜索(BFS)

類似樹的層序遍歷,廣度優先搜索是一個分層的查找過程,沒有回退的過程,必須借助一個輔助隊列,記憶正在訪問的頂點的下一層頂點。

int visited[MAX_VERTEX_NUM];void BFSTraverse(Graph G){for(int i=0; i<G.vexNum; i++)visited[i] = 0;for(int i=0; i<G.vexNum; i++)if( !visit[i] )BFS(G, i); }void BFS(Graph G, int v){initQueue(Q);visit(v);visited[v] = 1;enQueue(Q, v);while( !isEmpty(Q) ){deQueue(Q, v);for(w=firstNeighbor(G, v); w>=0; w=nextNeighbor(G, v, w)){if(!visited[w]){visit(w);visited[w] = 1;enQueue(Q, w);}}}}

無論采用鄰接表還是鄰接矩陣,BFS都需要借助一個輔助隊列,所有頂點均需入隊一次。最壞情況下,空間復雜度為O(∣V∣)O(|V|)O(V)
采用鄰接表存儲時,每個頂點均需被搜索依次,時間復雜度為O(∣V∣)O(|V|)O(V);在搜索任一頂點的邊時,每一條邊至少訪問依次,總時間復雜度為O(∣E∣)O(|E|)O(E)。算法總的時間復雜度為O(∣V∣+∣E∣)O(|V|+|E|)O(V+E)
采用鄰接矩陣存儲時,查找每個頂點的所有鄰接點的時間為O(V)O(V)O(V),算法總的時間復雜度為O(∣V∣2)O(|V|^2)O(V2)

借助BFS,可以求解非帶權圖的單源最短路徑問題。

在廣度遍歷中,可以得到一棵遍歷樹,稱為廣度優先生成樹。鄰接矩陣產生的生成樹是唯一的,而鄰接表產生的生成樹不唯一。

深度優先搜索(DFS)

類似樹的先序遍歷,先盡可能“深”的搜索一個圖,當不能繼續向下訪問時,依次退回最近被訪問的頂點,若其還有未被訪問的鄰接頂點,則從這個鄰接頂點繼續該搜索過程。

DFS是一個遞歸的過程,需要借助一個遞歸工作棧,空間復雜度為O(∣V∣)O(|V|)O(V)
采用鄰接表存儲時,訪問所有頂點的時間復雜度為O(∣V∣)O(|V|)O(V),查找所有頂點的鄰接點的總時間復雜度為O(∣E∣)O(|E|)O(E),故算法總的時間復雜度為O(∣V∣+∣E∣)O(|V|+|E|)O(V+E)
采用鄰接表存儲是,查找一個頂點的所有鄰接點所需時間為O(|V|),故總的時間復雜度為O(∣V∣2)O(|V|^2)O(V2)

借助DFS,可以求解有向無環圖的拓撲排序問題。

在深度遍歷中,可以得到一棵遍歷樹,稱為深度優先生成樹。鄰接矩陣產生的生成樹是唯一的,而鄰接表產生的生成樹不唯一。

圖的遍歷和圖的連通性

對于無向圖,若圖是連通的,則從任一頂點出發,僅需一次遍歷就可以訪問圖中的所有頂點。
對于有向圖,若從初始頂點到圖中每個頂點都有路徑,則能訪問到圖中的所有頂點。一個有向圖的連通子圖分為強連通分量和非強連通分量,非強連通分量調用一次搜索過程無法訪問到所有頂點。

五、最小生成樹

最小生成樹具有如下特征:

  • 最小生成樹不是唯一的。但圖中各邊的權值互不相等是,生成樹唯一。
  • 若無向連通圖的邊數比頂點數少1,它本身就是其的最小生成樹。
  • 最小生成樹邊的權值之和總是唯一的,且是最小的。

Prim算法

初始時從圖中任取一頂點加入樹T,此時樹中只含有一個頂點。之后選擇一個與T中頂點距離最近的頂點,將頂點和相應的邊加入T中。每次操作,T中的頂點數和邊數都增加1,直到所有頂點都加入T。
prim算法基于貪心策略,其簡單實現如下

def prim(G):T = set() # 初始化空邊集U = {w} # 初始化頂點集,添加任一頂點w# 若樹中未包含全部頂點,選擇一個加入while not (V - U): # (u,v)是u∈U,v∈(V-U)且權值最小的邊# 此處最壞情況需要遍歷所有n個頂點,時間復雜度為O(n)find a edge (u, v) U.add(v) # 將選定的點加入頂點集T.add((u, v)) # 將選定的邊加入邊集

算法的時間復雜度為O(∣V∣2)O(|V|^2)O(V2),適合求解稠密圖的最小生成樹。

Kruskal算法

初始時只有n個頂點而無邊的非連通圖T={ V,{ } },每個頂點自成一個連通分量。然后按邊權值從小到大的順序,查看當前為被選取且權值最小的邊,若該邊依附的兩個頂點落在T中不同的連通分量中,則將此邊加入T。
kruskal算法基于貪心策略,其簡單實現如下

def kruskal(G):T = V # 初始化樹,僅含所有頂點numS = n # 連通分量數while numS > 1:# 從邊集中找到權值最小的邊pop a edge with shortest length (u, v)if (u ,v) belong to different connected components:T.add((u, v)) # 將此邊加入生成樹中numS = numS - 1

通常在kruskal算法中,采用堆來存放邊的集合,每次選擇邊只需O(log?∣E∣)O(\log |E|)O(logE)的時間。此外,由于生成樹T中的所有邊可視為一個等價類,每次添加新邊的過程類似求解等價類的過程,可以采用并查集的數據結構來描述T,構造T總的時間復雜度為O(∣E∣log?∣E∣)O(|E|\log |E|)O(ElogE)。適合求解稀疏圖的最小生成樹。

六、最短路徑

把帶權路徑長度最短的那條路徑稱為最短路徑
重要性質:兩點之間的最短路徑也包含了路徑上其他點間的最短路徑。

Dijkstra求單源最短路徑

設置一個集合S,記錄已求得的最短路徑的頂點。初始時把源點v0v_0v0?放入SSS。集合S每并入一個新頂點viv_ivi?,都要修改v0v_0v0?到集合V?SV-SV?S中頂點的當前最短路徑長度。
設置兩個輔助數組:

  • dist[]:記錄從源點v0v_0v0?到其他各頂點當前的最短路徑長度。它的初態為:若從v0v_0v0?viv_ivi?有弧,則dist[i]設為弧上權值,否則置為∞\infty
  • path[]:path[i]表示從源點到頂點i最短路徑的前驅結點。在算法結束時,可根據其值追溯到源點的最短路徑。

假設從頂點0出發,即v0=0v_0=0v0?=0,集合SSS最初只包含頂點0,鄰接矩陣arcs表示帶權有向圖,arcs[i][j]表示有向邊<i, j>的權值。若不存在有向邊,arcs[i][j]置為∞\infty

Dijkstra算法基于貪心策略,步驟如下:

  • 初始化:集合S初始為{0},dist[]初始值為dist[i] = arcs[0][i]。
  • 從頂點集合V?SV-SV?S中選出vjv_jvj?vjv_jvj?是集合V?SV-SV?S中到v0v_0v0?距離最短的頂點,即滿足dist[j]=min{dist[i]∣vi∈V?S}dist[j] =min \{dist[i]\mid v_i\in V-S\}dist[j]=min{dist[i]vi?V?S},此時 vjv_jvj?就是當前求得的一條從v0v_0v0?出發的最短路徑的終點。再令S=S∪{j}S=S\cup\{j\}S=S{j}
  • 修改從v0v_0v0?到集合V?SV-SV?S上所有頂點vkv_kvk?可達的最短路徑長度:
    若dist[j] + arcs[j][k] <dist[k], 更新dist[k] = dist[j] + arcs[j][k]。
  • 重復二、三步n-1次,直到所有頂點都包含在S中。
  • Djkstra算法并不適用于帶負權值的有向圖。

    算法時間復雜度為O(∣V∣2)O(|V|^2)O(V2)

    Floyd算法求解各頂點間的最短路徑問題

    遞推產生一個n階方陣序列A(?1),A(0),...,A(k),...,A(n?1)A^{(-1)}, A^{(0)},...,A^{(k)},...,A^{(n-1)}A(?1),A(0),...,A(k),...,A(n?1),其中A(k)[i][j]A^{(k)}[i][j]A(k)[i][j]表示從頂點viv_ivi?vjv_jvj?的路徑長度,k表示繞行第k個頂點的運算步驟。
    初始時,對于任意兩個頂點,若它們之間存在弧,則以此邊上的權值作為它們之間的最短路徑長度;若它們不存在有向邊,則置為∞\infty
    以后逐步嘗試在原路徑中加入頂點k作為中間頂點,若增加中間頂點后,它們之間的路徑長度比原來的路徑減少了,則以此新路徑代替原路徑。

    算法描述如下:
    定義一個n階反正序列A(?1),A(0),...,A(n?1)A^{(-1)}, A^{(0)},...,A^{(n-1)}A(?1),A(0),...,A(n?1),其中

    • A(?1)[i][j]=arcs[i][j]A^{(-1)}[i][j]=arcs[i][j]A(?1)[i][j]=arcs[i][j]
    • A(k)=min?{A(k?1)[i][j],A(k?1)[i][k]+A(k?1)[k][j]}A^{(k)}=\min\{A^{(k-1)}[i][j],A^{(k-1)}[i][k]+A^{(k-1)}[k][j]\}A(k)=min{A(k?1)[i][j],A(k?1)[i][k]+A(k?1)[k][j]}

    Floyd算法是一個迭代的過程,每迭代一次,在從viv_ivi?vjv_jvj?的最短路徑上就多考慮一個頂點。經過n次迭代后,所得到的A(n?1)[i][k]A^{(n-1)}[i][k]A(n?1)[i][k]就是viv_ivi?vjv_jvj?的最短路徑,方陣A(n?1)A^{(n-1)}A(n?1)保存了任意一對頂點之間的最短路徑長度。

    Floyd算法允許圖中有帶負權值的邊,但不允許有包含帶負權值邊的回路。
    Floyd算法同樣適合帶權無向圖,因為無向圖可視為權值相同往返二重邊的有向圖。

    算法的時間復雜度為O(∣V∣3O(|V|^3O(V3),但對于中等規模的輸入,仍然有效。

    七、有向無環圖(DAG圖)描述表達式

    DAG圖是描述含有公共子式的表達式的有效工具。
    用二叉樹描述表達式時,相同的子式會重復出現,使用DAG圖可以對相同子式的共享,從而節省存儲空間。

    八、拓撲排序

    AOV網:若用DAG圖表示一個工程,其頂點表示活動,用有向邊<vi,vj><v_i,v_j><vi?,vj?>表示活動ViV_iVi?必須先于ViV_iVi?進行,則間這種有向圖稱為頂點表示活動的網絡,記為AOV網。
    拓撲排序:由一個有向無環圖的頂點組成的序列,當且僅當滿足下列條件時:

  • 每個頂點出現且僅出現一次;
  • 若頂點A在序列中排在B的前面,則在圖中不存在從B到A的路徑。
  • 稱這個序列為該圖的一個拓撲排序。每個AOV網都有一個或多個拓撲排序序列。

    拓撲排序常用算法如下

  • 從AOV網中選擇一個沒有前驅的頂點并輸出;
  • 從網中刪除該頂點和以該頂點為起點的所有邊;
  • 重復前面兩部直到AOV網為空或網中不存在無前驅的頂點位置。后一種情況說明有向圖中存在環
  • 由于輸出每個頂點的同時還有刪除以它為起點的邊,故拓撲排序的時間復雜度為O(∣V∣+∣E∣)O(|V|+|E|)O(V+E)

    由于AOC網中各頂點地位平等,每個頂點的編號都是人為的,因此可以按拓撲排序的結果重新編號,生成AOV網的新的鄰接存儲矩陣,這種鄰接矩陣可以時三角矩陣。對于一個圖,若其鄰接矩陣是三角矩陣,其必存在拓撲排序。

    九、關鍵路徑

    AOE網:在帶權有向無環圖中,若以頂點表示事件,以有向邊表示活動,以邊上的權值代表活動的開銷,則稱其為用邊表示活動的圖,記為AOE網。
    在AOE網中僅有一個入度為0的點,稱為開始頂點(源點),表示整個工程的開始;僅有一個出度為0的點,稱為結束頂點(匯點),表示整個工程的結束。
    從源點到匯點的所有路徑中,具有最大路徑長度的路徑稱為關鍵路徑,它決定了完成整個工程的最短時間。關鍵路徑上的活動稱為關鍵活動

    • 事件最早發生時間ve(k)ve(k)ve(k)
      指從源點viv_ivi?到頂點vkv_kvk?的最長路徑長度,決定了所有從vkv_kvk?開始的活動能夠開工的最早時間。可以用下面的遞推公式計算:
      {ve(源點)=0ve(k)=max?{ve(j)+weight(vj,vk)},vk為vj的任意后繼\left\{ \begin{array}{l} ve(源點)=0\\ ve(k)=\max\{{ve(j)}+weight(v_j,v_k)\},v_k為v_j的任意后繼 \end{array} \right.{ve()=0ve(k)=max{ve(j)+weight(vj?,vk?)},vk?vj??
      計算ve()ve()ve()時,按從前往后的順序進行,可以在拓撲排序的基礎上進行。
    • 事件最遲發生時間vl(k)vl(k)vl(k)
      指在不推遲整個工程完成的前提下(即保證它的后繼事件在其最遲發生時間內能夠發生),該事件最遲必須發生時間。可以用下面的遞推公式計算:
      {vl(匯點)=ve(匯點)vl(k)=min?{vl(j)?weight(vk,vj)},vk為vj的任意前驅\left\{ \begin{array}{l} vl(匯點)=ve(匯點) \\ vl(k)=\min\{vl(j)-weight(v_k,v_j)\},v_k為v_j的任意前驅 \end{array} \right. {vl()=ve()vl(k)=min{vl(j)?weight(vk?,vj?)}vk?vj??
      在計算vl()vl()vl()時,按從后往前的順序進行,可以在逆拓撲排序的基礎上計算(可以在計算ve()時設置一個棧記錄拓撲序列,拓撲排序結束后從棧頂至棧底變為逆拓撲排序序列)
    • 活動最早開始時間e(i)e(i)e(i)
      指活動弧的起點所表示的事件的最早發生時間。若<vk,vj><v_k,v_j><vk?,vj?>表示活動aia_iai?,則有e(i)=ve(k)e(i)=ve(k)e(i)=ve(k)
    • 活動最遲開始時間l(i)l(i)l(i)
      指活動弧的終點所表示的事件的最遲發生事件與該活動所需時間的差值。若<vk,vj><v_k,v_j><vk?,vj?>表示活動aia_iai?,則有l(i)=vl(j)?weight(vk,vj)l(i)=vl(j)-weight(v_k,v_j)l(i)=vl(j)?weight(vk?,vj?)
    • 活動最早開始時間和最遲開始時間的差額d(i)d(i)d(i)
      指該活動完成的時間余量,即在不增加整個工程所需總時間的情況下,活動aia_iai?可以拖延的時間。d(i)=l(i)?e(i)d(i)=l(i)-e(i)d(i)=l(i)?e(i)
      若一個活動的時間余量為0,即l(i)=e(i)l(i)=e(i)l(i)=e(i),說明該活動必須要如期完成,否則就會拖延整個工程的進度,稱其為關鍵活動。

    求解關鍵路徑的算法步驟如下:

  • 從源點出發,令ve(源點)=0ve(源點)=0ve()=0,按拓撲有序求其余頂點的最早發生時間;
  • 從匯點出發,令le(匯點)=ve(匯點)le(匯點)=ve(匯點)le()=ve(),按逆拓撲有序求其余頂點的最遲發生時間;
  • 根據各頂點的ve()ve()ve()值求所有弧的最早開始時間;
  • 根據各頂點的vl()vl()vl()值求所有弧的最遲開始時間;
  • 求AOE網中所有活動的差額,找出所有d()=0d()=0d()=0的活動,構成關鍵路徑。
  • 關鍵路徑上的所有活動都是關鍵活動,可以通過加快關鍵活動來縮短整個工程的工期。但不能任意縮短關鍵活動,因為一旦縮短到一定程度,關鍵活動就可能會變成非關鍵活動。
    AOE網中的關鍵路徑不是唯一的,對于有多條關鍵路徑的AOE網,只提高一條關鍵路徑上的關鍵活動速度并不能縮短整個工期。

    可以判斷一個有向圖是否有環的算法:

    • 深度優先遍歷
    • 拓撲排序
    • 求關鍵路徑(預先要使用拓撲排序判斷是否有環)

    總結

    以上是生活随笔為你收集整理的王道408数据结构——第六章 图的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。