資料結構-圖-知識點總結
一、基本術語
圖(graph):圖是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中的頂點的集合,E是圖G中邊的集合。
頂點(Vertex):圖中的資料元素。線性表中我們把資料元素叫元素,樹中將資料元素叫結點。
邊:頂點之間的邏輯關係用邊來表示,邊集可以是空的。
無向邊(Edge):若頂點V1到V2之間的邊沒有方向,則稱這條邊為無向邊。
無向圖(Undirected graphs):圖中任意兩個頂點之間的邊都是無向邊。(A,D)=(D,A)
對於無向圖G來說,G1=(V1,{E1}),其中頂點集合V1={A,B,C,D};邊集和E1={(A,B),(B,C),(C,D),(D,A),(A,C)}
有向邊:若從頂點V1到V2的邊有方向,則稱這條邊為有向邊,也稱弧(Arc)。用<V1,V2>表示,V1為狐尾(Tail),V2為弧頭(Head)。(V1,V2)≠(V2,V1)。
有向圖(Directed graphs):圖中任意兩個頂點之間的邊都是有向邊。
注意:無向邊用“()”,而有向邊用“< >”表示。
簡單圖:圖中不存在頂點到其自身的邊,且同一條邊不重複出現。
無向完全圖:無向圖中,任意兩個頂點之間都存在邊。
有向完全圖:有向圖中,任意兩個頂點之間都存在方向互為相反的兩條弧。
稀疏圖:有很少條邊。
稠密圖:有很多條邊。
權(Weight):與圖的邊或弧相關的數。
網(Network):帶權的圖。
子圖(Subgraph):假設G=(V,{E})和G‘=(V',{E'}),如果V'包含於V且E'包含於E,則稱G'為G的子圖。
度(Degree):無向圖中,與頂點V相關聯的邊的數目。有向圖中,入度表示指向自己的邊的數目,出度表示指向其他邊的數目,該頂點的度等於入度與出度的和。
路徑的長度:一條路徑上邊或弧的數量。
連通圖:圖中任意兩個頂點都是連通的。
圖1不是連通圖,圖2是連通圖。
連通分量:無向圖中的極大連通子圖。(子圖必須是連通的且含有極大頂點數)。圖1有兩個連通分量
強連通分量:有向圖中的極大強連通子圖。
生成樹:無向圖中連通且n個頂點n-1條邊叫生成樹。
有向樹:有向圖中一頂點入度為0其餘頂點入度為1。
森林:一個有向圖由若干棵有向樹構成生成森林。
二、圖的儲存結構
1.鄰接矩陣:用兩個陣列,一個數組儲存頂點集,一個數組儲存邊集。
圖的鄰接矩陣儲存的結構:
#define maxvex 100
typedef struct
{
char vexs[maxvex];
int arc[maxvex][maxvex];
int vertex,edges;
}MGraph;
無向圖的建立程式碼:
#define maxvexs 100
#define infinity 65535//用65535來表示∞
typedef struct
{
char vexs[maxvexs];
int arc[maxvexs][maxvexs];
int vertexes,edges;
}mgraph;
void creatgraph(mgraph *g)
{
int i,j,k,w;
printf("輸入頂點數和邊數:\n");
scanf("%d,%d",&g->vertexes,&g->edges);
for(i=0;i<g->vertexes;i++)//讀入頂點資訊,建立頂點表
scanf("%c",&g->vexs[i]);
for(i=0;i<g->vertexes;i++)
for(j=0;j<g->vertexes;j++)
g->arc[i][j]=infinity;//初始化鄰接矩陣
for(k=0;k<g->vertexes;k++)//讀入edges條邊,建立鄰接矩陣
{
printf("輸入邊(Vi,vj)上的下標i,下標j,和權w:\n");
scanf("%d%d%d",&i,&j,&w);
g->arc[i][j]=w;
g->arc[j][i]=w;//無向圖,矩陣對稱
}
}
2.鄰接表:陣列與連結串列相結合的儲存方法。
對於帶權值的網圖,可以在邊表結點定義中再增加一個weight的資料域,儲存權值資訊即可。
鄰接表結點定義
typedef struct EdgeNode
{
int adjvex; //鄰接點域,儲存該頂點對應的下標
int weight; //用於儲存權值,對於非網圖可以不需要
struct EdgeNode *next; //鏈域,指向下一個鄰接點
}EdgeNode;//邊表結點
typedef struct VertexNode //頂點表結點
{
char data; //頂點域,儲存頂點資訊
EdgeNode *firstedge; //邊表頭指標
}VertexNode,AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges;//圖中當前頂點數和邊數
}GraphAdjList;
三、圖的遍歷
1.深度優先遍歷(DFS):從圖中某個頂點v出發,訪問此頂點,然後從v的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。
2.廣度優先遍歷(BFS):類似於樹的層次遍歷。
四、最小生成樹
最小生成樹:構造連通網的最小代價生成樹。
1.普里姆(prime):
第一種:先將一個起點加入最小生成樹,之後不斷尋找與最小生成樹相連的邊權最小的邊能通向的點,並將其加入最小生成樹,直至所有頂點都在最小生成樹中。
第二種:
1.將一個圖的頂點分為兩部分,一部分是最小生成樹中的結點(A集合),另一部分是未處理的結點(B集合)。
2.首先選擇一個結點,將這個結點加入A中,然後,對集合A中的頂點遍歷,找出A中頂點關聯的邊權值最小的那個(設為v),將此頂點從B中刪除,加入集合A中。
3.遞迴重複步驟2,直到B集合中的結點為空,結束此過程。
4.A集合中的結點就是由Prime演算法得到的最小生成樹的結點,依照步驟2的結點連線這些頂點,得到的就是這個圖的最小生成樹。
2.克魯斯卡爾(kluskal):在剩下的所有未選取的邊中,找最小邊,如果和已選取的邊構成迴路,則放棄,選取次小邊。
五、最短路徑
1.迪傑斯特拉演算法(Dijkstra):把圖中的頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合S(初始時S中只有源節點,以後每求得一條最短路徑,就將它對應的頂點加入到集合S中,直到全部頂點都加入到S中);第二組是未確定最短路徑的頂點集合U。
演算法步驟:
(1)初始化時,S只含有源節點;
(2)從U中選取一個距離v最小的頂點k加入S中(該選定的距離就是v到k的最短路徑長度);
(3)以k為新考慮的中間點,修改U中各頂點的距離;若從源節點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值是頂點k的距離加上k到u的距離;
(4)重複步驟(2)和(3),直到終點在S中。
2.弗洛伊德演算法(Floyd):
1,從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權為無窮大。
2,對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比已知的路徑更短。如果是更新它。
---------------------
基本概念
圖(Graph):圖(Graph)是一種比線性表和樹更為複雜的資料結構。
圖結構:是研究資料元素之間的多對多的關係。在這種結構中,任意兩個元素之間可能存在關係。即結點之間的關係可以是任意的,圖中任意元素之間都可能相關。
圖G由兩個集合V(頂點Vertex)和E(邊Edge)組成,定義為G=(V,E)
線性結構:是研究資料元素之間的一對一關係。在這種結構中,除第一個和最後一個元素外,任何一個元素都有唯一的一個直接前驅和直接後繼。
樹結構:是研究資料元素之間的一對多的關係。在這種結構中,每個元素對下(層)可以有0個或多個元素相聯絡,對上(層)只有唯一的一個元素相關,資料元素之間有明顯的層次關係。
圖相關的概念和術語
一個圖(G)定義為一個偶對(V,E) ,記為G=(V,E) 。其中: V是頂點(Vertex)的非空有限集合,記為V(G);E是無序集V&V的一個子集,記為E(G) ,其元素是圖的弧(Arc)。
將頂點集合為空的圖稱為空圖。其形式化定義為:
G=(V ,E)
V={v|vdata object}
E={<v,w>| v,wV∧p(v,w)}
1
2
3
1. 無向圖和有向圖
對於一個圖,若每條邊都是沒有方向的,則稱該圖為無向圖。圖示如下:
因此,(Vi,Vj)和(Vj,Vi)表示的是同一條邊。注意,無向圖是用小括號,而下面介紹的有向圖是用尖括號。
無向圖的頂點集和邊集分別表示為:
V(G)={V1,V2,V3,V4,V5}
E(G)={(V1,V2),(V1,V4),(V2,V3),(V2,V5),(V3,V4),(V3,V5),(V4,V5)}
對於一個圖G,若每條邊都是有方向的,則稱該圖為有向圖。圖示如下:
有向圖的頂點集和邊集分別表示為:
V(G)={V1,V2,V3}
E(G)={
2,無向完全圖和有向完全圖
我們將具有n(n-1)/2條邊的無向圖稱為無向完全圖。同理,將具有n(n-1)條邊的有向圖稱為有向完全圖。
完全無向圖
對於無向圖,若圖中頂點數為n ,用e表示邊的數目,則e [0,n(n-1)/2] 。具有n(n-1)/2條邊的無向圖稱為完全無向圖。
完全無向圖另外的定義是:
對於無向圖G=(V,E),若vi,vj V ,當vi≠vj時,有(vi ,vj)E,即圖中任意兩個不同的頂點間都有一條無向邊,這樣的無向圖稱為完全無向圖。
#### 完全有向圖
對於有向圖,若圖中頂點數為n ,用e表示弧的數目,則e[0,n(n-1)] 。具有n(n-1)條邊的有向圖稱為完全有向圖。
完全有向圖另外的定義是:
對於有向圖G=(V,E),若vi,vjV ,當vi ≠vj時,圖中任意兩個不同的頂點間都有一條弧,這樣的有向圖稱為完全有向圖。
子圖和生成子圖
設有圖G=(V,E)和G’=(V’,E’),若V’V且E’E ,則稱圖G’是G的子圖;若V’=V且E’E,則稱圖G’是G的一個生成子圖。
連通圖(無向圖)
連通圖是指圖G中任意兩個頂點Vi和Vj都連通,則稱為連通圖。比如圖(b)就是連通圖。下面是一個非連通圖的例子。
圖的建立和遍歷
圖的遍歷方法
1) 深度優先搜尋遍歷
深度優先搜尋DFS遍歷類似於樹的前序遍歷。其基本思路是:
a) 假設初始狀態是圖中所有頂點都未曾訪問過,則可從圖G中任意一頂點v為初始出發點,首先訪問出發點v,並將其標記為已訪問過。
b) 然後依次從v出發搜尋v的每個鄰接點w,若w未曾訪問過,則以w作為新的出發點出發,繼續進行深度優先遍歷,直到圖中所有和v有路徑相通的頂點都被訪問到。
c) 若此時圖中仍有頂點未被訪問,則另選一個未曾訪問的頂點作為起點,重複上述步驟,直到圖中所有頂點都被訪問到為止。
圖示如下:
注:紅色數字代表遍歷的先後順序,所以圖(e)無向圖的深度優先遍歷的頂點訪問序列為:V0,V1,V2,V5,V4,V6,V3,V7,V8
如果採用鄰接矩陣儲存,則時間複雜度為O(n2);當採用鄰接表時時間複雜度為O(n+e)。
2) 廣度優先搜尋遍歷
廣度優先搜尋遍歷BFS類似於樹的按層次遍歷。其基本思路是:
a) 首先訪問出發點Vi
b) 接著依次訪問Vi的所有未被訪問過的鄰接點Vi1,Vi2,Vi3,…,Vit並均標記為已訪問過。
c) 然後再按照Vi1,Vi2,… ,Vit的次序,訪問每一個頂點的所有未曾訪問過的頂點並均標記為已訪問過,依此類推,直到圖中所有和初始出發點Vi有路徑相通的頂點都被訪問過為止。
圖示如下:
因此,圖(f)採用廣義優先搜尋遍歷以V0為出發點的頂點序列為:V0,V1,V3,V4,V2,V6,V8,V5,V7
如果採用鄰接矩陣儲存,則時間複雜度為O(n2),若採用鄰接表,則時間複雜度為O(n+e)。
圖的其他演算法實現
圖的建立
AdjGraph *Create_Graph(MGraph * G)
{ printf(“請輸入圖的種類標誌:”) ;
scanf(“%d”, &G->kind) ;
G->vexnum=0 ; /* 初始化頂點個數 */
return(G) ;
}
1
2
3
4
5
6
7
圖的頂點定位
圖的頂點定位操作實際上是確定一個頂點在vexs陣列中的位置(下標) ,其過程完全等同於在順序儲存的線性表中查詢一個數據元素。
int LocateVex(MGraph *G , VexType *vp)
{ int k ;
for (k=0 ; k<G->vexnum ; k++)
if (G->vexs[k]==*vp) return(k) ;
return(-1) ; /* 圖中無此頂點 */
}
1
2
3
4
5
6
7
向圖中增加頂點
向圖中增加一個頂點的操作,類似在順序儲存的線性表的末尾增加一個數據元素。
int AddVertex(MGraph *G , VexType *vp)
{ int k , j ;
if (G->vexnum>=MAX_VEX)
{ printf(“Vertex Overflow !\n”) ; return(-1) ; }
if (LocateVex(G , vp)!=-1)
{ printf(“Vertex has existed !\n”) ; return(-1) ; }
k=G->vexnum ; G->vexs[G->vexnum++]=*vp ;
if (G->kind==DG||G->kind==AG)
for (j=0 ; j<G->vexnum ; j++)
G->adj[j][k].ArcVal=G->adj[k][j].ArcVal=0 ;
/* 是不帶權的有向圖或無向圖 */
else
for (j=0 ; j<G->vexnum ; j++)
{ G->adj[j][k].ArcVal=INFINITY ;
G->adj[k][j].ArcVal=INFINITY ;
/* 是帶權的有向圖或無向圖 */
}
return(k) ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
向圖中增加一條弧
根據給定的弧或邊所依附的頂點,修改鄰接矩陣中所對應的陣列元素。
int AddArc(MGraph *G , ArcType *arc)
{ int k , j ;
k=LocateVex(G , &arc->vex1) ;
j=LocateVex(G , &arc->vex1) ;
if (k==-1||j==-1)
{ printf(“Arc’s Vertex do not existed !\n”) ;
return(-1) ;
if (G->kind==DG||G->kind==WDG)
{ G->adj[k][j].ArcVal=arc->ArcVal;
G->adj[k][j].ArcInfo=arc->ArcInfo ;
/* 是有向圖或帶權的有向圖*/
}
else
{ G->adj[k][j].ArcVal=arc->ArcVal ;
G->adj[j][k].ArcVal=arc->ArcVal ;
G->adj[k][j].ArcInfo=arc->ArcInfo ;
G->adj[j][k].ArcInfo=arc->ArcInfo ;
/* 是無向圖或帶權的無向圖,需對稱賦值 */
}
return(1) ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
最小生成樹
一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部頂點,但只有足以構成一棵樹的n-1條邊。那麼我們把構造連通網的最小代價生成樹稱為最小生成樹。
找連通網的最小生成樹,經典的有兩種演算法,普里姆演算法和克魯斯卡爾演算法。
鄰接矩陣儲存
@Test
public void Dijkstra(){
int distance[] = new int[9];
int pre[] = new int[9];
boolean finished[] = new boolean[9];
finished[0] = true;
for(int i=0;i<9;i++){
distance[i] = g1.adjMatrix[0][i];
}
int k = 0;
for(int i=1;i<9;i++){
int min = 65536;
for(int j=0;j<9;j++){
if(!finished[j]&&distance[j]<min){
min = distance[j];
k = j;
}
}
finished[k] = true;
System.out.println(pre[k]+","+k);
for(int j=1;j<9;j++){
if(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){
distance[j] = min+g1.adjMatrix[k][j];
pre[j] = k;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
拓撲排序
(1)棧:用來存放入度為0的節點;
(2)變種鄰接列表:作為圖的儲存結構;此鄰接列表的頂點節點還需要存放入度屬性;
private static String topologicalSort(Graph2 g2) {
Stack<Integer> s = new Stack<Integer>();
int count = 0;
for(int i=0;i<g2.nodes.length;i++){
if(g2.nodes[i].indegree==0){
s.push(i);
}
}
while(!s.isEmpty()){
int value = s.pop();
System.out.println(value+"、");
count++;
EdgeNode node = g2.nodes[value].next;
while(node!=null){
g2.nodes[node.idx].indegree--;
if(g2.nodes[node.idx].indegree==0){
s.push(node.idx);
}
node = node.next;
}
}
if(count<g2.nodes.length){
return "error";
}
return "ok";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
圖的邊表儲存結構
在某些應用中,有時主要考察圖中各個邊的權值以及所依附的兩個頂點,即圖的結構主要由邊來表示,稱為邊表儲存結構。
在邊表結構中,邊採用順序儲存,每個邊元素由三部分組成:邊所依附的兩個頂點和邊的權值;圖的頂點用另一個順序結構的頂點表儲存。如圖:
---------------------
1、名詞解釋:
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。在圖中的資料元素,我們稱之為頂點(Vertex),頂點集合有窮非空。在圖中,任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示,邊集可以是空的。
圖按照邊的有無方向分為無向圖和有向圖。無向圖由頂點和邊組成,有向圖由頂點和弧構成。弧有弧尾和弧頭之分,帶箭頭一端為弧頭。
圖按照邊或弧的多少分稀疏圖和稠密圖。如果圖中的任意兩個頂點之間都存在邊叫做完全圖,有向的叫有向完全圖。若無重複的邊或頂點到自身的邊則叫簡單圖。
圖中頂點之間有鄰接點、依附的概念。無向圖頂點的邊數叫做度。有向圖頂點分為入度和出度。
圖上的邊或弧帶有權則稱為網。
圖中頂點間存在路徑,兩頂點存在路徑則說明是連通的,如果路徑最終回到起始點則稱為環,當中不重複的叫簡單路徑。若任意兩頂點都是連通的,則圖就是連通圖,有向則稱為強連通圖。圖中有子圖,若子圖極大連通則就是連通分量,有向的則稱為強連通分量。
無向圖中連通且n個頂點n-1條邊稱為生成樹。有向圖中一頂點入度為0其餘頂點入度為1的叫有向樹。一個有向圖由若干棵有向樹構成生成森林。
2、圖的儲存結構—-鄰接矩陣
圖的鄰接矩陣的表示方式需要兩個陣列來表示圖的資訊,一個一維陣列表示每個資料元素的資訊,一個二維陣列(鄰接矩陣)表示圖中的邊或者弧的資訊。
如果圖有n個頂點,那麼鄰接矩陣就是一個n*n的方陣,方陣中每個元素的值的計算公式如下:
鄰接矩陣表示圖的具體示例如下圖所示:
首先給個無向圖的例項:
下面是一個有向圖的例項:
OK,到這裡為止,我們給出一個無向圖的鄰接矩陣和一個有向圖的鄰接矩陣,我們可以從這兩個鄰接矩陣得出一些結論:
無向圖的鄰接矩陣都是沿對角線對稱的
要知道無向圖中某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行或(第i列)的元素之和;
對於有向圖,要知道某個頂點的出度,其實就是這個頂點vi在鄰接矩陣中第i行的元素之和,如果要知道某個頂點的入度,那就是第i列的元素之和。
但是,如果我們需要表示的圖是一個網的時候,例如假設有個圖有n個頂點,同樣該網的鄰接矩陣也是一個n*n的方陣,只是方陣元素的值的計算方式不同,如下圖所示:
這裡的wij表示兩個頂點vi和vj邊上的權值。無窮大表示一個計算機允許的、大於所有邊上權值的值,也就是一個不可能的極限值。下面是具體示例,表示的一個有向網的圖和鄰接矩陣:
3、圖的儲存結構—-鄰接矩陣的程式碼實現
#include<iostream>
using namespace std;
enum Graphkind{ DG, DN, UDG, UDN }; //{有向圖,無向圖,有向網,無向網}
typedef struct Node
{
int * vex; //頂點陣列
int vexnum; //頂點個數
int edge; //圖的邊數
int ** adjMatrix; //圖的鄰接矩陣
enum Graphkind kind;
}MGraph;
void createGraph(MGraph & G,enum Graphkind kind)
{
cout << "輸入頂點的個數" << endl;
cin >> G.vexnum;
cout << "輸入邊的個數" << endl;
cin >> G.edge;
//輸入種類
//cout << "輸入圖的種類:DG:有向圖 DN:無向圖,UDG:有向網,UDN:無向網" << endl;
G.kind = kind;
//為兩個陣列開闢空間
G.vex = new int[G.vexnum];
G.adjMatrix = new int*[G.vexnum];
cout << G.vexnum << endl;
int i;
for (i = 0; i < G.vexnum; i++)
{
G.adjMatrix[i] = new int[G.vexnum];
}
for (i = 0; i < G.vexnum; i++)
{
for (int k = 0; k < G.vexnum; k++)
{
if (G.kind == DG || G.kind == DN)
{
G.adjMatrix[i][k] = 0;
}
else {
G.adjMatrix[i][k] = INT_MAX;
}
}
}
/*//輸入每個元素的資訊,這個資訊,現在還不需要使用
for (i = 0; i < G.vexnum; i++)
{
cin >> G.vex[i];
}*/
cout << "請輸入兩個有關係的頂點的序號:例如:1 2 代表1號頂點指向2號頂點" << endl;
for (i = 0; i < G.edge; i++)
{
int a, b;
cin >> a;
cin >> b;
if (G.kind == DN) {
G.adjMatrix[b - 1][a - 1] = 1;
G.adjMatrix[a - 1][b - 1] = 1;
}
else if (G.kind == DG)
{
G.adjMatrix[a - 1][b - 1] = 1;
}
else if (G.kind == UDG)
{
int weight;
cout << "輸入該邊的權重:" << endl;
cin >> weight;
G.adjMatrix[a - 1][b - 1] = weight;
}
else {
int weight;
cout << "輸入該邊的權重:" << endl;
cin >> weight;
G.adjMatrix[b - 1][a - 1] = weight;
G.adjMatrix[a - 1][b - 1] = weight;
}
}
}
void print(MGraph g)
{
int i, j;
for (i = 0; i < g.vexnum; i++)
{
for (j = 0; j < g.vexnum; j++)
{
if (g.adjMatrix[i][j] == INT_MAX)
cout << "∞" << " ";
else
cout << g.adjMatrix[i][j] << " ";
}
cout << endl;
}
}
void clear(MGraph G)
{
delete G.vex;
G.vex = NULL;
for (int i = 0; i < G.vexnum; i++)
{
delete G.adjMatrix[i];
G.adjMatrix[i] = NULL;
}
delete G.adjMatrix;
}
int main()
{
MGraph G;
cout << "有向圖例子:" << endl;
createGraph(G, DG);
print(G);
clear(G);
cout << endl;
cout << "無向圖例子:" << endl;
createGraph(G, DN);
print(G);
clear(G);
cout << endl;
cout << "有向圖網例子:" << endl;
createGraph(G, UDG);
print(G);
clear(G);
cout << endl;
cout << "無向圖網例子:" << endl;
createGraph(G, UDN);
print(G);
clear(G);
cout << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
輸出結果:
4、圖的儲存結構—-鄰接矩陣的優缺點
優點:
直觀、容易理解,可以很容易的判斷出任意兩個頂點是否有邊,最大的優點就是很容易計算出各個頂點的度。
缺點:
當我麼表示完全圖的時候,鄰接矩陣是最好的表示方法,但是對於稀疏矩陣,由於它邊少,但是頂點多,這樣就會造成空間的浪費。
5、 圖的儲存結構—鄰接表
鄰接表是圖的一種鏈式儲存結構。主要是應對於鄰接矩陣在頂點多邊少的時候,浪費空間的問題。它的方法就是宣告兩個結構。如下圖所示:
OK,我們雖然知道了鄰接表是這兩個結構來表示圖的,那麼它的怎麼表示的了,不急,我們先把它轉為c++程式碼先,然後,再給個示例,你就明白了。
typedef char Vertextype;
//表結點結構
struct ArcNode {
int adjvex; //某條邊指向的那個頂點的位置(一般是陣列的下標)。
ArcNode * nextarc; //指向下一個表結點
int weight; //這個只有網圖才需要使用。普通的圖可以直接忽略
};
//頭結點
struct Vnode
{
Vertextype data; //這個是記錄每個頂點的資訊(現在一般都不需要怎麼使用)
ArcNode * firstarc; //指向第一條依附在該頂點邊的資訊(表結點)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
無向圖的示例:
從圖中我們可以知道,頂點是通過一個頭結點型別的一維陣列來儲存的,其中我們每個頭結點的firstarc都是指向第一條依附在該頂點邊的資訊,表結點的adjvex表示的該邊的另外一個頂點在頂點陣列中的下標,weight域對於普通圖是無意義的,可以忽略,nextarc指向的是下一條依附在該頂點的邊的資訊。
下面再給出一個有向圖的例子:
通過上述的兩個例子,我們應該明白鄰接表是如何進行表示圖的資訊的了。
6、圖的儲存結構—-鄰接表的程式碼實現
#include<iostream>
#include<string>
using namespace std;
typedef string Vertextype;
//表結點結構
struct ArcNode {
int adjvex; //某條邊指向的那個頂點的位置(一般是陣列的下標)。
ArcNode * nextarc; //指向下一個表結點
int weight; //這個只有網圖才需要使用。
};
//頭結點
struct Vnode
{
Vertextype data; //這個是記錄每個頂點的資訊(現在一般都不需要怎麼使用)
ArcNode * firstarc; //指向第一條依附在該頂點邊的資訊(表結點)
};
//
struct Graph
{
int kind; //圖的種類(有向圖:0,無向圖:1,有向網:2,無向網:3)
int vexnum; //圖的頂點數
int edge; //圖的邊數
Vnode * node; //圖的(頂點)頭結點陣列
};
void createGraph(Graph & g,int kind)
{
cout << "請輸入頂點的個數:" << endl;
cin >> g.vexnum;
cout << "請輸入邊的個數(無向圖/網要乘2):" << endl;
cin >> g.edge;
g.kind = kind; //決定圖的種類
g.node = new Vnode[g.vexnum];
int i;
cout << "輸入每個頂點的資訊:" << endl;//記錄每個頂點的資訊
for (i = 0; i < g.vexnum; i++)
{
cin >> g.node[i].data;
g.node[i].firstarc=NULL;
}
cout << "請輸入每條邊的起點和終點的編號:" << endl;
for (i = 0; i < g.edge; i++)
{
int a;
int b;
cin >> a; //起點
cin >> b; //終點
ArcNode * next=new ArcNode;
next->adjvex = b - 1;
if(kind==0 || kind==1)
next->weight = -1;
else {//如果是網圖,還需要權重
cout << "輸入該邊的權重:" << endl;
cin >> next->weight;
}
next->nextarc = NULL;
//將邊串聯起來
if (g.node[a - 1].firstarc == NULL) {
g.node[a - 1].firstarc=next;
}
else
{
ArcNode * p;
p = g.node[a - 1].firstarc;
while (p->nextarc)//找到該連結串列的最後一個表結點
{
p = p->nextarc;
}
p->nextarc = next;
}
}
}
void print(Graph g)
{
int i;
cout << "圖的鄰接表為:" << endl;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data<<" ";
ArcNode * now;
now = g.node[i].firstarc;
while (now)
{
cout << now->adjvex << " ";
now = now->nextarc;
}
cout << endl;
}
}
int main()
{
Graph g;
cout << "有向圖的例子" << endl;
createGraph(g,0);
print(g);
cout << endl;
cout << "無向圖的例子" << endl;
createGraph(g, 1);
print(g);
cout << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
輸出結果:
7、圖的儲存結構—-鄰接表的優缺點
優點:
對於,稀疏圖,鄰接表比鄰接矩陣更節約空間。
缺點:
不容易判斷兩個頂點是有關係(邊),頂點的出度容易,但是求入度需要遍歷整個鄰接表。
8、有向圖的儲存結構—-十字連結串列
十字連結串列是有向圖的一個專有的連結串列結構,我們之前也說了,鄰接表對於我們計算頂點的入度是一個很麻煩的事情,而十字連結串列正好可以解決這個問題。十字連結串列和鄰接表一樣,他會有兩個結構來表示圖:其中一個結構用於儲存頂點資訊,另外一個結構是用於儲存每條邊的資訊,如下圖所示:
同樣,我們知道頭結點就是用於儲存每個頂點資訊的結構,其中data主要是儲存頂點的資訊(如頂點的名稱),firstin是儲存第一個入度的邊的資訊,firstout儲存第一個出度的邊的資訊。其中,表結點就是記錄每條邊的資訊,其中tailvex是記錄這條邊弧頭的頂點的在頂點表中的下標(不是箭頭那個),headvex則是記錄弧尾對應的那個頂點在頂點表中的下標(箭頭的那個),hlink是指向具有下一個具有相同的headvex的表結點,tlink指向具有相同的tailvex的表結點,weight是表示邊的權重(網圖才需要使用)。具體的程式碼表示如下:
typedef string Vertextype;
//表結點結構
struct ArcNode {
int tailvex; //弧尾的下標,一般都是和對應的頭結點下標相同
int headvex; //弧頭的下標
ArcNode * hlink; //指向下一個弧頭同為headvex的表結點 ,邊是箭頭的那邊
ArcNode * tlink; //指向下一個弧尾為tailvex的表結點,邊不是箭頭的那邊
int weight; //只有網才會用這個變數
};
//頭結點
struct Vnode
{
Vertextype data; //這個是記錄每個頂點的資訊(現在一般都不需要怎麼使用)
ArcNode *firstin; //指向第一條(入度)在該頂點的表結點
ArcNode *firstout; //指向第一條(出度)在該頂點的表結點
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
下面,我們給出一個有向圖的十字連結串列的例子:
其實,這個自己也可以去嘗試手畫一個十字連結串列出來,其實都是很簡單的
9、有向圖的儲存結構—-十字連結串列程式碼實現
#include<iostream>
#include<string>
using namespace std;
typedef string Vertextype;
//表結點結構
struct ArcNode {
int tailvex; //弧尾的下標,一般都是和對應的頭結點下標相同
int headvex; //弧頭的下標
ArcNode * hlink; //指向下一個弧頭同為headvex的表結點 ,邊是箭頭的那邊
ArcNode * tlink; //指向下一個弧尾為tailvex的表結點,邊不是箭頭的那邊
int weight; //只有網才會用這個變數
};
//頭結點
struct Vnode
{
Vertextype data; //這個是記錄每個頂點的資訊(現在一般都不需要怎麼使用)
ArcNode *firstin; //指向第一條(入度)在該頂點的表結點
ArcNode *firstout; //指向第一條(出度)在該頂點的表結點
};
struct Graph
{
int kind; //圖的種類(有向圖:0,有向網:1)
int vexnum; //圖的頂點數
int edge; //圖的邊數
Vnode * node; //圖的(頂點)頭結點陣列
};
void createGraph(Graph & g,int kind)
{
cout << "請輸入頂點的個數:" << endl;
cin >> g.vexnum;
cout << "請輸入邊的個數(無向圖/網要乘2):" << endl;
cin >> g.edge;
g.kind = kind; //決定圖的種類
g.node = new Vnode[g.vexnum];
int i;
cout << "輸入每個頂點的資訊:" << endl;//記錄每個頂點的資訊
for (i = 0; i < g.vexnum; i++)
{
cin >> g.node[i].data;
g.node[i].firstin = NULL;
g.node[i].firstout = NULL;
}
cout << "請輸入每條邊的起點和終點的編號:" << endl;
for (i = 0; i < g.edge; i++)
{
int a, b;
cin >> a;
cin >> b;
ArcNode * next = new ArcNode;
next->tailvex = a - 1; //首先是弧頭的下標
next-> headvex = b - 1; //弧尾的下標
//只有網圖需要權重資訊
if(kind==0)
next->weight = -1;
else
{
cout << "輸入該邊的權重:" << endl;
cin >> next->weight;
}
next->tlink = NULL;
next->hlink = NULL;
//該位置的頂點的出度還為空時,直接讓你fisrstout指標指向新的表結點
//記錄的出度資訊
if (g.node[a - 1].firstout == NULL)
{
g.node[a - 1].firstout = next;
}
else
{
ArcNode * now;
now = g.node[a - 1].firstout;
while (now->tlink)
{
now = now->tlink;
}
now->tlink = next;
}
//記錄某個頂點的入度資訊
if (g.node[b - 1].firstin == NULL)
{
g.node[b - 1].firstin = next;
}
else {
ArcNode * now;
now = g.node[b - 1].firstin;
while (now->hlink)//找到最後一個表結點
{
now = now->hlink;
}
now->hlink = next;//更新最後一個表結點
}
}
}
void print(Graph g)
{
int i;
cout << "各個頂點的出度資訊" << endl;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstout;
while (now)
{
cout << now->headvex << " ";
now = now->tlink;
}
cout << "^" << endl;
}
cout << "各個頂點的入度資訊" << endl;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstin;
while (now)
{
cout << now->tailvex << " ";
now = now->hlink;
}
cout << "^" << endl;
}
}
int main()
{
Graph g;
cout << "有向圖的例子" << endl;
createGraph(g, 0);
print(g);
cout << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
10、無向圖的儲存結構—-鄰接多重表
鄰接多重表是無向圖的另一種鏈式儲存結構。我們之前也說了使用鄰接矩陣來儲存圖比價浪費空間,但是如果我們使用鄰接表來儲存圖時,對於無向圖又有一些不便的地方,例如我們需要對一條已經訪問過的邊進行刪除或者標記等操作時,我們除了需要找到表示同一條邊的兩個結點。這會給我們的程式執行效率大打折扣,所以這個時候,鄰接多重表就派上用場啦。
首先,鄰接多重表同樣是對鄰接表的一個改進得到來的結構,它同樣需要一個頭結點儲存每個頂點的資訊和一個表結點,儲存每條邊的資訊,他們的結構如下:
其中,頭結點的結構和鄰接表一樣,而表結點中就改變比較大了,其中mark為標誌域,例如標誌是否已經訪問過,ivex和jvex代表邊的兩個頂點在頂點表中的下標,ilink指向下一個依附在頂點ivex的邊,jlink指向下一個依附在頂點jvex的邊,weight在網圖的時候使用,代表該邊的權重。
下面是一個無向圖的鄰接多重表的例項(自己也可以嘗試去畫畫,具體的原理都是很簡單的):
11、無向圖的儲存結構—-鄰接多重表程式碼實現
#include<iostream>
#include<string>
using namespace std;
//表結點
struct ArcNode
{
int mark; //標誌位
int ivex; //輸入邊資訊的那個起點
ArcNode * ilink; //依附在頂點ivex的下一條邊的資訊
int jvex; //輸入邊資訊的那個終點
ArcNode * jlink; //依附在頂點jvex的下一條邊的資訊
int weight;
};
//頭結點
struct VexNode {
string data; //頂點的資訊,如頂點名稱
ArcNode * firstedge; //第一條依附頂點的邊
};
struct Graph {
int vexnum; //頂點的個數
int edge; //邊的個數
VexNode *node; //儲存頂點資訊
};
void createGraph(Graph & g)
{
cout << "請輸入頂點的個數:" << endl;
cin >> g.vexnum;
cout << "請輸入邊的個數(無向圖/網要乘2):" << endl;
cin >> g.edge;
g.node = new VexNode[g.vexnum];
int i;
cout << "輸入每個頂點的資訊:" << endl;//記錄每個頂點的資訊
for (i = 0; i < g.vexnum; i++)
{
cin >> g.node[i].data;
g.node[i].firstedge = NULL;
}
cout << "請輸入每條邊的起點和終點的編號:" << endl;
for (i = 0; i < g.edge; i++)
{
int a, b;
cin >> a;
cin >> b;
ArcNode * next = new ArcNode;
next->mark = 0;
next->ivex = a - 1; //首先是弧頭的下標
next->jvex = b - 1; //弧尾的下標
next->weight = -1;
next->ilink = NULL;
next->jlink = NULL;
//更新頂點表a-1的資訊
if (g.node[a - 1].firstedge == NULL)
{
g.node[a - 1].firstedge = next;
}
else {
ArcNode * now;
now = g.node[a - 1].firstedge;
while (1) {
if (now->ivex == (a - 1) && now->ilink == NULL)
{
now->ilink = next;
break;
}
else if (now->ivex == (a - 1) && now->ilink != NULL) {
now = now->ilink;
}
else if (now->jvex == (a - 1) && now->jlink == NULL)
{
now->jlink = next;
break;
}
else if (now->jvex == (a - 1) && now->jlink != NULL) {
now = now->jlink;
}
}
}
//更新頂點表b-1
if (g.node[b - 1].firstedge == NULL)
{
g.node[b - 1].firstedge = next;
}
else {
ArcNode * now;
now = g.node[b - 1].firstedge;
while (1) {
if (now->ivex == (b - 1) && now->ilink == NULL)
{
now->ilink = next;
break;
}
else if (now->ivex == (b - 1) && now->ilink != NULL) {
now = now->ilink;
}
else if (now->jvex == (b - 1) && now->jlink == NULL)
{
now->jlink = next;
break;
}
else if (now->jvex == (b - 1) && now->jlink != NULL) {
now = now->jlink;
}
}
}
}
}
void print(Graph g)
{
int i;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstedge;
while (now)
{
cout << "ivex=" << now->ivex << " jvex=" << now->jvex << " ";
if (now->ivex == i)
{
now = now->ilink;
}
else if (now->jvex == i)
{
now = now->jlink;
}
}
cout << endl;
}
}
int main()
{
Graph g;
createGraph(g);
print(g);
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
輸出結果:
---------------------