數據結構之-圖的知識及計算機存儲方法

一、圖的定義

樹的表示是分層的,例如父子關係,而其他關係只能間接的表示,例如同級關係。而圖卻不受這種限制。圖是由頂點(或結點)及頂點之間的關係組成的集合。通常,圖中的頂點數量或者一個頂點與其他頂點之間的連線的個數不受限制。

圖是由頂點集合(Vertex)及頂點間的關係集合組成的一種數據結構:Graph=( V, E )

V = {x | x ∈某個數據對象 } 是頂點的有窮非空集合;

E ={ (x, y) | x, y ∈V } 是頂點之間關係的有窮集合,也叫做邊(Edge)集合。

下圖的都是圖的表示
在這裏插入圖片描述
可能有人會提出異議,G2和G3怎麼會是圖,明明就是二叉樹和線性表嘛。不錯,你們說的是對的,但是根據圖的定義我們不難發現,二叉樹和線性表也符合圖的特徵。大家來看,二叉樹的所有結點是不是相當於頂點的集合,結點間都存在一定的關係;線性表也是一樣,也就是說二叉樹和線性表是特殊的圖。只不過圖比線性表和樹更加複雜罷了。

在線性表中,數據元素之間僅有線性關係,每個數據元素只有一個直接前驅和一個直接後繼;在樹形結構中,數據元素之間有着明顯的層次關係,並且每一層上的數據元素可能和下一層中多個元素相關,但只能和上一層中的一個元素相關;而在圖形結構中,結點之間的關係可以是任意的,圖中任意兩個數據元素之間都可能相關。

圖是一種擴展的樹結構,每個結點可以指向任意的其它結點。鏈表是特殊的樹結構,樹是特殊的圖結構。圖這種數據結構常用於網絡規劃和路徑路徑規劃等領域。GPS相關產品中大量應用了圖結構和圖算法。

二、相關術語

  1. 無向邊:若頂點 x 和 y 之間的邊沒有方向,則稱該邊爲無向邊(x, y),(x, y) 與
    (y,x) 意義相同,表示 x 和 y 之間有連接。

  2. 無向圖:若圖中任意兩個頂點之間的邊均是無向邊,則稱該圖爲無向圖(如上圖G1,G2)。

  3. 有向邊:若頂點 x 和 y 之間的邊有方向,則稱該邊爲有向邊<x, y>,<x, y> 與
    <y, x> 意義不同,表示從 x 連接到 y,x 稱爲尾,y 稱爲頭。

  4. 有向圖:若圖中任意兩個頂點之間的邊均是有向邊,則稱該圖爲有向圖(如上圖G3,G4)。

  5. 在圖中的兩個重要關係是鄰接和關聯。

  6. 鄰接:是兩個頂點之間的一種關係。如果圖包含(u,v),則稱頂點v與頂點u鄰接。在無向圖中,這也暗示了頂點u也與頂點v鄰接。換句話說,在無向圖中鄰接關係是對稱的。

  7. 關聯:是指頂點和邊之間的關係。在有向圖中,邊(u,v)從頂點u開始關聯到v,或者相反,從頂點v開始關聯到u。在無向圖中,邊(u,v)與頂點u和v相關聯。

  8. 完全圖:每個頂點都與其他頂點相鄰接的圖。

  9. 度(Degree)的定義:頂點 v 的度是和 v 相關聯的邊的數目,記爲TD(v)。

  10. 入度:以 v 爲頭的邊的數目,記爲ID(v)

  11. 出度:以 v 爲尾的邊的數目,記爲OD(v)

    很顯然:TD(v) = ID(v) + OD(v)

    E = [TD(v1) + TD(v2) + … + TD(vn)] / 2

    E = ID(v1) + ID(v2) + … + ID(vn)

    E = OD(v1) + OD(v2) + … + OD(vn)

  12. 權(Weight)的定義:與圖的邊相關的數字叫做權,權常用來表示圖中頂點間的距離或者耗費。帶權的圖通常稱爲網。
    在這裏插入圖片描述

  13. 路徑:依次遍歷頂點序列之間的邊所形成的軌跡。沒有重複頂點的路徑稱爲簡單路徑。路徑的長度是路徑上的邊或弧的數目。

  14. 環是指路徑包含相同的頂點兩次或兩次以上。也就是說,在有向圖的一條路徑中,如果從某頂點出發,最後能夠返回該頂點,則該路徑是環。除了第一個頂點和最後一個頂點之外,其餘頂點不重複出現的迴路稱爲簡單環或簡單迴路。

  15. 連通性是圖中另一個重要的概念。對於無向圖而言,如果它的每個頂點都能通過某條路徑到達其他頂點,那麼我們稱它爲聯通的。如果該條件在有向圖中同樣成立,則稱該圖是強連通。儘管無向圖可能不是連通的,但它扔然可能包含連通的部分,這部分分支爲連通分支。如果有向圖中只有部分是強連通的,則該部分稱爲強連通分支。

  16. 某些特定的頂點對於保護圖或連通分支的連通性有特殊的重要意義。如果移除某個頂點將使得圖或某分支失去連通性,則稱該頂點爲關結點。

三、圖的一些常用操作:

創建圖,銷燬圖,清空圖,加入邊,刪除邊,獲取權,獲取結點的度,獲取圖的結點數,獲取圖的邊數
在這裏插入圖片描述

四、圖的二種常用數據存儲結構

  1. 鄰接矩陣表數據存儲結構

圖的鄰接矩陣存儲方式是用兩個數組表示。

一個一維數組儲存圖的頂點(V)信息,一個二維數組儲存圖的邊或是弧(E)的信息

假如圖G有n個頂點,則鄰接矩陣是一個nXn的方陣,定義爲:
在這裏插入圖片描述
如下無向圖:
在這裏插入圖片描述
如下有向圖:
在這裏插入圖片描述
java數據存儲結構定義方式:

Class MGraph{

// 頂點個數

int numNodes;

//一維數組儲存圖的頂點(V)信息

String[] vexs = new String[numNodes];

//鄰接矩陣,二維數組儲存圖的邊或是弧(E)的信息

int[][] edgs = new int[numNodes][numNodes];

}

這種定義方式是邊很多的情況下使用,如果邊很少,用這種方式就很浪費空間,就應該採用鄰接連接表數據存儲結構

  1. 鄰接連接表數據存儲結構

對於邊界較少的圖來說,這種存儲方式會對空間造成極大的浪費,尤其對於稀疏有向圖來說。所以可以考慮使用鏈表的方式來按需分配。

數組鏈表的相結合的存儲方式被稱爲鄰接表

1)對於所有頂點的值使用數組進行存儲

2)對於每個頂點的邊使用鏈表進行存儲

java實現鄰接表的數據結構如下:

1)//創建一個頂點類型,用來表示頂點信息

class Vexs{

String data;//頂點的值

Chain chain;//本頂點的鏈表

}

2)//創建一個鏈表類型

class Chain{

int id;//相鄰接頂點的數組下標

Chain next;//下一個相鄰接頂點的數組下標

}

3)//定義一個圖節點數組

class ALGraph{

int numNodes;//頂點個數

Vexs vexs[] = new Vexs[numNodes];//頂點數組

六、圖的遍歷算法:深度遍歷算法和廣度遍歷算法

  1. 基於鄰接矩陣表的圖遍歷算法

  2. 深度優先遍歷算法

上面鄰接矩陣表的數據存儲結構已經把圖抽象成一個類,因此我們可以將圖的遍歷定義成類的方法。對於連通圖,調用遍歷算法後即可訪問所有結點,但對於非連通圖,調用遍歷算法後仍有一些結點沒有被訪問,需要從圖中另選一個未被訪問的頂點再次調用遍歷算法。因此需要附設一個訪問標誌數組visited[n],來記錄被訪問的結點。增加了訪問標誌的數據存儲結構如下:

(1)定義圖的數據結構

class AMGraph{

// 頂點個數

int numNodes;

//一維數組儲存圖的頂點(V)信息

String[] vexs = new String[numNodes];

//鄰接矩陣,二維數組儲存圖的邊或是弧(E)的信息

int[][] edgs = new int[numNodes][numNodes];

//false表示該位置的頂點未訪問,true表示已訪問

boolean[] visited = null;

}

(2)圖的初始化:。。。

(3)圖的深度遍歷

for(int i = 0; i < this.visited.length; i++) {

//對未訪問的頂點調用深度優先遍歷算法

if(!this.visited[i]) {

dFS_AM(i); //深度優先遍歷算法

}

}

//深度優先遍歷算法

public void dFS_AM(int site) {//輸入深度優先遍歷的開始頂點

System.out.println(this.vexs[site]); //輸出該頂點

this.visited[site] = true; //置訪問標誌爲true

for(int i = 0; i < this.vexs.length; i++) {

//依次查找未訪問鄰接點,並以該鄰接點爲始點調用深度優先遍歷算法

if(this.arcs[site][i] != 0 && !this.visited[i]) {

this.dFS_AM(i);

}

}

}

  1. 廣度優先遍歷算法

public void bFS_AM(int site) { //輸入開始頂點

System.out.println(this.vexs[site]); //輸出該頂點

this.visited[site] = true; //置訪問標誌爲true

LinkedList<Integer> linkedList = new LinkedList<Integer>();
//藉助隊列來實現廣度優先遍歷

linkedList.offer(site); //將訪問過的頂點入隊

while(!linkedList.isEmpty()) {

int vexSite = linkedList.poll();//隊頭頂點出隊

for(int i = 0; i < this.vexs.length; i++) {

if(this.arcs[vexSite][i] != 0 && !this.visited[i]) {
//依次查找未訪問的鄰接點進行訪問後入隊

System.out.println(this.vexs[i]);

this.visited[i] = true;

linkedList.offer(i);

}

}

}

}
.vexs.length; i++) {

if(this.arcs[vexSite][i] != 0 && !this.visited[i]) {
//依次查找未訪問的鄰接點進行訪問後入隊

System.out.println(this.vexs[i]);

this.visited[i] = true;

linkedList.offer(i);

}

}

}

}