详解图的各种令人心慌的概念和四种图的存储结构(整理到吐)
寫在前面:博主是一位普普通通的19屆雙非軟工在讀生,平時最大的愛好就是聽聽歌,逛逛B站。博主很喜歡的一句話花開堪折直須折,莫待無花空折枝:博主的理解是頭一次為人,就應該做自己想做的事,做自己不后悔的事,做自己以后不會留有遺憾的事,做自己覺得有意義的事,不浪費這大好的青春年華。博主寫博客目的是記錄所學到的知識并方便自己復習,在記錄知識的同時獲得部分瀏覽量,得到更多人的認可,滿足小小的成就感,同時在寫博客的途中結交更多志同道合的朋友,讓自己在技術的路上并不孤單。
目錄:
1.圖的基本概念
???? ?? 無向圖有向圖
???? ?? 完全有向圖完全無向圖
???? ?? 弧頭和弧尾
???? ?? 入度和出度
???? ?? (V1,V2) 和 <V1,V2>
???? ?? 集合 VR 的含義
???? ?? 路徑和回路
???? ?? 權和網的含義
???? ?? 稀疏圖和稠密圖
???? ?? 連通圖和強連通圖
???? ?? 生成樹生成森林
???? ?? 極大連通圖和極小連通圖
2.圖的順序存儲結構(鄰接矩陣)
3.圖的鄰接表存儲結構
4.圖的十字鏈表存儲結構
5.圖的鄰接多重表存儲結構
1.圖的基本概念
1.1無向圖有向圖
1.無向圖:如果任意兩個頂點之間的邊都是無向邊,那么該圖稱為無向圖
2.有向圖:如果任意兩個頂點之間的邊都是有向邊,那么該圖稱為有向圖
無向圖:
有向圖:
1.2完全有向圖完全無向圖
完全圖:若圖中各個頂點都與除自身外的其他頂點有關系,這樣的無向圖稱為完全圖,完全圖分為完全有向圖和完全無向圖
完全無向圖:
完全有向圖:
在n個頂點的無向完全圖中,有n(n-1)/2條邊。在n個頂點的有向完全圖中有n(n-1)條邊
1.3弧頭和弧尾
有向圖中,無箭頭一端的頂點通常被稱為"初始點"或"弧尾",箭頭直線的頂點被稱為"終端點"或"弧頭"。
1.4入度和出度
對于有向圖中的一個頂點 V 來說,箭頭指向 V 的弧的數量為 V 的入度(InDegree,記為 ID(V));箭頭遠離 V 的弧的數量為 V 的出度(OutDegree,記為OD(V))。某個頂點的度等于這個頂點的入度加出度
1.5(V1,V2) 和 <V1,V2>
無向圖中描述兩頂點(V1 和 V2)之間的關系可以用 (V1,V2) 來表示,而有向圖中描述從 V1 到 V2 的"單向"關系用 <V1,V2> 來表示。
由于圖存儲結構中頂點之間的關系是用線來表示的,因此 (V1,V2) 還可以用來表示無向圖中連接 V1 和 V2 的線,又稱為邊;同樣,<V1,V2> 也可用來表示有向圖中從 V1 到 V2 帶方向的線,又稱為弧。
1.6集合 VR 的含義
圖中習慣用 VR 表示圖中所有頂點之間關系的集合。例如,圖 1 中無向圖的集合 VR={(v1,v2),(v1,v4),(v1,v3),(v3,v4)},圖 2 中有向圖的集合 VR={<v1,v2>,<v1,v3>,<v3,v4>,<v4,v1>}。
1.7路徑和回路
無論是無向圖還是有向圖,從一個頂點到另一頂點途徑的所有頂點組成的序列(包含這兩個頂點),稱為一條路徑。如果路徑中第一個頂點和最后一個頂點相同,則此路徑稱為回路(或環)。
并且,若路徑中各頂點都不重復,此路徑又被稱為簡單路徑;同樣,若回路中的頂點互不重復,此回路被稱為簡單回路(或簡單環)。
1.8權和網的含義
在某些實際場景中,圖中的每條邊(或弧)會賦予一個實數來表示一定的含義,這種與邊(或弧)相匹配的實數被稱為"權",而帶權的圖通常稱為網。如圖 3 所示,就是一個網結構如下:
1.9稀疏圖和稠密圖
稀疏圖和稠密圖:這兩種圖是相對存在的,即如果圖中具有很少的邊(或弧),此圖就稱為"稀疏圖";反之,則稱此圖為"稠密圖"。
稀疏和稠密的判斷條件是:e<nlogn,其中 e 表示圖中邊(或弧)的數量,n 表示圖中頂點的數量。如果式子成立,則為稀疏圖;反之為稠密圖。
1.10連通圖和強連通圖
連通圖:無向圖中,如果任意兩個頂點之間都能夠連通。
強連通圖:有向圖中,若任意兩個頂點 Vi 和 Vj,滿足從 Vi 到 Vj 以及從 Vj 到 Vi 都連通,也就是都含有至少一條通路,則稱此有向圖為強連通圖。
1.連通圖:
若無向圖不是連通圖,但圖中存儲某個子圖符合連通圖的性質,則稱該子圖為連通分量。
注意:
1.由圖中部分頂點和邊構成的圖為該圖的一個子圖,但這里的子圖指的是圖中"最大"的連通子圖(也稱"極大連通子圖")。
2.連通分量的提出是以"整個無向圖不是連通圖"為前提的,因為如果無向圖是連通圖,則其無法分解出多個最大連通子圖,因為圖中所有的頂點之間都是連通的。也就是說連通圖只有一個連通分量就是他自己
這里有幾個注意點:
如下左邊是一個無向非連通圖,右邊是他的三個連通分量
2.強連通圖:
若有向圖本身不是強連通圖,但其包含的最大連通子圖具有強連通圖的性質,則稱該子圖為強連通分量,如下的非強連通圖就有兩個強連通分量
1.11生成樹生成森林
對連通圖進行遍歷,過程中所經過的邊和頂點的組合可看做是一棵普通樹,通常稱為生成樹。生成樹也理解為包含所有頂點的極小連通子圖(極小連通子圖后邊會講)
如下左邊的連通圖對應右邊的兩種生成樹
連通圖中,由于任意兩頂點之間可能含有多條通路,遍歷連通圖的方式有多種,往往一張連通圖可能有多種不同的生成樹與之對應
連通圖中的生成樹必須滿足以下 2 個條件:
1.包含連通圖中所有的頂點;
2.任意兩頂點之間有且僅有一條通路;
因此,連通圖的生成樹具有這樣的特征,即生成樹中邊的數量 = 頂點數 - 1。
生成樹是對應連通圖來說的生成森林是對應非連通圖來說的,我們知道,非連通圖可分解為多個連通分量,而每個連通分量又各自對應多個生成樹(至少是 1 棵),因此與整個非連通圖相對應的,是由多棵生成樹組成的生成森林
舉個例子:
左邊是連通圖,右邊是連通分量,那么它所對應的生成森林為:
1.12極大連通子圖和極小連通子圖
首先明確極大連通子圖和極小連通子圖都是在無向圖中進行討論的,極大連通子圖上邊已經講了,我們看看極小連通子圖
極小連通子圖:一個子圖刪除任意一條邊子圖不再連通
2.圖的順序存儲結構(鄰接矩陣)
2.1順序存儲的概念
存儲圖中各頂點本身數據,使用一維數組就足夠了;存儲頂點之間的關系時,要記錄每個頂點和其它所有頂點之間的關系,所以需要使用二維數組。所以我們要存儲整張圖的邏輯和物理信息就必須使用兩個數組一維數組存儲數據,二維數組存儲節點之間的關系
圖,包括無向圖和有向圖;網,是指帶權的圖,包括無向網和有向網。圖和網的存儲方式的不同,指的是:在使用二維數組存儲圖中頂點之間的關系時,如果頂點之間存在邊或弧,在相應位置用 1 表示,反之用 0 表示;如果使用二維數組存儲網中頂點之間的關系,頂點之間如果有邊或者弧的存在,在數組的相應位置存儲其權值;反之用∞表示。
比如要存儲下面的無向圖和有向圖(我們重點寫二維數組):
對于A而言,我們的二維數組可以是(一維數組存儲數據就不再):
例如,arcs[0][1] = 1 ,證明從 V1 到 V2 有弧存在。且通過二階矩陣,可以很輕松得知各頂點的出度和入度,出度為該行非 0 值的和,入度為該列非 0 值的和。例如,V1 的出度為第一行兩個 1 的和,為 2 ; V1 的入度為第一列中 1 的和,為 1 。所以 V1 的出度為 2 ,入度為 1 ,度為兩者的和 3 。
對于B而言二維數組是:
每一行代表一個頂點,依次從 V1 到 V5 ,每一列也是如此。比如 arcs[0][1] = 1 ,表示 V1 和 V2 之間有邊存在;而 arcs[0][2] = 0,說明 V1 和 V3 之間沒有邊。
再比如我們要存儲下圖有向網的時候:
那么其二維數組為:
2.2順序存儲的代碼實現:
#define MaxInt 32767 //表示極大值∞ #define MVNum 100 //表示最大頂點數 typedef char VerTexType;//假設頂點的數據結構類型為char typedef int ArcType;//假設權值類型為整形 typedef struct{VerTexType vexs[MVNum];//頂點表 ArcType arcs[MVNum][MVNum];//鄰接矩陣 int vexnum;//圖的當前頂點數int arcnum;//圖的當前邊數 }AMGraph;注意∞:表示計算機允許的大于邊上所有權值的數
3.圖的鄰接表存儲結構
3.1鄰接表的存儲概念
鄰接點:在圖中,如果兩個點相互連通,即通過其中一個頂點,可直接找到另一個頂點,則稱它們互為鄰接點,鄰接指的是圖中頂點之間有邊或者弧的存在
鄰接表存儲圖的實現方式是,給圖中的各個頂點獨自建立一個鏈表,用節點存儲該頂點,用鏈表中其他節點存儲各自的鄰接點。。鄰接表既適用于存儲無向圖,也適用于存儲有向圖。
與此同時,為了便于管理這些鏈表,通常會將所有鏈表的頭節點存儲到數組中(也可以用鏈表存儲)。也正因為各個鏈表的頭節點存儲的是各個頂點,因此各鏈表在存儲臨界點數據時,僅需存儲該鄰接頂點位于數組中的位置下標即可。啥意思呢,如下圖:
拿頂點 V1 來說,與其相關的鄰接點分別為 V2 和 V3,因此存儲 V1 的鏈表中存儲的是 V2 和 V3 在數組中的位置下標 1 和 2。
存儲各頂點的節點(稱為表頭結點如上圖的V1,V2)結構分為兩部分,數據域和指針域。數據域用于存儲頂點數據信息,指針域用于鏈接下一個節點,如圖 :
對應上圖的:
那么除了表頭節點我們還有由表頭節點引出來的鏈表,稱為邊表,若邊表的節點存儲的是圖不是網,那么任仍然可以使用上邊圖示的存儲結構,但是如果存儲的是網,那么就可以使用下面的存儲結構(adjvex存儲與表頭節點有關系的頂點的下標,next是連接的鏈表,info表示的是權重等信息):
對應上圖的:
3.2鄰接表的存儲代碼實現
#define MVNum 100 //表示最大頂點數 typedef int VerTexType//頂點數據的數據類型 typedef char OtherInfo;//權重等相關信息 typedef struct ArcNode{int adjvex;//該邊所指向的頂點的位置struct ArcNode *nextarc;//指向下一條邊的指針 OtherInfo info; //和邊相關的信息如權重等 }ArcNode; typedef struct VNode{VerTexType data;ArcNode *firstarc; //指向第一條依附該頂點的邊的指針 }VNoded,AdjList[MVNum]; typedef struct{//鄰接表 AdjList vertices;int vexnum;//圖當前的頂點數 int arcnum;//圖當前的邊數 };3.3鄰接表計算頂點的出度和入度
使用鄰接表計算無向圖中頂點的入度和出度會非常簡單,只需從數組中找到該頂點然后統計此鏈表中節點的數量即可。
而使用鄰接表存儲有向圖時,通常各個頂點的鏈表中存儲的都是以該頂點為弧尾的鄰接點,因此通過統計各頂點鏈表中的節點數量,只能計算出該頂點的出度,而無法計算該頂點的入度。
對于利用鄰接表求某頂點的入度,有兩種方式:
我們建立一個鄰接表的逆鄰接表,如下圖:
那么他的逆鄰接表:
注意一個圖的鄰接矩陣是唯一的但是鄰接表不是唯一的,這是因為鄰接表表示中,各邊表結點的鏈接次序取決于建立鄰接表的算法,以及邊的輸入次序
4.圖的十字鏈表存儲結構
4.1圖的十字鏈表存儲結構的概念
建立個各個鏈表中用于存儲頂點的首元節點結構如圖:
從上圖 可以看出,首元節點中有一個數據域和兩個指針域(分別用 firstin 和 firstout 表示):
- firstin 指針用于連接以當前頂點為弧頭的其他頂點構成的鏈表;
- firstout 指針用于連接以當前頂點為弧尾的其他頂點構成的鏈表;
- data 用于存儲該頂點中的數據;
由此可以看出,十字鏈表實質上就是為每個頂點建立兩個鏈表,分別存儲以該頂點為弧頭的所有頂點和以該頂點為弧尾的所有頂點。
注意,存儲圖的十字鏈表中,各鏈表中首元節點與其他節點的結構并不相同,上圖所示僅是十字鏈表中首元節點的結構,鏈表中其他普通節點的結構如圖:
五個域分別表示:
- tailvex 該弧的尾頂點的位置;
- headvex 該弧的頭頂點的位置;
- hlink 指針:用于鏈接下一個存儲以首元節點為弧頭的頂點的節點;
- tlink 指針:用于鏈接下一個存儲以首元節點為弧尾的頂點的節點;
- info 指針:用于存儲與該頂點相關的信息,例如量頂點之間的權值;
比如用存儲下圖的有向圖:
十字鏈表法:
拿頂點 V1 來說,通過構建好的十字鏈表得知,以該頂點為弧頭的頂點只有存儲在數組中第 3 位置的 V4(因此該頂點的入度為 1),而以該頂點為弧尾的頂點有兩個,分別為存儲數組第 1 位置的 V2 和第 2 位置的 V3(因此該頂點的出度為 2)。
對于各個鏈表中節點來說,由于表示的都是該頂點的出度或者入度,因此沒有先后次序之分。
4.1圖的十字鏈表存儲結構代碼實現
#define MAX_VERTEX_NUM 20 #define InfoType int//圖中弧包含信息的數據類型 #define VertexType int typedef struct ArcBox{int tailvex,headvex;//弧尾、弧頭對應頂點在數組中的位置下標struct ArcBox *hlik,*tlink;//分別指向弧頭相同和弧尾相同的下一個弧InfoType *info;//存儲弧相關信息的指針 }ArcBox; typedef struct VexNode{VertexType data;//頂點的數據域ArcBox *firstin,*firstout;//指向以該頂點為弧頭和弧尾的鏈表首個結點 }VexNode; typedef struct {VexNode xlist[MAX_VERTEX_NUM];//存儲頂點的一維數組int vexnum,arcnum;//記錄圖的頂點數和弧數 }OLGraph;5.圖的鄰接多重表的存儲結構
5.1圖的鄰接多重表的存儲結構概念
無向圖的存儲可以使用鄰接表,但在實際使用時,如果想對圖中某頂點進行實操(修改或刪除),由于鄰接表中存儲該頂點的節點有兩個,因此需要操作兩個節點。
為了提高在無向圖中操作頂點的效率,本節學習一種新的適用于存儲無向圖的方法——鄰接多重表。
注意,鄰接多重表僅適用于存儲無向圖或無向網。
鄰接多重表存儲無向圖的方式,可看作是鄰接表和十字鏈表的結合。同鄰接表和十字鏈表存儲圖的方法相同,都是獨自為圖中各頂點建立一張鏈表,存儲各頂點的節點作為各鏈表的首元節點,同時為了便于管理將各個首元節點存儲到一個數組中。各首元節點結構如圖 所示:
- data:存儲此頂點的數據;
- firstedge:指針域,用于指向同該頂點有直接關聯的存儲其他頂點的節點。
從上圖 可以看到,鄰接多重表采用與鄰接表相同的首元節點結構。但各鏈表中其他節點的結構與十字鏈表中相同,如下圖 所示:
- mark:標志域,用于標記此節點是否被操作過,例如在對圖中頂點做遍歷操作時,為了防止多次操作同一節點,mark 域為 0 表示還未被遍歷;mark 為 1 表示該節點已被遍歷;
- ivex 和 jvex:數據域,分別存儲圖中各邊兩端的頂點所在數組中的位置下標;
- ilink:指針域,指向下一個存儲與 ivex 有直接關聯頂點的節點;
- jlink:指針域,指向下一個存儲與 jvex 有直接關聯頂點的節點;
- info:指針域,用于存儲與該頂點有關的其他信息,比如無向網中各邊的權;
例子:
從例子中,可直接找到與各頂點有直接關聯的其他頂點。比如說,與頂點 V1 有關聯的頂點為存儲在數組下標 1 處的 V2 和數組下標 3 處的 V4,而與頂點 V2 有關聯的頂點有 3 個,分別是 V1、V3 和 V5。
5.2圖的鄰接多重表的存儲結構代碼實現
#define MAX_VERTEX_NUM 20 //圖中頂點的最大個數 #define InfoType int //邊含有的信息域的數據類型 #define VertexType int //圖頂點的數據類型 typedef enum {unvisited,visited}VisitIf; //邊標志域 typedef struct EBox{VisitIf mark; //標志域int ivex,jvex; //邊兩邊頂點在數組中的位置下標struct EBox * ilink,*jlink; //分別指向與ivex、jvex相關的下一個邊InfoType *info; //邊包含的其它的信息域的指針 }EBox; typedef struct VexBox{VertexType data; //頂點數據域EBox * firstedge; //頂點相關的第一條邊的指針域 }VexBox; typedef struct {VexBox adjmulist[MAX_VERTEX_NUM];//存儲圖中頂點的數組int vexnum,degenum;//記錄途中頂點個數和邊個數的變量 }AMLGraph;總結
以上是生活随笔為你收集整理的详解图的各种令人心慌的概念和四种图的存储结构(整理到吐)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一文搞定JS事件基础与进阶
- 下一篇: JVM内存进阶