資料結構:圖——圖的遍歷、最小生成樹、最短路徑演算法
前言
在這裡,如果大家對圖或者資料結構還不太熟悉,想找一個動態的生成過程來參考,這是一個不錯的網站.
知識框架
圖的定義
線上性結構中,資料元素之間滿足唯一的線性關係,每個資料元素(除第一個和最後一個外)只有一個直接前趨和一個直接後繼;
在樹形結構中,資料元素之間有著明顯的層次關係,並且每個資料元素只與上一層中的一個元素(雙親節點)及下一層的多個元素(孩子節點)相關;
而在圖形結構中,節點之間的關係是任意的,圖中任意兩個資料元素之間都有可能相關。
圖G由兩個集合V(頂點Vertex)和E(邊Edge)組成,定義為G=(V,E)
圖的儲存結構
1.鄰接矩陣
維持一個二維陣列,arr[i][j]表示i到j的邊,如果兩頂點之間存在邊,則為1,否則為0;
維持一個一維陣列,儲存頂點資訊,比如頂點的名字;
下圖為一般的有向圖:
注意:如果我們要看vi節點鄰接的點,則只需要遍歷arr[i]即可;
下圖為帶有權重的圖的鄰接矩陣表示法:
缺點:鄰接矩陣表示法對於稀疏圖來說不合理,因為太浪費空間;
2.鄰接表
如果圖示一般的圖,則如下圖:
如果是網,即邊帶有權值,則如下圖:
圖的遍歷
DFS(Depth-first-search) 深度優先遍歷
形象的說,就像搜尋資料夾,從第一個開始資料夾開始搜尋到底,然後再搜尋第二個到底,然後第三個….一直到最後.
官方的說:
a) 假設初始狀態是圖中所有頂點都未曾訪問過,則可從圖G中任意一頂點v為初始出發點,首先訪問出發點v,並將其標記為已訪問過。
b) 然後依次從v出發搜尋v的每個鄰接點w,若w未曾訪問過,則以w作為新的出發點出發,繼續進行深度優先遍歷,直到圖中所有和v有路徑相通的頂點都被訪問到。
c) 若此時圖中仍有頂點未被訪問,則另選一個未曾訪問的頂點作為起點,重複上述步驟,直到圖中所有頂點都被訪問到為止。
圖示如下:
ds59
注:紅色數字代表遍歷的先後順序,所以圖(e)無向圖的深度優先遍歷的頂點訪問序列為:V0,V1,V2,V5,V4,V6,V3,V7,V8
如果採用鄰接矩陣儲存,則時間複雜度為O(n2);當採用鄰接表時時間複雜度為O(n+e)。
BFS(Breadth-first-search) 廣度優先搜尋遍歷
形象的,類似於樹的層次遍歷,一層層的找.
書面上:
廣度優先搜尋遍歷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)。
圖的Java實現以及遍歷
public classGraph {
private int vertexSize;//頂點數量
private int [] vertexs;//頂點陣列
private int[][] matrix; //鄰接矩陣
private static final int MAX_WEIGHT = 1000;
private boolean [] isVisited;
public Graph(int vertextSize){
this.vertexSize = vertextSize;
matrix = new int[vertextSize][vertextSize];
vertexs = new int[vertextSize];
for(int i = 0;i<vertextSize;i++){
vertexs[i] = i;
}
isVisited = new boolean[vertextSize];
}
/**
* 獲取某個頂點的出度
* @return
*/
public int getOutDegree(int index){
int degree = 0;
for(int j = 0;j<matrix[index].length;j++){
int weight = matrix[index][j];
if(weight!=0&&weight!=MAX_WEIGHT){
degree++;
}
}
return degree;
}
/**
* 入度
* @return
*/
/**
* 獲取某個頂點的第一個鄰接點
* 根據index(代表第幾行) 往右遍歷j++(代表第幾列),則第一個數為第一個鄰接點.
*/
public int getFirstNeighbor(int index){
for(int j = 0;j<vertexSize;j++){
if(matrix[index][j]>0&&matrix[index][j]<MAX_WEIGHT){
return j;
}
}
return -1;
}
/**
* 在頂點v處,根據前一個鄰接點的下標index來取得下一個鄰接點
* @param v1表示要找的頂點
* @param v2 表示該頂點相對於哪個鄰接點去獲取下一個鄰接點(index表示從參照點開始往後遍歷)
*/
public int getNextNeighbor(int v,int index){
for(int j = index+1;j<vertexSize;j++){
if(matrix[v][j]>0&&matrix[v][j]<MAX_WEIGHT){
return j;
}
}
return -1;
}
/**
* 圖的深度優先遍歷演算法
*/
private void depthFirstSearch(int i){
isVisited[i] = true;
int w = getFirstNeighbor(i);//
while(w!=-1){
if(!isVisited[w]){
//需要遍歷該頂點
System.out.println("訪問到了:"+w+"頂點");
depthFirstSearch(w);
}
w = getNextNeighbor(i, w);//第一個相對於w的鄰接點
}
}
/**
* 對外公開的深度優先遍歷
*/
public void depthFirstSearch(){
isVisited = new boolean[vertexSize];
for(int i = 0;i<vertexSize;i++){
if(!isVisited[i]){
System.out.println("訪問到了:"+i+"頂點");
depthFirstSearch(i);
}
}
isVisited = new boolean[vertexSize];
}
public void broadFirstSearch(){
isVisited = new boolean[vertexSize];
for(int i =0;i<vertexSize;i++){
if(!isVisited[i]){
broadFirstSearch(i);
}
}
}
/**
* 實現廣度優先遍歷
* @param i
*/
private void broadFirstSearch(int i) {
int u,w;
LinkedList<Integer> queue = new LinkedList<Integer>();
System.out.println("訪問到:"+i+"頂點");
isVisited[i] = true;
queue.add(i);//第一次把v0加到佇列
while(!queue.isEmpty()){
u = (Integer)(queue.removeFirst()).intValue();
w = getFirstNeighbor(u);
while(w!=-1){
if(!isVisited[w]){
System.out.println("訪問到了:"+w+"頂點");
isVisited[w] = true;
queue.add(w);
}
w = getNextNeighbor(u, w);
}
}
}
/*********************************/
public static void main(String [] args){
Graph graph = new Graph(9);
int [] a1 = new int[]{0,10,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,11,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT};
int [] a2 = new int[]{10,0,18,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,16,MAX_WEIGHT,12};
int [] a3 = new int[]{MAX_WEIGHT,MAX_WEIGHT,0,22,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,8};
int [] a4 = new int[]{MAX_WEIGHT,MAX_WEIGHT,22,0,20,MAX_WEIGHT,MAX_WEIGHT,16,21};
int [] a5 = new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,20,0,26,MAX_WEIGHT,7,MAX_WEIGHT};
int [] a6 = new int[]{11,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,26,0,17,MAX_WEIGHT,MAX_WEIGHT};
int [] a7 = new int[]{MAX_WEIGHT,16,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,17,0,19,MAX_WEIGHT};
int [] a8 = new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,16,7,MAX_WEIGHT,19,0,MAX_WEIGHT};
int [] a9 = new int[]{MAX_WEIGHT,12,8,21,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0};
graph.matrix[0] = a1;
graph.matrix[1] = a2;
graph.matrix[2] = a3;
graph.matrix[3] = a4;
graph.matrix[4] = a5;
graph.matrix[5] = a6;
graph.matrix[6] = a7;
graph.matrix[7] = a8;
graph.matrix[8] = a9;
// int degree = graph.getOutDegree(4);
// System.out.println("vo的出度:"+degree);
// System.out.println("權值:"+graph.getWeight(2,3));
//graph.depthFirstSearch();
graph.broadFirstSearch();
}
}
- 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
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 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
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
最小生成樹
1.相關概念
- 生成樹概念:生成樹是將圖中所有頂點以最少的邊連通的子圖
- 最小生成樹概念: 權值和最小的生成樹就是最小生成樹。
2.演算法prim java實現
就是用一個最小代價的陣列,不斷記錄陣列中對應下標的頂點所連線的最小路徑
這樣就可以求得所有頂點連線後的路徑之和的最小值
/**
* []
*
* prim 普里姆演算法 最小生成樹
* 就是用一個最小代價的陣列,不斷記錄陣列中對應下標的頂點所連線的最小路徑
* 一個頂點 先找到四周頂點的權值 分別比較,找到最小的進行連線,然後 再找相鄰的下一個點四周頂點的權值
*/
public void prim(){
int [] lowcost = new int[vertexSize];//最小代價頂點權值的陣列,為0表示已經獲取最小權值
int [] adjvex = new int[vertexSize];//放頂點權值
int min,minId,sum = 0;//min最小,minID最小的下標,sum權值總和
for(int i = 1;i<vertexSize;i++){
lowcost[i] = matrix[0][i];
}
for(int i = 1;i<vertexSize;i++){
min = MAX_WEIGHT;
minId = 0;
for(int j = 1;j<vertexSize;j++){
if(lowcost[j]<min&&lowcost[j]>0){
min = lowcost[j];
minId = j;
}
}
System.out.println("頂點:"+adjvex[minId]+"權值:"+min);
sum+=min;
lowcost[minId] = 0;
for(int j = 1;j<vertexSize;j++){
if(lowcost[j]!=0&&matrix[minId][j]<lowcost[j]){
lowcost[j] = matrix[minId][j];
adjvex[j] = minId;
}
}
}
System.out.println("最小生成樹權值和:"+sum);
}
- 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
- 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
3.Kruskal演算法
演算法的思想是:
1.按照權值 從小到大排列 。
2.從上往下依次頂點相連
3.一旦構成迴環(三點連線),就扔掉(第三條邊)
4.最後構成最小生成樹
/**
* 克魯茲卡爾 演算法
* 按照權值 從小到大排列
* 從上往下依次頂點相連
* 一旦構成迴環(三點連線),就扔掉(第三條邊)
* 最後構成最小生成樹
*/
public classGraphKruskal {
private Edge[] edges;
private int edgeSize;
public GraphKruskal(int edgeSize) {
this.edgeSize = edgeSize;
edges = new Edge[edgeSize];
}
public void miniSpanTreeKruskal(){
int m,n,sum=0;
int[] parent = new int[edgeSize];//神奇的陣列,下標為起點,值為終點
for(int i = 0 ;i<edgeSize;i++){
parent[i] = 0;
}
for(int i = 0;i<edgeSize;i++){
n = find(parent,edges[i].begin);
m = find(parent,edges[i].end);
if(n!=m){
parent[n] = m;
System.out.println("起始頂點:"+edges[i].begin+"---結束頂點:"+edges[i].end+"~權值:"+edges[i].weight);
sum+=edges[i].weight;
}else{
System.out.println("第"+i+"條邊迴環了");
}
}
System.out.println("sum:"+sum);
}
/*將神奇陣列進行查詢獲取非迴環的值
*
*/
public int find(int[] parent,int f){
while(parent[f]>0){
System.out.println("找到起點"+f);
f = parent[f];
System.out.println("找到終點:"+f);
}
return f;
}
public void createEdgeArray(){
Edge edge0 = new Edge(4,7,7);
Edge edge1 = new Edge(2,8,8);
Edge edge2 = new Edge(0,1,10);
Edge edge3 = new Edge(0,5,11);
Edge edge4 = new Edge(1,8,12);
Edge edge5 = new Edge(3,7,16);
Edge edge6 = new Edge(1,6,16);
Edge edge7 = new Edge(5,6,17);
Edge edge8 = new Edge(1,2,18);
Edge edge9 = new Edge(6,7,19);
Edge edge10 = new Edge(3,4,20);
Edge edge11 = new Edge(3,8,21);
Edge edge12 = new Edge(2,3,22);
Edge edge13 = new Edge(3,6,24);
Edge edge14 = new Edge(4,5,26);
edges[0] = edge0;
edges[1] = edge1;
edges[2] = edge2;
edges[3] = edge3;
edges[4] = edge4;
edges[5] = edge5;
edges[6] = edge6;
edges[7] = edge7;
edges[8] = edge8;
edges[9] = edge9;
edges[10] = edge10;
edges[11] = edge11;
edges[12] = edge12;
edges[13] = edge13;
edges[14] = edge14;
}
class Edge{
private int begin;
private int end;
private int weight;
public Edge(int begin, int end, int weight) {
super();
this.begin = begin;
this.end = end;
this.weight = weight;
}
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
public static void main(String[]args){
GraphKruskal graphKruskal = new GraphKruskal(15);
graphKruskal.createEdgeArray();
graphKruskal.miniSpanTreeKruskal();
}
}
- 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
- 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
最短路徑
求最短路徑也就是求最短路徑長度。下面是一個帶權值的有向圖,表格中分別列出了頂點V1其它各頂點的最短路徑長度。
從圖中可以看出,頂點V1到V4的路徑有3條(V1,V2,V4),(V1,V4),(V1,V3,V2,V4),其路徑長度分別為15,20和10,因此,V1到V4的最短路徑為(V1,V3,V2,V4)。
dijkstra演算法
public classDnjavaDijstra {
private final static int MAXVEX = 9;
private final static int MAXWEIGHT = 65535;
private int shortTablePath[] = new int[MAXVEX];// 記錄的是v0到某頂點的最短路徑和
/**
* 獲取一個圖的最短路徑
*/
public void shortestPathDijstra(Graph graph) {
int min;
int k = 0;// 記錄下標
boolean isgetPath[] = new boolean[MAXVEX];
for (int v = 0; v < graph.getVertexSize(); v++) {
shortTablePath[v] = graph.getMatrix()[0][v];// 獲取v0這一行的權值陣列
}
shortTablePath[0] = 0;
isgetPath[0] = true;
for (int v = 1; v < graph.getVertexSize(); v++) {
min = MAXWEIGHT;
for (int w = 0; w < graph.getVertexSize(); w++) {
if (!isgetPath[w] && shortTablePath[w] < min) {
k = w;
min = shortTablePath[w];
}
}
isgetPath[k] = true;
for (int j = 0; j < graph.getVertexSize(); j++) {
if(!isgetPath[j]&&(min+graph.getMatrix()[k][j]<shortTablePath[j])){
shortTablePath[j] = min + graph.getMatrix()[k][j];
}
}
}
for(int i = 0;i<shortTablePath.length;i++){
System.out.println("V0到V"+i+"的最短路徑為:"+shortTablePath[i]+"\n");
}
}
public static void main(String[] args){
Graph graph = new Graph(MAXVEX);
graph.createGraph();
DnjavaDijstra dijstra = new DnjavaDijstra();
dijstra.shortestPathDijstra(graph);
}
}
- 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
- 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
Floyd演算法
Floyd演算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的演算法,與Dijkstra演算法類似。