圖
Reference:《數據結構》 安徽大學出版社
可同時參照:https://blog.csdn.net/Sensente/article/details/100887588(離散數學中的圖論基礎定義)
作爲數據結構的圖,僅有兩個構成要素:頂點和邊。
其是一種多對多關係的抽象描述
圖比之前介紹的其他結構要複雜的多:
線性結構中結點之間是線性關係,一個結點最多隻有一個直接前驅和直接後繼。
樹形結構呈現明顯的層次關係,每個結點最多隻有一個雙親結點,卻可以有多個孩子結點。
而圖結構任意兩個頂點都可能發生鄰接關係。
一、圖的基本概念和術語定義
1.1 基本概念
圖由兩部分構成:頂點集合和邊的集合E,計作G=(V,E)
V(Vertex)頂點:表示數據元素。
E(Edge)邊:表示頂點對的關係,分爲有向邊和無向邊(弧,arc)
無向邊,簡稱邊,由兩個頂點的無序偶構成,“(頂點1,頂點2)”表示,兩頂點可以互換。即E1 = (V1,V2) = (V2,V1)
有向邊,弧。E2=(V1,V2)≠(V2,V1)
弧表示一種單向關係(離散數學)
1.2 圖的圖形化表示(略)
1.3 定義和術語
1.3.1 無向圖和有向圖
無向圖:每條邊都是無向邊
有向圖:每條邊都是弧、有向邊
混合圖:既有有向邊又有無向邊(一般不做討論)
1.3.2 網絡(帶權圖)
網(network) 指的是邊或弧上帶有權值的圖,又稱帶權圖。
網可以是無向的,也可以是有向的。
權值表示兩個頂點間的距離。
1.3.3 子圖
已知圖G=(V,E),若另一個圖G1=(V1,E1)是圖G中選取部分頂點和部分邊(弧)構成,即V1⊆V,E1⊆E,則稱G1是G的子圖。
1.3.4 鄰接( Adjacent )
若兩個頂點之間有邊相連,則稱這兩個頂點鄰接(相鄰的)。
無向圖 G=(V,E),若頂點 u、w 之間有一條邊 (u, w)∈E,則稱頂點 u、w 互爲鄰接點。
有向圖 G=(V,E),若頂點 u、w 之間有一條弧 <u, w>∈E,則稱 u 鄰接 w,w鄰接自(於)u。
1.3.5度(Degree)
度指的是一個頂點關聯的邊的數量。對於有向圖還可以區分入度和出度:
入度指的是射入/指向頂點的邊的數量
出度指的是該點射出/指向其他頂點的邊的數量
有向圖:度 = 入度+出度
其中,度和邊是有相關性的:
無向圖中:圖的邊數 = 圖的頂點度數/2;
有向圖中:入度之和=出度之和; 圖的邊數 = 入度之和/出度之和/圖的頂點度數之和/2;
1.3.6 路徑(Path)
通俗的說是從一個頂點途徑一些頂點和邊到達另一個頂點中依次經過的頂點和邊的序列。
對於無向圖,路徑是雙向可達的,對於有向圖,路徑往往是單向可達。
路徑長度:一條路徑上經過的邊數(弧數)
1.3.7 迴路
路徑上第一個頂點和最後一個頂點相同的閉合路徑叫做迴路,或稱爲環(Loop)
1.3.8 簡單路徑
路徑中途徑的頂點不重複的叫做簡單路徑
1.3.9 簡單迴路
除了第一個頂點和最後一個頂點外,中間途徑頂點不重複的閉合路徑叫做簡單路徑(環)
1.3.10 連通圖
連通(可達) :圖G中,如果從頂點U到W有路徑,則稱U和W是聯通的。
連通圖:無向圖G中,如果任意兩個頂點之間都有路徑,或是連通,則稱G是連通圖。
1.3.11 連通分量
無向圖中分割出來的極大連通子圖。非連通圖可視爲由若干連通分量(連通子圖)構成。
1.3.12 強連通圖
有向圖G中,若任意兩個頂點之間都有路徑,或是連通的,則稱G爲強連通圖。或有向圖中任意兩個頂點間可以相互到達。
1.3.13 強連通分量
有向圖中分割出來的極大連通子圖。非強連通有向圖由若干強連通分量構成。
1.3.14 無向完全圖
若無向圖G中任意兩個頂點間都有一條邊相連,稱其爲無向完全圖。
N個頂點的無向完全圖有N*(N-1)/2條邊。
1.3.15 有向完全圖
若有向圖G中任意兩個頂點之間都有一條弧(有向邊)相連,稱其爲有向完全圖。
1.3.16 (無向)樹
若無向圖連通並且無迴路,則稱爲(無向)樹,除此之外,樹還有以下幾種描述:
·連通的無環圖
·有N-1條邊的連通圖
·有最少邊的連通圖
N個點N-1條邊的連通圖
1.3.17 有向樹
僅有一個頂點入度爲0,其餘頂點入度均爲1的有向圖。其中入度爲0的頂點爲其根。
1.3.18 連通圖的生成樹
一個N個頂點的連通圖(強連通圖),其生成樹是它的一個極小的連通子圖,它含有圖中的全部頂點,但只有足以構成一棵樹的N-1條邊。
1.3.19 非連通圖的生成森林 (spanning forest)
生成森林 (spanning forest):一個非連通圖的生成森林由若干棵互不相交的樹組成,含有圖中的全部頂點,但是隻有足以構成若干棵不相交的樹的邊(弧)。
請注意:此處樹的定義與之前章節的樹並不相同,在圖論中,樹只是一種特殊的圖。
1.4圖的頂點編號
(略)
二、圖的儲存結構
鄰接矩陣表示法
鄰接鏈表(鄰接表)表示法
鄰接多重表表示法
十字鏈表表示法等(Dancing Links)
以下使用第一、二種。
2.1鄰接矩陣
這裏的圖指無向圖或有向圖,不含網(帶權圖)。矩陣A中,若頂點vi到vj之間有邊或弧連接,則Aij=1;否則Aij=0。
有關鄰接矩陣的一些結論:
·無向圖:
鄰接矩陣是對稱的
第i行或j列 1 的個數就是該頂點的度
圖的邊數 = 矩陣中 1 的個數 / 2
·有向圖:
因爲邊的方向性,鄰接矩陣不一定對稱
第i行“1”的個數是頂點vi的出度,第i列“1”的個數是頂點vi的入度
圖的邊數=矩陣中“1”的個數。
網的表示即用邊權代替1
下列給出的定義式並不通用。
鄰接矩陣存儲結構描述
#define INF 65535 //定義無窮大,也可以是其它很大的數字
#define MaxVerNum 1000
//定義最大頂點個數,可根據需要定義最大頂點數
typedef char elementType;
//定義圖中頂點的數據類型,這裏不妨設爲char類型
typedef int cellType;
//定義鄰接矩陣中元素的數據類型,這裏不妨設爲int型
//對無權圖,1-相鄰(有邊),0-不相鄰(無邊)
//對有權圖,爲邊的權值,無邊爲無窮大。
typedef enum{ UDG, UDN, DG, DN } GraphKind;
//枚舉圖的類型--無向圖,無向網,有向圖,有向網
typedef struct GraphAdjMatrix
{ //頂點數組,存放頂點元素的值
elementType data[MaxVerNum];
cellType AdjMatrix[MaxVerNum][MaxVerNum];
//鄰接矩陣,元素類型爲cellType
int VerNum; //頂點數
int ArcNum; //弧(邊)數
GraphKind gKind;
//圖的類型:0-無向圖;1-無向網;
// 2-有向圖;3-有向網
//此項用以區分圖的類型,爲可選分量,可以取消。
//此項也可以直接定義爲整型,而不用枚舉定義。
} Graph; //圖的類型名
圖的鄰接矩陣優缺點:
優點:直觀、易於實現。
缺點:對於稀疏圖毫無優勢。
2.2 鄰接表
1. 頂點表 頂點表存儲頂點信息,可用n個結點的順序表存儲。
表中每個元素(結點)表示圖中的一個頂點。
頂點表的結點結構:數據域+指針域。
數據域存放頂點數據元素或頂點相關的其它信息; 指針域指向此頂點對應的邊鏈表,即第一個鄰接頂點。
2. 邊鏈表 邊鏈表保存頂點表中對應頂點的邊的信息,即鄰接頂點信息。
邊鏈表結點結構 可由3個部分構成:鄰接點域+信息域+指針域。
鄰接點域—保存鄰接點信息,比如鄰接點在頂點表中的編號等;
信息域—這個域可選的,保存邊的相關信息,比如在網中,可用來保存邊的權值。
指針域—指向下一條邊(下一個鄰接點)。
定義邊鏈表的結點結構
typedef struct eNode
{ //鄰接頂點信息,此處爲頂點編號,從1開始
int adjVer;
//邊鏈表中表示邊的相關信息,比如表的權值
eInfoType eInfo;
//指向邊鏈表中的下一個結點。
struct eNode* next;
}EdgeNode; //邊鏈表結點類型
//定義頂點表的結點結構
typedef struct vNode
{
elementType data; //存放圖中頂點的數據值
EdgeNode* firstEdge;
//指向此頂點關聯的第一條邊的指針,即邊鏈表的頭指針
//注意:fristEdge指針與邊鏈表結點中的next指針類型相同
}VerNode; //頂點表結點類型
//定義圖的整體結構
typedef struct GraphAdjLinkList
{ //頂點表,此爲數組(順序表),存放頂點信息
//數組的元素爲VerNode結構類型
VerNode VerList[MaxVerNum];
int VerNum; //頂點數
int ArcNum; //弧(邊)數
GraphKind gKind;
//圖的類型:0-無向圖;1-無向網;2-有向圖;3-有向網
//此項用以區分圖的類型,爲可選分量,可以取消。
//此項也可以直接定義爲整型,而不用枚舉定義。
}Graph; //圖的類型名
對無向圖和網計數所有邊鏈表的結點數,除以2即是邊數
對有向圖和網通過鄰接表或逆鄰接表計數邊鏈表結點數即是邊數。
鄰接表的優點:
作爲鏈式儲存結構,可以動態申請內存,有以下幾個方面優點:
便於求邊。
稀疏圖空間效率極高。
缺點:
判斷兩個頂點是否有邊(弧)相對複雜。
圖的遍歷
·深度優先(DFS)
訪問當前結點V
訪問當前對所有V的鄰接點執行DFS
在本例中
firstAdj( G, v ):返回圖G中頂點v的第一個鄰接點。若不存在鄰接點(編號),則返回0;
nextAdj( G, v, w ):返回圖G中頂點v的鄰接點中處於w之後的那個鄰接點。若不存在這樣的鄰接點(編號),則返回0;
通過這兩個函數,可依次求出一個頂點的所有鄰接點。
DFS&DFSTraverse
void DFS( Graph G, int v )
{ //從編號v的頂點開始對圖G進行深度優先搜索遍歷
int w;
visit(G, v); //訪問頂點v
visited[v]=TRUE; //設置v已經訪問標誌
w=firstAdj(G,v); //求v的第一個鄰接點,返回其編號給w
while(w!=0) //v的鄰接點循環
{ //從沒有訪問過的鄰接點出發遞歸深度遍歷
if(!visited[w])
DFS(G,w); //對v的鄰接點w遞歸DFS
w=nextAdj(G,v,w); //找v 的w後的下一個鄰接點
}
}
void DFSTraverse( Graph G, int v )
{
int i; //頂點編號
for(i=1;i<=G.VerNum;i++) //初始化訪問標記數組
visited[i]=false;
DFS(G,v); //遍歷指定頂點v所在的連通分量
for(i=1;i<=G.VerNum;i++)
{ //循環遍歷圖中所有其它的連通分量
if(!visited[i])
{
DFS(G,i); //遍歷i所在的連通分量
}
}
}
(2) 基於鄰接表的實現
void DFS( Graph G, int v )
{
visit(G,v); //訪問頂點v,如:cout<<Data[v]<<"\t";
visited[v]=TRUE; //標記編號爲v的頂點已經訪問
EdgeNode *p;
//p初始化爲頂點v的邊鏈表的頭指針
p=G.VerList[v].firstEdge;
while(p)
{
if(!visited[ p->adjVer ]) // p->adjVer 爲v的鄰接點編號
DFS( G, p->adjVer ); //遞歸深度遍歷頂點v的鄰接點
p=p->next; //p指向v的下一個鄰接點
}
}
DFS算法分析:
時間複雜度
基於鄰接矩陣:O(N^2)
基於鄰接表:O(N+E) (E stands for edge nums)
·廣度優先(BFS)
訪問V0
訪問V0的所有鄰接點
假設訪問的V1,V2...
依次訪問其鄰接點直到爲空
(層次遍歷序)
需要隊列輔助。
void BFS( Graph G, int v )
{
int w;
Queue Q; //初始化隊列
visit(v); //訪問頂點v
visited[v]=TRUE; //標記頂點v已訪問
enQueue(Q,v); //v入隊
while (!queueEmpty(Q))
{
getFront(Q,v) //取隊頭元素到v
outQueue(Q); //隊頭元素出隊
w=firstAdj(G,v); //查找v的第一個鄰接點
while (w!=0) //循環搜索v的每一個鄰接點
{
if ( !visited[w] ) //鄰接點w未被訪問
{
visit(G,w); //訪問頂點w
visited[w]=TRUE; //標記頂點w已經訪問
enQueue(Q,w); //w入隊
}
//查找頂點v的在w之後的下一個鄰接點
w=nextAdj(G,v,w);
}}}
算法分析:
時間複雜度:
基於鄰接矩陣:O(N^2)
基於鄰接表:O(N+E) (E stands for edge nums)
最小生成樹
https://blog.csdn.net/Sensente/article/details/102492607
二分圖(KM/匈牙利)
https://blog.csdn.net/Sensente/article/details/96148276
最短路(SPFA,Dijkstra,Floyd)