圖
定義
圖(Graph), 又V和E兩個非空集合構成,表示為G = (V,E);
其中,V表示的是圖G中的頂點的又窮非空集合;E表示的是圖G中的兩個頂點之間連接的邊的有窮集合;
V(G),E(G)通常分別表示G的頂點集,邊集;
ps: 一個圖,可以沒有邊,也就是E(G)可以為空,但是V(G)不能
圖的基本術語
無向邊
: 若頂點vi到vj之間的邊沒有方向,則稱這條邊為無向邊(Edge),用無序偶對(vi,vj)來表示。
無向圖
: 如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖為無向圖(Undirected graphs)。
有向邊
: 若從頂點vi到vj的邊有方向,則稱這條邊為有向邊,也稱為弧(Arc)。用有序偶來表示,vi稱為弧尾(Tail),vj稱為弧頭(Head)。
有向圖
: 如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為有向圖(Directed graphs)。
無向邊用小括號“()”表示,而有向邊則是用尖括號“<>”表示。
簡單圖
: 在圖中,若不存在頂點到其自身的邊,且同一條邊不重復出現,則稱這樣的圖為簡單圖。
無向完全圖
: 在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。
含有n個頂點的無向完全圖有n(n-1)/2條邊。
有向完全圖
: 在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,則稱該圖為有向完全圖。
含有n個頂點的有向完全圖有n×(n-1)條邊。
對於具有n個頂點和e條邊數的圖,無向圖,有向圖。
稀疏圖與稠密圖
: 有很少條邊或弧的圖稱為稀疏圖,反之稱為稠密圖。這里稀疏和稠密是模糊的概念,都是相對而言的。
權
: 有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。
網
: 帶權的圖通常稱為網(Network)。
子圖
: 設有兩個圖G=(V,{E})和G'=(V',{E'}),如果V'∈V且E'∈E,則稱G'為G的子圖(Sub-graph)。
鄰接點
: 對於無向圖G=(V,{E}),如果邊(v,v')∈E,則稱頂點v和v'互為鄰接點(Adjacent),即v和v'相鄰接。
邊(v,v')依附(incident)於頂點v和v',或者說(v,v')與頂點v和v'相關聯。
度
: 頂點v的度(Degree)是和v相關聯的邊的數目,記為TD(v)。
對於有向圖G=(V,{E}),如果弧∈E,則稱頂點v鄰接到頂點v',頂點v'鄰接自頂點v。弧和頂點v,v'相關聯。以頂點v為
頭弧的數目稱為v的入度(InDegree),記為ID(v)
;以v為尾的弧的數目稱為v的出度(OutDegree),記為OD(v)
;頂點v的度為TD(v)=ID(v)+OD(v)。
路徑
: 無向圖G=(V,{E})中從頂點v到頂點v'的路徑(Path)是一個頂點序列。如果G是有向圖,則路徑也是有向的。
路徑的長度是路徑上的邊或弧的數目。
回路
: 第一個頂點和最后一個頂點相同的路徑稱為回路或環(Cycle)。
簡單路徑
: 序列中頂點不重復出現的路徑稱為簡單路徑。
簡單回路
: 除了第一個頂點和最后一個頂點之外,其余頂點不重復出現的回路,稱為簡單回路或簡單環。
連通
: 在無向圖G中,如果從頂點v到頂點v'有路徑,則稱v和v'是連通的。
連通圖
: 如果對於圖中任意兩個頂點vi、vj∈V,vi和vj都是連通的,則稱G是連通圖(Connected Graph)。
連通分量
: 無向圖中的極大連通子圖稱為連通分量。連通分量的概念強調:
- 要是子圖;
- 子圖要是連通的;
- 連通子圖含有極大頂點數;
- 具有極大頂點數的連通子圖包含依附於這些頂點的所有邊。
強連通圖
: 在有向圖G中,如果對於每一對vi、vj∈V、vi≠vj,從vi到vj和從vj到vi都存在路徑,則稱G是強連通圖。
強連通分量
: 有向圖中的極大強連通子圖稱做有向圖的強連通分量。
連通圖的生成樹
: 所謂的一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的n個頂點,但只有足以構成一棵樹的n-1條邊。
如果一個圖有n個頂點和小於n-1條邊,則是非連通圖,如果它多於n-1邊條,必定構成一個環,因為這條邊使得它依附的那兩個頂點之間有了第二條路徑。
有向樹
: 如果一個有向圖恰有一個頂點的入度為0,其余頂點的入度均為1,則是一個有向樹。
對有向樹的理解比較容易,所謂入度為0其實就相當於樹中的根結點,其余頂點入度為1就是說樹的非根結點的雙親只有一個。
有向圖的生成森林
: 一個有向圖的生成森林由若干棵有向樹組成,含有圖中全部頂點,但只有足以構成若干棵不相交的有向樹的弧。
圖的存儲結構
由千圖的結構比較復雜,任意兩個頂點之間都可能存在聯系,因此無法以數據元素在存儲區
中的物理位置來表示元素之間的關系,即圖沒有順序存儲結構,但可以借助
二維數組來表示
元素之間的關系,即鄰接矩陣表示法。另一方面,由於圖的任意兩個頂點間都可能存在關系,因此,
用鏈式存儲表示圖是很自然的事,圖的鏈式存儲有多種,有鄰接表、十字鏈表和鄰接多重表,應
根據實際需要的不同選擇不同的存儲結構。
鄰接矩陣表示法
由圖解:頂點之間關聯就對應二維數組上就賦值1,反之就為0;
對於網,有邊的位置,賦值其權,其余為 ∞;
使用鄰接矩陣表示法,除了需要用二維數組表示元素之前的關系時,還需要用一個一維數組來表示每個頂點所包含的數據;
// 對於網和普通的圖,主要是多了一個權,所以這里以網結構來展開
const int MVNum = 100; // 最大頂點數
const int MaxInt = 32767; // 最大值, ∞
// 定義一個結構體
typedef struct {
ElemType vexs[MVNum]; // 頂點表
int arcs[MVNum][MVNum]; // 鄰接矩陣
int vexNum, arcNum; // 圖的當前點數和邊數
}AMGraph;
創建一個無向網
//
Status CreateUDN(AMGraph &G) {
//
cin>>G.vexNum>>G.arcNum; // 輸入中的頂點數,和邊數
for(int i = 0; i < G.vexNum; i++) {
cin>>G.vexs[i]; // 這里對頂點元素數據賦值
}
for(int i = 0; i < G.vexNum; i++) {
for(int j = 0; j < G.vexNum; j++) {
G.arcs[i][j] = MaxInt; // 對鄰接矩陣初始化
}
}
for(int i = 0; i < G.arcNum; i++) {
int v1,v2; // 頂點
int w; // 權重
cin>>v1>>v2>>w;
// 獲取v1,v2頂點在鄰接矩陣中的位置
int _v1 = locateVex(G,v1);
int _v2 = lovateVex(G,v2);
G.arcs[_v1][_v2] = w; //設置權重
G.arcs[_v2][_v1] = G.arcs[_v1][_v2]; // 這是無向網的特別處理
}
return OK;
}
鄰接矩陣表示的優劣
(1)優點
-
便於判斷兩個頂點之間是否有邊, 即根據
A[i][j] = 0
或1來判斷。 -
便於計算各個頂點的度。對千無向圖,鄰接矩陣第凶於元素之和就是頂點l的度;對於有向圖,第i行 元素之和就是頂點 i 的出度,第i 列元素之和就是頂點l 的入度。
(2) 缺點
-
不便千增加和刪除頂點。
-
不便千統計邊的數目,需要掃描鄰接矩陣所有元素才能統計完畢。
-
空間復雜度高。如果是有向圖,n個頂點需要n2個單元存儲邊。如果是無向圖,因其鄰接矩陣是對稱的,所以對規模較大的鄰接矩陣可以采用壓縮存儲的方法,僅存儲下三角(或上三角)的元素,這樣需要n(n-1)/2個單元即可。但無論以何種方式存儲,鄰接矩陣表示法的空間復雜度均為0(心,這對千稀疏圖而言尤其浪費空間。
鄰接表表示法
采用鏈式存儲的結構,每個頂點構建一個單鏈表,其中表頭存放頂點信息,其余結點存放相關邊的信息;
- data, info 為數據域
- firstarc,nextarc 為鏈域
- adjvex 鄰接域
表頭結點采用順序存儲的結構存儲,以便可以隨機訪問任一頂點的邊鏈表
//
const int MaxInt = 100;
// 邊結點
typedef struct ArcNode{
int adjvex; // 改變所指向的頂點的位置
struct ArcNode * nextarc; // 指向下一條邊
int info; // 和邊相關的信息
}ArcNode;
// 頂點信息
typedef struct VNode {
int data; // 數據域
ArcNode * firstarc; // 指向第一條依附該頂點的邊的指針
}VNode, AdjList[MVNum];
//
typedef struct {
AdjList vertices; //鄰接表
int vexNum, arcNum; // 圖的當前頂點,邊數
}ALGraph;
采用鄰接表創建一個無向圖
//
status CreateUDG(ALGraph & G) {
cin>>G.vexNum>>G.arcNum;
// 對每一個鏈表進行賦值初始化
for(int i = 0; i<G.vexNum; i++) {
cin>>G.vertices[i].data; // 輸入頭結點信息
G.vertices[i].firstarc = NULL;
}
for(int i = 0; i < G.arcNum; i++) {
cin>>v1>>v2;
int _v1 = locateVex(G,v1);
int _v2 = lovateVex(G,v2);
// 動態開辟空間,創建一個邊結點
ArcNode * p1 = new ArcNode; //
p1->adjvex = _v2; // 鄰接點序號為_v2
p1->nextarc = G.vertices[_v1].firstarc;
G.vertices[_v1].firstarc = p1;
// 對稱結點
ArcNode * p2 = new ArcNode;
p2->adjvex = _v1;
p2->nextarc = G.vertices[_v2].firstarc;
G.vertices[_v2].firstarc = p2;
}
return OK;
}
ps:,一個圖的鄰接矩陣表示是唯一的,但其鄰接表表示不唯一,這是因為鄰接表表示中,各邊表結點的鏈接次序取決於建立鄰接表的算法,以及邊的輪入次序
鄰接表表示法的優缺點
(1) 優點
-
便於增加和刪除頂點。
-
便於統計邊的數目, 按頂點表 順 序掃描 所 有邊表可得到邊的數目。
-
空間效率高。對於一個具有n個頂點e條邊的圖 G, 若 G 是無向圖,則在其鄰接表表示中有 n 個頂點表結點和 2e 個邊表結點;若 G 是有向圖,則在它的鄰接表表示或逆鄰接表表示中均有 n 個頂點表結點和e個邊表結點。因此,鄰接表或逆鄰接表表示的空間復雜度為 O(n + e), 適合表示稀疏圖。對於稠密圖,考慮到鄰接表中要附加鏈域,因此常采取鄰接矩陣表示法。
(2) 缺點
-
不便於判斷頂點之間是否有邊,要判定 Vi和vj之間是否有邊,就需掃描第i個邊表,最壞情況下要耗費 O(n)時間。
-
不便於計算有向圖各個頂點的度。對於無向圖,在鄰接表表示中頂點Vi的度是第i個邊表中的結點個數。 在有向圖的鄰接表中,第 i 個邊表上的結點個數是頂點 Vi的出度,但求 Vj的入度較困難,需遍歷各頂點的邊表。若有向圖采用逆鄰接表表示(就是邊表尾入度),則與鄰接表表示相反,求頂點的入度容易,而求頂點的出度較難。
十字鏈表
十字鏈表 (Orthogonal List) 是有向圖的另一種鏈式存儲結構。可以看成是將有向圖的鄰接表和逆鄰接表結合起來得到的一種鏈表。 在十字鏈表中,對應於有向圖中每一條弧有一個結點,對應於每個頂點也有一個結點。
- 尾域 (tailvex) 和頭域 (headvex) 分別指示弧尾和弧頭這兩個頂點在圖中的位置;
- 鏈域 hlink 指向弧頭相同的下一條弧,而鏈域 tlink 指向弧尾相同的下一條弧;
- info域指向該弧的相關信息。弧頭相同的弧在同一鏈表上,弧尾相同的弧也在同一鏈表上;
- data 域存儲和頂點相關的信息,如頂點的名稱等;
- firstin 和 firstout為兩個鏈域,分別指向以該頂點為弧頭或弧尾的第一個弧結點。
ps: 此圖弧結點省略了info
有向圖的十字鏈表存儲表示
//
const int MAX_VERTEX_NUM = 20;
typedef struct ArcBox {
int tailvext, headvex; // 該弧頭尾頂點的位置, 這里特指頂點在順序存儲中的索引值
struct ArcBox * hlink, * tlink; // 弧頭,弧尾相同的鏈域
int info; // 信息
}ArcBox;
typedef struct VexNode {
int data;
ArcBox * firstin, * firstout; // 入弧,出弧
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM]; //
int vexnum,arcnum; // 有向圖當前頂點數,和弧數
}OLGraph;
鄰接多重表
鄰接多重表 (Adjacency Multilist) 是無向圖的另一種鏈式存儲結構。雖然鄰接表是無向圖的
一種很有效的存儲結構,在鄰接表中容易求得頂點和邊的各種信息。但是,在鄰接表中每一條邊
(Vi, Vj )有兩個結點,分別在第i個和第j個鏈表中,這給某些圖的操作帶來不便。
- mark 為標志域,可用以標記該條邊是否被搜索過;
- ivex 和 jvex為該邊依附的兩個頂點在圖中的位置;
- ilink 指向下一條依附於頂點 ivex 的邊;
- jlink 指向下一條依附於頂點jvex的邊,info為指向和邊相關的各種信息的指針域;
無向圖的鄰接多重表存儲表示
const int MAX_VERTEX_NUM = 20;
typedef enum{unvisited, visited} VisitIf;
typedef struct EBox {
VisitIf mark; // 訪問標記
int ivex, jvex; // 該邊依附的兩個頂點的位置
struct EBox * ilink, *jlink; // 分別指向依附這兩個頂點的下一條邊
int info;
}EBox;
typedef struct VexBox {
int data;
EBox * firstedge; // 指向第一條依附該頂點的邊
}VexBox;
typedef struct {
VexBox adjmulist[MAX_VERTEX_NUM];
int vexNum, arcNum;
}AMLGraph;
圖的基本操作
CreateGraph{&G,V,VR}
初始條件:V是圖的頂點集,VR是圖中弧的集合。
操作結果:按V和VR的定義構造圖G。
DestroyGraph { &G}
初始條件:圖G存在。
操作結果:銷毀圖G。
Locat eVex{G,u}
初始條件:圖G存在,u和G中頂點有相同特征。
操作結果:若G中存在頂點 u, 則返回該頂點在圖中的位置;否則返回其他信息。
GetVex{G,v}
初始條件:圖G存在,v是G中某個頂點。
操作結果:返回v的值
PutVex(&G,v,value);
初始條件:圖G存在,v是G中某個頂點。
操作結果:對 v賦值value。
FirstAdjVex(G,v)
初始條件:圖G存在,v是G中某個頂點。
操作結果:返回 v的第一 個鄰接頂點。若v在G中沒有鄰接頂點,則返回 “空” 。
NextAdjVex(G,v,w)
初始條件:圖G存在,v是G中某個頂點,w是v的鄰接頂點。
操作結果:返回v的(相對千w的)下一 個鄰接頂點。若w是v的最后一 個鄰接點,則返回 “空” 。
InsertVex(&G,v)
初始條件:圖G存在,v和圖中頂點有相同特征。
操作結果:在圖G中增添新頂點v。
DeleteVex(&G,v)
初始條件:圖G存在,v是G中某個頂點。
操作結果:刪除 G中頂點v及其相關的弧。
InsertArc(&G,v,w)
初始條件:圖G存在,v和w是G中兩個頂點。
操作結果:在G中增添弧<v, w>, 若G是無向圖,則還增添對稱弧<w, v>。
Dele七eArc(&G,v,w)
初始條件:圖G存在,v和w是G中兩個頂點。
操作結果:在G中刪除弧<v, w>, 若G是無向圖,則還刪除對稱弧<w, v>。
DFSTraverse(G)
初始條件:圖G存在。
操作結果:對圖進行深度優先遍歷,在遍歷過程中對每個頂點訪問一次。
BFSTraverse(G)
初始條件:圖G存在。
操作結果:對圖進行廣度優先遍歷,在遍歷過程中對每個頂點訪問一次