演算法與資料結構之圖的相關知識,簡單易懂。
一、 圖的理論基礎
1) 概要
本章介紹資料結構中圖的基本概念。
目錄
1. 圖的基本概念
2. 圖的儲存結構
2) 圖的基本概念
1. 圖的定義
定義:圖(graph)是由一些點(vertex)和這些點之間的連線(edge)所組成的;其中,點通常被成為"頂點(vertex)",而點與點之間的連線則被成為"邊或弧"(edege)。通常記為,G=(V,E)。
2. 圖的種類
根據邊是否有方向,將圖可以劃分為:無向圖和有向圖。
2.1 無向圖
上面的圖G0是無向圖,無向圖的所有的邊都是不區分方向的。G0=(V1,{E1})。其中,
(01) V1={A,B,C,D,E,F}。 V1表示由"A,B,C,D,E,F"幾個頂點組成的集合。
(02) E1={(A,B),(A,C),(B,C),(B,E),(B,F),(C,F),(C,D),(E,F),(C,E)}。 E1是由邊(A,B),邊(A,C)...等等組成的集合。其中,(A,C)表示由頂點A和頂點C連線成的邊。
2.2 有向圖
上面的圖G2是有向圖。和無向圖不同,有向圖的所有的邊都是有方向的! G2=(V2,{A2})。其中,
(01) V2={A,C,B,F,D,E,G}。 V2表示由"A,B,C,D,E,F,G"幾個頂點組成的集合。
(02)A2={<A,B>,<B,C>,<B,F>,<B,E>,<C,E>,<E,D>,<D,C>,<E,B>,<F,G>}。 E1是由向量<A,B>,向量<B,C>...等等組成的集合。其中,向量<A,B)表示由"頂點A"指向"頂點C"的有向邊。
3. 鄰接點和度
3.1 鄰接點
一條邊上的兩個頂點叫做鄰接點。
例如,上面無向圖G0中的頂點A和頂點C就是鄰接點。
在有向圖中,除了鄰接點之外;還有"入邊"和"出邊"的概念。
頂點的入邊,是指以該頂點為終點的邊。而頂點的出邊,則是指以該頂點為起點的邊。
例如,上面有向圖G2中的B和E是鄰接點;<B,E>是B的出邊,還是E的入邊。
3.2 度
在無向圖中,某個頂點的度是鄰接到該頂點的邊(或弧)的數目。
例如,上面無向圖G0中頂點A的度是2。
在有向圖中,度還有"入度"和"出度"之分。
某個頂點的入度,是指以該頂點為終點的邊的數目。而頂點的出度,則是指以該頂點為起點的邊的數目。
頂點的度=入度+出度。
例如,上面有向圖G2中,頂點B的入度是2,出度是3;頂點B的度=2+3=5。
4. 路徑和迴路
路徑:如果頂點(Vm)到頂點(Vn)之間存在一個頂點序列。則表示Vm到Vn是一條路徑。
路徑長度:路徑中"邊的數量"。
簡單路徑:若一條路徑上頂點不重複出現,則是簡單路徑。
迴路:若路徑的第一個頂點和最後一個頂點相同,則是迴路。
簡單迴路:第一個頂點和最後一個頂點相同,其它各頂點都不重複的迴路則是簡單迴路。
5. 連通圖和連通分量
連通圖:對無向圖而言,任意兩個頂點之間都存在一條無向路徑,則稱該無向圖為連通圖。 對有向圖而言,若圖中任意兩個頂點之間都存在一條有向路徑,則稱該有向圖為強連通圖。
連通分量:非連通圖中的各個連通子圖稱為該圖的連通分量。
6. 權
在學習"哈夫曼樹"的時候,瞭解過"權"的概念。圖中權的概念與此類似。
上面就是一個帶權的圖。
3) 圖的儲存結構
上面瞭解了"圖的基本概念",下面開始介紹圖的儲存結構。圖的儲存結構,常用的是"鄰接矩陣"和"鄰接表"。
1. 鄰接矩陣
鄰接矩陣是指用矩陣來表示圖。它是採用矩陣來描述圖中頂點之間的關係(及弧或邊的權)。
假設圖中頂點數為n,則鄰接矩陣定義為:
下面通過示意圖來進行解釋。
圖中的G1是無向圖和它對應的鄰接矩陣。
圖中的G2是無向圖和它對應的鄰接矩陣。
通常採用兩個陣列來實現鄰接矩陣:一個一維陣列用來儲存頂點資訊,一個二維陣列來用儲存邊的資訊。
鄰接矩陣的缺點就是比較耗費空間。
2. 鄰接表
鄰接表是圖的一種鏈式儲存表示方法。它是改進後的"鄰接矩陣",它的缺點是不方便判斷兩個頂點之間是否有邊,但是相對鄰接矩陣來說更省空間。
圖中的G1是無向圖和它對應的鄰接矩陣。
圖中的G2是無向圖和它對應的鄰接矩陣。
二、 鄰接矩陣無向圖之Java詳解
1) 概要
本文通過Java實現鄰接矩陣無向圖。
目錄
1. 鄰接矩陣無向圖的介紹
2. 鄰接矩陣無向圖的程式碼說明
3. 鄰接矩陣無向圖的完整原始碼
2) 鄰接矩陣無向圖的介紹
鄰接矩陣無向圖是指通過鄰接矩陣表示的無向圖。
上面的圖G1包含了"A,B,C,D,E,F,G"共7個頂點,而且包含了"(A,C),(A,D),(A,F),(B,C),(C,D),(E,G),(F,G)"共7條邊。由於這是無向圖,所以邊(A,C)和邊(C,A)是同一條邊;這裡列舉邊時,是按照字母先後順序列舉的。
上圖右邊的矩陣是G1在記憶體中的鄰接矩陣示意圖。A[i][j]=1表示第i個頂點與第j個頂點是鄰接點,A[i][j]=0則表示它們不是鄰接點;而A[i][j]表示的是第i行第j列的值;例如,A[1,2]=1,表示第1個頂點(即頂點B)和第2個頂點(C)是鄰接點。
3) 鄰接矩陣無向圖的程式碼說明
1. 基本定義
public class MatrixUDG {
private char[] mVexs; // 頂點集合
private int[][] mMatrix; // 鄰接矩陣
...
}
MatrixUDG是鄰接矩陣對應的結構體。mVexs用於儲存頂點,mMatrix則是用於儲存矩陣資訊的二維陣列。例如,mMatrix[i][j]=1,則表示"頂點i(即mVexs[i])"和"頂點j(即mVexs[j])"是鄰接點;mMatrix[i][j]=0,則表示它們不是鄰接點。
2. 建立矩陣
這裡介紹提供了兩個建立矩陣的方法。一個是用已知資料,另一個則需要使用者手動輸入資料。
2.1 建立圖(用已提供的矩陣)
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public MatrixUDG(char[] vexs, char[][]edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
mMatrix[p1][p2] = 1;
mMatrix[p2][p1] = 1;
}
}
該函式的作用是利用已知資料來建立一個鄰接矩陣無向圖。 實際上,在本文的測試程式原始碼中,該方法建立的無向圖就是上面圖G1。具體的呼叫程式碼如下:
char[] vexs = {'A', 'B', 'C', 'D', 'E','F', 'G'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'F', 'G'}};
MatrixUDG pG;
pG = new MatrixUDG(vexs, edges);
2.2 建立圖(自己輸入)
/*
* 建立圖(自己輸入資料)
*/
public MatrixUDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("input error: invalid edge!\n");
return ;
}
mMatrix[p1][p2] = 1;
mMatrix[p2][p1] = 1;
}
}
該函式是通過讀取使用者的輸入,而將輸入的資料轉換成對應的無向圖。
4) 鄰接矩陣無向圖的完整原始碼
/**
*Java: 鄰接矩陣表示的"無向圖(ListUndirected Graph)"
*
*@author skywang
*@date 2014/04/19
*/
import java.io.IOException;
import java.util.Scanner;
public class MatrixUDG {
private char[] mVexs; // 頂點集合
private int[][] mMatrix; // 鄰接矩陣
/*
* 建立圖(自己輸入資料)
*/
public MatrixUDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("inputerror: invalid edge!\n");
return ;
}
mMatrix[p1][p2] = 1;
mMatrix[p2][p1] = 1;
}
}
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public MatrixUDG(char[] vexs, char[][] edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
mMatrix[p1][p2] = 1;
mMatrix[p2][p1] = 1;
}
}
/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i]==ch)
return i;
return -1;
}
/*
* 讀取一個輸入字元
*/
private char readChar() {
char ch='0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') ||(ch>='A'&&ch<='Z')));
return ch;
}
/*
* 讀取一個輸入字元
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
/*
* 列印矩陣佇列圖
*/
public void print() {
System.out.printf("Martix Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
for (int j = 0; j < mVexs.length; j++)
System.out.printf("%d", mMatrix[i][j]);
System.out.printf("\n");
}
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'F', 'G'}};
MatrixUDG pG;
// 自定義"圖"(輸入矩陣佇列)
//pG = new MatrixUDG();
// 採用已有的"圖"
pG = new MatrixUDG(vexs, edges);
pG.print(); // 列印圖
}
}
三、 鄰接表無向圖之Java詳解
1) 概要
本文通過Java實現鄰接表無向圖。
目錄
1. 鄰接表無向圖的介紹
2. 鄰接表無向圖的程式碼說明
3. 鄰接表無向圖的完整原始碼
2) 鄰接表無向圖的介紹
鄰接表無向圖是指通過鄰接表表示的無向圖。
上面的圖G1包含了"A,B,C,D,E,F,G"共7個頂點,而且包含了"(A,C),(A,D),(A,F),(B,C),(C,D),(E,G),(F,G)"共7條邊。
上圖右邊的矩陣是G1在記憶體中的鄰接表示意圖。每一個頂點都包含一條連結串列,該連結串列記錄了"該頂點的鄰接點的序號"。例如,第2個頂點(頂點C)包含的連結串列所包含的節點的資料分別是"0,1,3";而這"0,1,3"分別對應"A,B,D"的序號,"A,B,D"都是C的鄰接點。就是通過這種方式記錄圖的資訊的。
3) 鄰接表無向圖的程式碼說明
1. 基本定義
public class ListUDG {
// 鄰接表中表對應的連結串列的頂點
private class ENode {
int ivex; // 該邊所指向的頂點的位置
ENode nextEdge; // 指向下一條弧的指標
}
// 鄰接表中表的頂點
private class VNode {
char data; // 頂點資訊
ENode firstEdge; // 指向第一條依附該頂點的弧
};
private VNode[] mVexs; // 頂點陣列
...
}
(01) ListUDG是鄰接表對應的結構體。mVexs則是儲存頂點資訊的一維陣列。
(02) VNode是鄰接表頂點對應的結構體。 data是頂點所包含的資料,而firstEdge是該頂點所包含連結串列的表頭指標。
(03) ENode是鄰接表頂點所包含的連結串列的節點對應的結構體。 ivex是該節點所對應的頂點在vexs中的索引,而nextEdge是指向下一個節點的。
2. 建立矩陣
這裡介紹提供了兩個建立矩陣的方法。一個是用已知資料,另一個則需要使用者手動輸入資料。
2.1 建立圖(用已提供的矩陣)
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public ListUDG(char[] vexs, char[][] edges){
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}
// 初始化"邊"
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
char c1 = edges[i][0];
char c2 = edges[i][1];
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
// 將node2連結到"p2所在連結串列的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}
該函式的作用是建立一個鄰接表無向圖。實際上,該方法建立的無向圖,就是上面圖G1。呼叫程式碼如下:
char[] vexs = {'A', 'B', 'C', 'D', 'E','F', 'G'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'F', 'G'}};
ListUDG pG;
pG = new ListUDG(vexs, edges);
2.2 建立圖(自己輸入)
/*
* 建立圖(自己輸入資料)
*/
public ListUDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}
// 初始化"邊"
//mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
// 將node2連結到"p2所在連結串列的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge, node2);
}
}
該函式是讀取使用者的輸入,將輸入的資料轉換成對應的無向圖。
4) 鄰接表無向圖的完整原始碼
/**
*Java: 鄰接表表示的"無向圖(ListUndirected Graph)"
*
*@author skywang
*@date 2014/04/19
*/
import java.io.IOException;
import java.util.Scanner;
public class ListUDG {
// 鄰接表中表對應的連結串列的頂點
private class ENode {
int ivex; // 該邊所指向的頂點的位置
ENode nextEdge; // 指向下一條弧的指標
}
// 鄰接表中表的頂點
private class VNode {
char data; // 頂點資訊
ENode firstEdge; // 指向第一條依附該頂點的弧
};
private VNode[] mVexs; // 頂點陣列
/*
* 建立圖(自己輸入資料)
*/
public ListUDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}
// 初始化"邊"
//mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge,node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
// 將node2連結到"p2所在連結串列的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge,node2);
}
}
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public ListUDG(char[] vexs, char[][] edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}
// 初始化"邊"
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
char c1 = edges[i][0];
char c2 = edges[i][1];
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge,node1);
// 初始化node2
ENode node2 = new ENode();
node2.ivex = p1;
// 將node2連結到"p2所在連結串列的末尾"
if(mVexs[p2].firstEdge == null)
mVexs[p2].firstEdge = node2;
else
linkLast(mVexs[p2].firstEdge,node2);
}
}
/*
* 將node節點連結到list的最後
*/
private void linkLast(ENode list, ENode node) {
ENode p = list;
while(p.nextEdge!=null)
p = p.nextEdge;
p.nextEdge = node;
}
/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i].data==ch)
return i;
return -1;
}
/*
* 讀取一個輸入字元
*/
private char readChar() {
char ch='0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') ||(ch>='A'&&ch<='Z')));
return ch;
}
/*
* 讀取一個輸入字元
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
/*
* 列印矩陣佇列圖
*/
public void print() {
System.out.printf("List Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("%d(%c): ", i, mVexs[i].data);
ENode node = mVexs[i].firstEdge;
while (node != null) {
System.out.printf("%d(%c)", node.ivex, mVexs[node.ivex].data);
node = node.nextEdge;
}
System.out.printf("\n");
}
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'F', 'G'}};
ListUDG pG;
// 自定義"圖"(輸入矩陣佇列)
//pG = new ListUDG();
// 採用已有的"圖"
pG = new ListUDG(vexs, edges);
pG.print(); // 列印圖
}
}
四、 鄰接矩陣有向圖之Java詳解
1) 概要
本文通過Java實現鄰接矩陣有向圖。
目錄
1. 鄰接矩陣有向圖的介紹
2. 鄰接矩陣有向圖的程式碼說明
3. 鄰接矩陣有向圖的完整原始碼
2) 鄰接矩陣有向圖的介紹
鄰接矩陣有向圖是指通過鄰接矩陣表示的有向圖。
上面的圖G2包含了"A,B,C,D,E,F,G"共7個頂點,而且包含了"<A,B>,<B,C>,<B,E>,<B,F>,<C,E>,<D,C>,<E,B>,<E,D>,<F,G>"共9條邊。
上圖右邊的矩陣是G2在記憶體中的鄰接矩陣示意圖。A[i][j]=1表示第i個頂點到第j個頂點是一條邊,A[i][j]=0則表示不是一條邊;而A[i][j]表示的是第i行第j列的值;例如,A[1,2]=1,表示第1個頂點(即頂點B)到第2個頂點(C)是一條邊。
3) 鄰接矩陣有向圖的程式碼說明
1. 基本定義
public class MatrixDG {
private char[] mVexs; // 頂點集合
private int[][] mMatrix; // 鄰接矩陣
...
}
MatrixDG是鄰接矩陣有向圖對應的結構體。
mVexs用於儲存頂點,mMatrix則是用於儲存矩陣資訊的二維陣列。例如,mMatrix[i][j]=1,則表示"頂點i(即mVexs[i])"和"頂點j(即mVexs[j])"是鄰接點,且頂點i是起點,頂點j是終點。
2. 建立矩陣
這裡介紹提供了兩個建立矩陣的方法。一個是用已知資料,另一個則需要使用者手動輸入資料。
2.1 建立圖(用已提供的矩陣)
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public MatrixDG(char[] vexs, char[][]edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
mMatrix[p1][p2] = 1;
}
}
該函式的作用是建立一個鄰接矩陣有向圖。實際上,該方法建立的有向圖,就是上面的圖G2。它的呼叫方法如下:
char[] vexs = {'A', 'B', 'C', 'D', 'E','F', 'G'};
char[][] edges = new char[][]{
{'A', 'B'},
{'B', 'C'},
{'B', 'E'},
{'B', 'F'},
{'C', 'E'},
{'D', 'C'},
{'E', 'B'},
{'E', 'D'},
{'F', 'G'}};
MatrixDG pG;
pG = new MatrixDG(vexs, edges);
2.2 建立圖(自己輸入)
/*
* 建立圖(自己輸入資料)
*/
public MatrixDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("input error: invalid edge!\n");
return ;
}
mMatrix[p1][p2] = 1;
}
}
該函式是讀取使用者的輸入,將輸入的資料轉換成對應的有向圖。
4) 鄰接矩陣有向圖的完整原始碼
/**
*Java: 鄰接矩陣圖
*
*@author skywang
*@date 2014/04/19
*/
import java.io.IOException;
import java.util.Scanner;
public class MatrixDG {
private char[] mVexs; // 頂點集合
private int[][] mMatrix; // 鄰接矩陣
/*
* 建立圖(自己輸入資料)
*/
public MatrixDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = readChar();
}
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
if (p1==-1 || p2==-1) {
System.out.printf("inputerror: invalid edge!\n");
return ;
}
mMatrix[p1][p2] = 1;
}
}
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public MatrixDG(char[] vexs, char[][] edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new char[vlen];
for (int i = 0; i < mVexs.length; i++)
mVexs[i] = vexs[i];
// 初始化"邊"
mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
mMatrix[p1][p2] = 1;
}
}
/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i]==ch)
return i;
return -1;
}
/*
* 讀取一個輸入字元
*/
private char readChar() {
char ch='0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') ||(ch>='A'&&ch<='Z')));
return ch;
}
/*
* 讀取一個輸入字元
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
/*
* 列印矩陣佇列圖
*/
public void print() {
System.out.printf("Martix Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
for (int j = 0; j < mVexs.length; j++)
System.out.printf("%d", mMatrix[i][j]);
System.out.printf("\n");
}
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char[][] edges = new char[][]{
{'A', 'B'},
{'B', 'C'},
{'B', 'E'},
{'B', 'F'},
{'C', 'E'},
{'D', 'C'},
{'E', 'B'},
{'E', 'D'},
{'F', 'G'}};
MatrixDG pG;
// 自定義"圖"(輸入矩陣佇列)
//pG = new MatrixDG();
// 採用已有的"圖"
pG = new MatrixDG(vexs, edges);
pG.print(); // 列印圖
}
}
五、 鄰接表有向圖之Java詳解
1) 概要
本文通過Java實現鄰接表有向圖。
目錄
1. 鄰接表有向圖的介紹
2. 鄰接表有向圖的程式碼說明
3. 鄰接表有向圖的完整原始碼
2) 鄰接表有向圖的介紹
鄰接表有向圖是指通過鄰接表表示的有向圖。
上面的圖G2包含了"A,B,C,D,E,F,G"共7個頂點,而且包含了"<A,B>,<B,C>,<B,E>,<B,F>,<C,E>,<D,C>,<E,B>,<E,D>,<F,G>"共9條邊。
上圖右邊的矩陣是G2在記憶體中的鄰接表示意圖。每一個頂點都包含一條連結串列,該連結串列記錄了"該頂點所對應的出邊的另一個頂點的序號"。例如,第1個頂點(頂點B)包含的連結串列所包含的節點的資料分別是"2,4,5";而這"2,4,5"分別對應"C,E,F"的序號,"C,E,F"都屬於B的出邊的另一個頂點。
3) 鄰接表有向圖的程式碼說明
1. 基本定義
public class ListDG {
// 鄰接表中表對應的連結串列的頂點
private class ENode {
int ivex; // 該邊所指向的頂點的位置
ENode nextEdge; // 指向下一條弧的指標
}
// 鄰接表中表的頂點
private class VNode {
char data; // 頂點資訊
ENode firstEdge; // 指向第一條依附該頂點的弧
};
private VNode[] mVexs; // 頂點陣列
...
}
(01) ListDG是鄰接表對應的結構體。 mVexs則是儲存頂點資訊的一維陣列。
(02) VNode是鄰接表頂點對應的結構體。 data是頂點所包含的資料,而firstEdge是該頂點所包含連結串列的表頭指標。
(03) ENode是鄰接表頂點所包含的連結串列的節點對應的結構體。 ivex是該節點所對應的頂點在vexs中的索引,而nextEdge是指向下一個節點的。
2. 建立矩陣
這裡介紹提供了兩個建立矩陣的方法。一個是用已知資料,另一個則需要使用者手動輸入資料。
2.1 建立圖(用已提供的矩陣)
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public ListDG(char[] vexs, char[][] edges){
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}
// 初始化"邊"
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
char c1 = edges[i][0];
char c2 = edges[i][1];
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
}
}
該函式的作用是建立一個鄰接表有向圖。實際上,該方法建立的有向圖,就是上面的圖G2。該函式的呼叫方法如下:
char[] vexs = {'A', 'B', 'C', 'D', 'E','F', 'G'};
char[][] edges = new char[][]{
{'A', 'B'},
{'B', 'C'},
{'B', 'E'},
{'B', 'F'},
{'C', 'E'},
{'D', 'C'},
{'E', 'B'},
{'E', 'D'},
{'F', 'G'}};
ListDG pG;
pG = new ListDG(vexs, edges);
2.2 建立圖(自己輸入)
/*
* 建立圖(自己輸入資料)
*/
public ListDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}
// 初始化"邊"
//mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge, node1);
}
}
4) 鄰接表有向圖的完整原始碼
/**
*Java: 鄰接矩陣圖
*
*@author skywang
*@date 2014/04/19
*/
import java.io.IOException;
import java.util.Scanner;
public class ListDG {
// 鄰接表中表對應的連結串列的頂點
private class ENode {
int ivex; // 該邊所指向的頂點的位置
ENode nextEdge; // 指向下一條弧的指標
}
// 鄰接表中表的頂點
private class VNode {
char data; // 頂點資訊
ENode firstEdge; // 指向第一條依附該頂點的弧
};
private VNode[] mVexs; // 頂點陣列
/*
* 建立圖(自己輸入資料)
*/
public ListDG() {
// 輸入"頂點數"和"邊數"
System.out.printf("input vertex number: ");
int vlen = readInt();
System.out.printf("input edge number: ");
int elen = readInt();
if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
System.out.printf("input error: invalid parameters!\n");
return ;
}
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("vertex(%d): ", i);
mVexs[i] = new VNode();
mVexs[i].data = readChar();
mVexs[i].firstEdge = null;
}
// 初始化"邊"
//mMatrix = new int[vlen][vlen];
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
System.out.printf("edge(%d):", i);
char c1 = readChar();
char c2 = readChar();
int p1 = getPosition(c1);
int p2 = getPosition(c2);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge,node1);
}
}
/*
* 建立圖(用已提供的矩陣)
*
* 引數說明:
* vexs -- 頂點陣列
* edges -- 邊陣列
*/
public ListDG(char[] vexs, char[][] edges) {
// 初始化"頂點數"和"邊數"
int vlen = vexs.length;
int elen = edges.length;
// 初始化"頂點"
mVexs = new VNode[vlen];
for (int i = 0; i <mVexs.length; i++) {
mVexs[i] = new VNode();
mVexs[i].data = vexs[i];
mVexs[i].firstEdge = null;
}
// 初始化"邊"
for (int i = 0; i < elen; i++) {
// 讀取邊的起始頂點和結束頂點
char c1 = edges[i][0];
char c2 = edges[i][1];
// 讀取邊的起始頂點和結束頂點
int p1 = getPosition(edges[i][0]);
int p2 = getPosition(edges[i][1]);
// 初始化node1
ENode node1 = new ENode();
node1.ivex = p2;
// 將node1連結到"p1所在連結串列的末尾"
if(mVexs[p1].firstEdge == null)
mVexs[p1].firstEdge = node1;
else
linkLast(mVexs[p1].firstEdge,node1);
}
}
/*
* 將node節點連結到list的最後
*/
private void linkLast(ENode list, ENode node) {
ENode p = list;
while(p.nextEdge!=null)
p = p.nextEdge;
p.nextEdge = node;
}
/*
* 返回ch位置
*/
private int getPosition(char ch) {
for(int i=0; i<mVexs.length; i++)
if(mVexs[i].data==ch)
return i;
return -1;
}
/*
* 讀取一個輸入字元
*/
private char readChar() {
char ch='0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} while(!((ch>='a'&&ch<='z') ||(ch>='A'&&ch<='Z')));
return ch;
}
/*
* 讀取一個輸入字元
*/
private int readInt() {
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
/*
* 列印矩陣佇列圖
*/
public void print() {
System.out.printf("List Graph:\n");
for (int i = 0; i < mVexs.length; i++) {
System.out.printf("%d(%c): ", i, mVexs[i].data);
ENode node = mVexs[i].firstEdge;
while (node != null) {
System.out.printf("%d(%c)", node.ivex, mVexs[node.ivex].data);
node = node.nextEdge;
}
System.out.printf("\n");
}
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char[][] edges = new char[][]{
{'A', 'B'},
{'B', 'C'},
{'B', 'E'},
{'B', 'F'},
{'C', 'E'},
{'D', 'C'},
{'E', 'B'},
{'E', 'D'},
{'F', 'G'}};
ListDG pG;
// 自定義"圖"(輸入矩陣佇列)
//pG = new ListDG();
// 採用已有的"圖"
pG = new ListDG(vexs, edges);
pG.print(); // 列印圖
}
}
六、 圖的遍歷之深度優先搜尋和廣度優先搜尋
1) 概要
本章會先對圖的深度優先搜尋和廣度優先搜尋進行介紹
目錄
1. 深度優先搜尋的圖文介紹
1.1 深度優先搜尋介紹
1.2 深度優先搜尋圖解
2. 廣度優先搜尋的圖文介紹
2.1 廣度優先搜尋介紹
2.2 廣度優先搜尋圖解
3. 搜尋演算法的原始碼
2) 深度優先搜尋的圖文介紹
1. 深度優先搜尋介紹
圖的深度優先搜尋(Depth First Search),和樹的先序遍歷比較類似。
它的思想:假設初始狀態是圖中所有頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點,然後依次從它的各個未被訪問的鄰接點出發深度優先搜尋遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。 若此時尚有其他頂點未被訪問到,則另選一個未被訪問的頂點作起始點,重複上述過程,直至圖中所有頂點都被訪問到為止。
顯然,深度優先搜尋是一個遞迴的過程。
2. 深度優先搜尋圖解
2.1 無向圖的深度優先搜尋
下面以"無向圖"為例,來對深度優先搜尋進行演示。
對上面的圖G1進行深度優先遍歷,從頂點A開始。
第1步:訪問A。
第2步:訪問(A的鄰接點)C。
在第1步訪問A之後,接下來應該訪問的是A的鄰接點,即"C,D,F"中的一個。但在本文的實現中,頂點ABCDEFG是按照順序儲存,C在"D和F"的前面,因此,先訪問C。
第3步:訪問(C的鄰接點)B。
在第2步訪問C之後,接下來應該訪問C的鄰接點,即"B和D"中一個(A已經被訪問過,就不算在內)。而由於B在D之前,先訪問B。
第4步:訪問(C的鄰接點)D。
在第3步訪問了C的鄰接點B之後,B沒有未被訪問的鄰接點;因此,返回到訪問C的另一個鄰接點D。
第5步:訪問(A的鄰接點)F。
前面已經訪問了A,並且訪問完了"A的鄰接點B的所有鄰接點(包括遞迴的鄰接點在內)";因此,此時返回到訪問A的另一個鄰接點F。
第6步:訪問(F的鄰接點)G。
第7步:訪問(G的鄰接點)E。
因此訪問順序是:A -> C -> B -> D -> F-> G -> E
2.2 有向圖的深度優先搜尋
下面以"有向圖"為例,來對深度優先搜尋進行演示。
對上面的圖G2進行深度優先遍歷,從頂點A開始。
第1步:訪問A。
第2步:訪問B。
在訪問了A之後,接下來應該訪問的是A的出邊的另一個頂點,即頂點B。
第3步:訪問C。
在訪問了B之後,接下來應該訪問的是B的出邊的另一個頂點,即頂點C,E,F。在本文實現的圖中,頂點ABCDEFG按照順序儲存,因此先訪問C。
第4步:訪問E。
接下來訪問C的出邊的另一個頂點,即頂點E。
第5步:訪問D。
接下來訪問E的出邊的另一個頂點,即頂點B,D。頂點B已經被訪問過,因此訪問頂點D。
第6步:訪問F。
接下應該回溯"訪問A的出邊的另一個頂點F"。
第7步:訪問G。
因此訪問順序是:A -> B -> C -> E -> D-> F -> G
3) 廣度優先搜尋的圖文介紹
1. 廣度優先搜尋介紹
廣度優先搜尋演算法(Breadth First Search),又稱為"寬度優先搜尋"或"橫向優先搜尋",簡稱BFS。
它的思想是:從圖中某頂點v出發,在訪問了v之後依次訪問v的各個未曾訪問過的鄰接點,然後分別從這些鄰接點出發依次訪問它們的鄰接點,並使得“先被訪問的頂點的鄰接點先於後被訪問的頂點的鄰接點被訪問,直至圖中所有已被訪問的頂點的鄰接點都被訪問到。如果此時圖中尚有頂點未被訪問,則需要另選一個未曾被訪問過的頂點作為新的起始點,重複上述過程,直至圖中所有頂點都被訪問到為止。
換句話說,廣度優先搜尋遍歷圖的過程是以v為起點,由近至遠,依次訪問和v有路徑相通且路徑長度為1,2...的頂點。
2. 廣度優先搜尋圖解
2.1 無向圖的廣度優先搜尋
下面以"無向圖"為例,來對廣度優先搜尋進行演示。還是以上面的圖G1為例進行說明。
第1步:訪問A。
第2步:依次訪問C,D,F。
在訪問了A之後,接下來訪問A的鄰接點。前面已經說過,在本文實現中,頂點ABCDEFG按照順序儲存的,C在"D和F"的前面,因此,先訪問C。再訪問完C之後,再依次訪問D,F。
第3步:依次訪問B,G。
在第2步訪問完C,D,F之後,再依次訪問它們的鄰接點。首先訪問C的鄰接點B,再訪問F的鄰接點G。
第4步:訪問E。
在第3步訪問完B,G之後,再依次訪問它們的鄰接點。只有G有鄰接點E,因此訪問G的鄰接點E。
因此訪問順序是:A -> C -> D -> F -> B-> G -> E
2.2 有向圖的廣度優先搜尋
下面以"有向圖"為例,來對廣度優先搜尋進行演示。還是以上面的圖G2為例進行說明。
第1步:訪問A。
第2步:訪問B。
第3步:依次訪問C,E,F。
在