數據結構--圖 的JAVA實現(上)

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 }
相關文章
相關標籤/搜索