1,摘要:java
本系列文章主要學習如何使用JAVA語言以鄰接表的方式實現了數據結構---圖(Graph),這是第一篇文章,學習如何用JAVA來表示圖的頂點。從數據的表示方法來講,有二種表示圖的方式:一種是鄰接矩陣,實際上是一個二維數組;一種是鄰接表,實際上是一個頂點表,每一個頂點又擁有一個邊列表。下圖是圖的鄰接表表示。算法
從圖中能夠看出,圖的實現須要可以表示頂點表,可以表示邊表。鄰接表指是的哪部分呢?每一個頂點都有一個鄰接表,一個指定頂點的鄰接表中,起始頂點表示邊的起點,其餘頂點表示邊的終點。這樣,就能夠用鄰接表來實現邊的表示了。如頂點V0的鄰接表以下:數組
與V0關聯的邊有三條,由於V0的鄰接表中有三個頂點(不考慮V0)。數據結構
2,具體分析ide
先來分析邊表:學習
在圖中如何來表示一條邊?很簡單,就是:起始頂點指向結束頂點、就是頂點對<startVertex, endVertex>。在這裏,爲了考慮邊帶有權值的狀況,單獨設計一個類Edge.java,做爲Vertex.java的內部類,Edge.java以下:this
1 protected class Edge implements java.io.Serializable { 2 private VertexInterface<T> vertex;// 終點 3 private double weight;//權值
Edge類中只有兩個屬性,vertex 用來表示頂點,該頂點是邊的終點。weight 表示邊的權值。若不考慮帶權的狀況,就不須要weight屬性,那麼能夠直接定義一個頂點列表 來存放 終點 就能夠表示邊了。這是由於:這些屬性是定義在Vertex.java中,而Vertex自己就表示頂點,若是在Vertex內部定義一個List存放終點,那麼該List再加上Vertex所表示的頂點自己,就能夠表示與起點鄰接的各個點了(稱之爲這個 起點的鄰接表)。這樣的邊的特色是:邊的全部的起始點都相同。spa
可是爲了表示帶權的邊,所以,新增長weight屬性,並用類Edge來封裝,這樣不論是帶權的邊仍是不帶權的邊均可以用同一個Edge類來表示。不帶權的邊將weight賦值爲0便可。設計
再分析頂點表:指針
定義接口VertexInterface<T>表示頂點的接口,全部的頂點都須要實現這個接口,該接口中定義了頂點的基本操做,如:判斷頂點是否有鄰接點,將頂點與另外一個頂點鏈接起來...。其次,頂點表中的每一個頂點有兩個域,一個是標識域:V0,V1,V2,V3 。一個是指針域,指針域指向一個"單鏈表"。綜上,設計一個類Vertex.java 用來表示頂點,其數據域以下:
class Vertex<T> implements VertexInterface<T>, java.io.Serializable { private T label;//標識標點,能夠用不一樣類型來標識頂點如String,Integer.... private List<Edge> edgeList;//到該頂點鄰接點的邊,實際以java.util.LinkedList存儲 private boolean visited;//標識頂點是否已訪問 private VertexInterface<T> previousVertex;//該頂點的前驅頂點 private double cost;//頂點的權值,與邊的權值要區別開來
如今一一解釋Vertex類中定義的各個屬性:
label : 用來標識頂點,如圖中的 V0,V1,V2,V3,在實際代碼中,V0...V3 以字符串的形式表示,就能夠用來標識不一樣的頂點了。所以,須要在Vertex類中添加得到頂點標識的方法---getLabel()
1 public T getLabel() { 2 return label; 3 }
edgeList : 存放與該頂點關聯的邊。從上面Edge.java中能夠看到,Edge的實質是「頂點」,由於,Edge類除去wight屬性,就只剩表示頂點的vertex屬性了。藉助edgeList,當給定一個頂點時,就能夠訪問該頂點的全部鄰接點。所以,Vertex.java中就須要實現根據edgeList中存放的邊來遍歷 某條邊的終點(也即相應頂點的各個鄰接點) 的迭代器了。
1 public Iterator<VertexInterface<T>> getNeighborInterator() { 2 return new NeighborIterator(); 3 }
迭代器的實現以下:
1 /**Task: 遍歷該頂點鄰接點的迭代器--爲 getNeighborInterator()方法 提供迭代器 2 * 因爲頂點的鄰接點以邊的形式存儲在java.util.List中,所以藉助List的迭代器來實現 3 * 因爲頂點的鄰接點由Edge類封裝起來了--見Edge.java的定義的第一個屬性 4 * 所以,首先得到遍歷Edge對象的迭代器,再根據得到的Edge對象解析出鄰接點對象 5 */ 6 private class NeighborIterator implements Iterator<VertexInterface<T>>{ 7 8 Iterator<Edge> edgesIterator; 9 private NeighborIterator() { 10 edgesIterator = edgeList.iterator();//得到遍歷edgesList 的迭代器 11 } 12 @Override 13 public boolean hasNext() { 14 return edgesIterator.hasNext(); 15 } 16 17 @Override 18 public VertexInterface<T> next() { 19 VertexInterface<T> nextNeighbor = null; 20 if(edgesIterator.hasNext()){ 21 Edge edgeToNextNeighbor = edgesIterator.next();//LinkedList中存儲的是Edge 22 nextNeighbor = edgeToNextNeighbor.getEndVertex();//從Edge對象中取出頂點 23 } 24 else 25 throw new NoSuchElementException(); 26 return nextNeighbor; 27 } 28 29 @Override 30 public void remove() { 31 throw new UnsupportedOperationException(); 32 } 33 }
visited : 之因此給每一個頂點設置一個用來標記它是否被訪問的屬性,是由於:實現一個數據結構,是要用它去完成某些功能的,如遍歷、查找…… 而在圖的遍歷過程當中,就須要標記某個頂點是否被訪問了,所以:設置該屬性以便實現這些功能。那麼,也就須要定義獲取頂點是否被訪問的isVisited()方法了。
1 public boolean isVisited() { 2 return visited; 3 }
previousVertex 屬性 ,在求圖中某兩個頂點之間的最短路徑時,在從起始頂點遍歷過程當中,須要記錄下遍歷到某個頂點時的前驅頂點, previousVertex 屬性就派上用場了。所以,須要有判斷和獲取頂點的前驅頂點的方法:
1 public boolean hasPredecessor() {//判斷頂點是否有前驅頂點 2 return this.previousVertex != null; 3 }
1 public VertexInterface<T> getPredecessor() {//得到前驅頂點 2 return this.previousVertex; 3 }
cost 屬性:用來表示頂點的權值。注意,頂點的權值與邊的權值是不一樣的。好比求無權圖(默認是邊不帶權值)的最短路徑時,如何求出頂點A到頂點B的最短的路徑?由定義,該最短路徑其實就是A走到B經歷的最少邊數目。所以,就能夠用 cost 屬性來記錄A到B之間的距離是多少了。好比說,A 先走到 C 再走到B;初始時,A的 cost = 0,因爲 A 是 C 的前驅,A到B須要經歷C,C 的 cost 就是 c.previousVertex.cost + 1,直至 B,就能夠求出 A 到 B 的最短路徑了。詳細算法及實現將會在第二篇博客中給出。
所以,針對 cost 屬性,Vertex.java須要實現的方法以下:
1 public void setCost(double newCost) { 2 cost = newCost; 3 } 4 public double getCost() { 5 return cost; 6 }
3,總結:
從上能夠看出,設計一個數據結構時,該數據結構須要包含哪些屬性不是隨意的,而是先肯定該數據結構須要完成哪些功能(如,圖的DFS、BFS、拓撲排序、最短路徑),這些功能的實現須要藉助哪些屬性(如,求最短路徑須要記錄每一個頂點的前驅頂點,就須要 previousVertex)。而後,去定義這些屬性以及關於該屬性的基本操做。設計一個合適的數據結構,當藉助該數據結構來實現算法時,能夠有效地下降算法的實現難度和複雜度!
Vertex.java的完整代碼以下:
1 package graph; 2 3 import java.util.Iterator; 4 import java.util.LinkedList; 5 import java.util.List; 6 import java.util.NoSuchElementException; 7 8 class Vertex<T> implements VertexInterface<T>, java.io.Serializable { 9 10 private T label;//標識標點,能夠用不一樣類型來標識頂點如String,Integer.... 11 private List<Edge> edgeList;//到該頂點鄰接點的邊,實際以java.util.LinkedList存儲 12 private boolean visited;//標識頂點是否已訪問 13 private VertexInterface<T> previousVertex;//該頂點的前驅頂點 14 private double cost;//頂點的權值,與邊的權值要區別開來 15 16 public Vertex(T vertexLabel){ 17 label = vertexLabel; 18 edgeList = new LinkedList<Edge>();//是Vertex的屬性,說明每一個頂點都有一個edgeList用來存儲全部與該頂點關係的邊 19 visited = false; 20 previousVertex = null; 21 cost = 0; 22 } 23 24 /** 25 *Task: 這裏用了一個單獨的類來表示邊,主要是考慮到帶權值的邊 26 *能夠看出,Edge類封裝了一個頂點和一個double類型變量 27 *若不須要考慮權值,能夠不須要單首創建一個Edge類來表示邊,只須要一個保存頂點的列表便可 28 * @author hapjin 29 */ 30 protected class Edge implements java.io.Serializable { 31 private VertexInterface<T> vertex;// 終點 32 private double weight;//權值 33 34 //Vertex 類自己就表明頂點對象,所以在這裏只需提供 endVertex,就能夠表示一條邊了 35 protected Edge(VertexInterface<T> endVertex, double edgeWeight){ 36 vertex = endVertex; 37 weight = edgeWeight; 38 } 39 40 protected VertexInterface<T> getEndVertex(){ 41 return vertex; 42 } 43 protected double getWeight(){ 44 return weight; 45 } 46 } 47 48 /**Task: 遍歷該頂點鄰接點的迭代器--爲 getNeighborInterator()方法 提供迭代器 49 * 因爲頂點的鄰接點以邊的形式存儲在java.util.List中,所以藉助List的迭代器來實現 50 * 因爲頂點的鄰接點由Edge類封裝起來了--見Edge.java的定義的第一個屬性 51 * 所以,首先得到遍歷Edge對象的迭代器,再根據得到的Edge對象解析出鄰接點對象 52 */ 53 private class NeighborIterator implements Iterator<VertexInterface<T>>{ 54 55 Iterator<Edge> edgesIterator; 56 private NeighborIterator() { 57 edgesIterator = edgeList.iterator();//得到遍歷edgesList 的迭代器 58 } 59 @Override 60 public boolean hasNext() { 61 return edgesIterator.hasNext(); 62 } 63 64 @Override 65 public VertexInterface<T> next() { 66 VertexInterface<T> nextNeighbor = null; 67 if(edgesIterator.hasNext()){ 68 Edge edgeToNextNeighbor = edgesIterator.next();//LinkedList中存儲的是Edge 69 nextNeighbor = edgeToNextNeighbor.getEndVertex();//從Edge對象中取出頂點 70 } 71 else 72 throw new NoSuchElementException(); 73 return nextNeighbor; 74 } 75 76 @Override 77 public void remove() { 78 throw new UnsupportedOperationException(); 79 } 80 } 81 82 /**Task: 生成一個遍歷該頂點全部鄰接邊的權值的迭代器 83 * 權值是Edge類的屬性,所以先得到一個遍歷Edge對象的迭代器,取得Edge對象,再得到權值 84 * @author hapjin 85 * 86 * @param <Double> 權值的類型 87 */ 88 private class WeightIterator implements Iterator{//這裏不知道爲何,用泛型報編譯錯誤??? 89 90 private Iterator<Edge> edgesIterator; 91 private WeightIterator(){ 92 edgesIterator = edgeList.iterator(); 93 } 94 @Override 95 public boolean hasNext() { 96 return edgesIterator.hasNext(); 97 } 98 @Override 99 public Object next() { 100 Double result; 101 if(edgesIterator.hasNext()){ 102 Edge edge = edgesIterator.next(); 103 result = edge.getWeight(); 104 } 105 else throw new NoSuchElementException(); 106 return (Object)result;//從迭代器中取得結果時,須要強制轉換成Double 107 } 108 @Override 109 public void remove() { 110 throw new UnsupportedOperationException(); 111 } 112 113 } 114 115 @Override 116 public T getLabel() { 117 return label; 118 } 119 120 @Override 121 public void visit() { 122 this.visited = true; 123 } 124 125 @Override 126 public void unVisit() { 127 this.visited = false; 128 } 129 130 @Override 131 public boolean isVisited() { 132 return visited; 133 } 134 135 @Override 136 public boolean connect(VertexInterface<T> endVertex, double edgeWeight) { 137 // 將"邊"(邊的實質是頂點)插入頂點的鄰接表 138 boolean result = false; 139 if(!this.equals(endVertex)){//頂點互不相同 140 Iterator<VertexInterface<T>> neighbors = this.getNeighborInterator(); 141 boolean duplicateEdge = false; 142 while(!duplicateEdge && neighbors.hasNext()){//保證不添加劇復的邊 143 VertexInterface<T> nextNeighbor = neighbors.next(); 144 if(endVertex.equals(nextNeighbor)){ 145 duplicateEdge = true; 146 break; 147 } 148 }//end while 149 if(!duplicateEdge){ 150 edgeList.add(new Edge(endVertex, edgeWeight));//添加一條新邊 151 result = true; 152 }//end if 153 }//end if 154 return result; 155 } 156 157 @Override 158 public boolean connect(VertexInterface<T> endVertex) { 159 return connect(endVertex, 0); 160 } 161 162 @Override 163 public Iterator<VertexInterface<T>> getNeighborInterator() { 164 return new NeighborIterator(); 165 } 166 167 @Override 168 public Iterator getWeightIterator() { 169 return new WeightIterator(); 170 } 171 172 @Override 173 public boolean hasNeighbor() { 174 return !(edgeList.isEmpty());//鄰接點實質是存儲是List中 175 } 176 177 @Override 178 public VertexInterface<T> getUnvisitedNeighbor() { 179 VertexInterface<T> result = null; 180 Iterator<VertexInterface<T>> neighbors = getNeighborInterator(); 181 while(neighbors.hasNext() && result == null){//得到該頂點的第一個未被訪問的鄰接點 182 VertexInterface<T> nextNeighbor = neighbors.next(); 183 if(!nextNeighbor.isVisited()) 184 result = nextNeighbor; 185 } 186 return result; 187 } 188 189 @Override 190 public void setPredecessor(VertexInterface<T> predecessor) { 191 this.previousVertex = predecessor; 192 } 193 194 @Override 195 public VertexInterface<T> getPredecessor() { 196 return this.previousVertex; 197 } 198 199 @Override 200 public boolean hasPredecessor() { 201 return this.previousVertex != null; 202 } 203 204 @Override 205 public void setCost(double newCost) { 206 cost = newCost; 207 } 208 209 @Override 210 public double getCost() { 211 return cost; 212 } 213 214 //判斷兩個頂點是否相同 215 public boolean equals(Object other){ 216 boolean result; 217 if((other == null) || (getClass() != other.getClass())) 218 result = false; 219 else 220 { 221 Vertex<T> otherVertex = (Vertex<T>)other; 222 result = label.equals(otherVertex.label);//節點是否相同最終仍是由標識 節點類型的類的equals() 決定 223 } 224 return result; 225 } 226 }