使用鄰接矩陣實現圖結構
關於圖的一些特點就不說了,現在我們先展示的是頂點的實現
/**
* Created by 西皮 on 2017/9/15 19:58.
* 圖的頂點類
*/
public class MyVertex<VItem> {
private VItem data;//資料
private int inDegree,outDegree;//出入度數
private VStatus status;//狀態
private long dTime,fTime;//時間標籤
private int parent;//在遍歷樹中的父節點
private int priority;//在遍歷樹中的優先順序
public MyVertex(VItem data){
this.data = data;
this.inDegree = 0;
this.outDegree = 0;
this.status = VStatus.UNDISCOVERED;
this.dTime = -1;
this.fTime = -1;
this.parent = -1;
this.priority = Integer.MAX_VALUE;
}
public VItem getData () {
return data;
}
public void setData(VItem data) {
this.data = data;
}
public int getInDegree() {
return inDegree;
}
public void setInDegree(int inDegree) {
this.inDegree = inDegree;
}
public int getOutDegree() {
return outDegree;
}
public void setOutDegree(int outDegree) {
this.outDegree = outDegree;
}
public VStatus getStatus() {
return status;
}
public void setStatus(VStatus status) {
this.status = status;
}
public long getdTime() {
return dTime;
}
public void setdTime(long dTime) {
this.dTime = dTime;
}
public long getfTime() {
return fTime;
}
public void setfTime(long fTime) {
this.fTime = fTime;
}
public int getParent() {
return parent;
}
public void setParent(int parent) {
this.parent = parent;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
}
這裡的屬性,status使用的enum來標識的,三種頂點狀態,分別是UNDISCOVERED
,DISCOVERED
,VISITED
,分別用來表示未被發現,已被發現但是還未訪問完畢,已經被訪問完畢
/**
* Created by 西皮 on 2017/9/16 9:49.
* 定義頂點狀態的列舉類
*/
public enum VStatus {
UNDISCOVERED("UNDISCOVERED"),DISCOVERED("DISCOVERED"),VISITED("VISITED");
private final String value;
VStatus(String value){
this.value = value;
}
public String getValue() {
return value;
}
}
接下來展示邊
/**
* Created by 西皮 on 2017/9/16 10:09.
* 圖的邊類
*/
public class MyEdge<EItem> {
private EItem data;//資料
private int weight;//權重
private EType type;//在便利書中所屬的型別
public MyEdge(EItem data,int weight){
this.data = data;
this.weight = weight;
this.type = EType.UNDETERMINED;
}
public EItem getData() {
return data;
}
public void setData(EItem data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public EType getType() {
return type;
}
public void setType(EType type) {
this.type = type;
}
}
這裡的邊同樣使用enum來表示邊的狀態
/**
* Created by 西皮 on 2017/9/16 9:55.
* 定義邊狀態的列舉類
*/
public enum EType {
UNDETERMINED("UNDETERMINED"),TREE("TREE"),
CROSS("CROSS"),FORWARD("FORWARD"),BACKWARD("BACKWARD");
private final String value;
EType(String value){
this.value = value;
}
public String getValue(){
return value;
}
}
UNDETERMINED
圖初始化時,所有邊預設是UNDETERMINED,其他的邊狀態需要結合圖的BFS和DFS來看,更簡單,這裡我們暫且放一邊。
接下來,我們來實現一個圖
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Created by 西皮 on 2017/9/16 10:13.
* 矩陣圖
*/
public class MyGrapMatrix<VItem,EItem> {
private List<MyVertex<VItem>> V = new ArrayList<>();//頂點集
private List<List<MyEdge<EItem>>> E = new ArrayList<>();//邊集,使用一個鄰接矩陣來表示邊
private int n;//頂點數量
private int e;//邊的數量
public MyGrapMatrix(){}
public VItem getMyVertex(int i){
return V.get(i).getData();//獲得資料
}
public int getMyVertexInDegree(int i){
return V.get(i).getInDegree();//獲得頂點入度
}
public int getMyVertexOutDegree(int i){
return V.get(i).getOutDegree();
}
public VStatus getMyVertexStatus(int i){
return V.get(i).getStatus();
}
public int getVertexNum(){
return n;
}
public int getEdgeNum(){
return e;
}
public long getMyVertexDTime(int i){
return V.get(i).getdTime();
}
public long getMyVertexFTime(int i){
return V.get(i).getfTime();
}
public int getMyVertexParent(int i){
return V.get(i).getParent();
}
public int getMyVertexPriority(int i){
return V.get(i).getPriority();
}
/**
* 獲得當前節點i的比j小的下一個鄰居
* @param i
* @param j
* @return
*/
public int nextNbr(int i,int j){
while (-1 < j && !exisits(i,--j));
return j;
}
/**
* 獲得頂點i的首個鄰居
* @param i
* @return
*/
public int firstNbr(int i){
return nextNbr(i,V.size());
}
/**
* 判斷兩個頂點是否有邊
* @param i
* @param j
* @return
*/
public boolean exisits(int i,int j){
if((0 <= i) && (i < n) &&
(0 <= j) && (j < n) &&
(E.get(i).get(j) != null))
return true;
return false;
}
/**
* 獲得邊(i,j)的資料
* @param i
* @param j
* @return
*/
public EItem getMyEdgeData(int i,int j){
return E.get(i).get(j).getData();
}
public EType getMyEdgeStatus(int i,int j){
return E.get(i).get(j).getType();
}
public int getMyEdgeWeight(int i,int j){
return E.get(i).get(j).getWeight();
}
/**
* 插入一條邊,邊的兩個頂點分別是i和j
* @param edgeData
* @param weight
* @param i
* @param j
*/
public void insertEdge(EItem edgeData,int weight,int i,int j){
if (exisits(i,j)) return;//忽略已有的邊
E.get(i).set(j,new MyEdge<EItem>(edgeData,weight));
e++;//更新邊計數
//更新關聯頂點i的出度和j的入度
V.get(i).setOutDegree(V.get(i).getOutDegree()+1);
V.get(j).setInDegree(V.get(j).getInDegree()+1);
}
/**
* 刪除邊(i,j)
* @param i
* @param j
* @return
*/
public EItem removeEdge(int i,int j){
EItem eBak = getMyEdgeData(i,j);
E.get(i).set(j,null);//刪除邊(i,j)
e--;//更新邊計數
V.get(i).setOutDegree(V.get(i).getOutDegree()-1);
V.get(j).setInDegree(V.get(j).getInDegree()-1);
return eBak;
}
/**
* 頂點插入
* @param vertexData
* @return
*/
public int insertVertex(VItem vertexData){
for (int j = 0;j < n;j++) E.get(j).add(null);
n++;
ArrayList<MyEdge<EItem>> lineEdges = new ArrayList<>(n);
for (int i = 0;i < n;i++){
lineEdges.add(null);
}
E.add(lineEdges);
V.add(new MyVertex<VItem>(vertexData));
return n-1;
}
/**
* 刪除頂點及其關聯邊,返回該頂點資訊
* @param i
* @return
* 索引所對應的頂點就改變了
*/
public VItem removeVertex(int i){
for (int j = 0;j < n;j++){
if(exisits(i,j)){
removeEdge(i,j);
}
}
E.remove(i);n--;//刪除第i行
VItem vBak = V.get(i).getData();V.remove(i);//備份之後,刪除頂點i
for (int j = 0;j < n;j++){//刪除所有入邊及第i列
E.get(j).remove(i);
V.get(j).setOutDegree(V.get(j).getOutDegree()-1);
}
return vBak;//返回被刪除頂點的資訊
}
private int BFSclock = 0;
/**
* 廣度優先搜尋Breadth-First Search
* @param v
*/
public void BFS(int v){
MyQueue<Integer> Q = new MyQueue<>();
V.get(v).setStatus(VStatus.DISCOVERED);
Q.enqueue(v);
while (!Q.isEmpty()){
v = Q.dequeue();
System.out.println(V.get(v).getData());
V.get(v).setdTime(++BFSclock);//取出對首頂點v,並給個時間戳
//考察v的每一個鄰居u
for (int u = firstNbr(v);-1 < u; u = nextNbr(v,u)){
if (VStatus.UNDISCOVERED == V.get(u).getStatus()){
//若u尚未被發現
V.get(u).setStatus(VStatus.DISCOVERED);
Q.enqueue(u);//發現該頂點
//將他們之間的邊設定為TREE邊
E.get(v).get(u).setType(EType.TREE);
//把u在遍歷樹中的父節點設定成v
V.get(u).setParent(v);
}else {
E.get(v).get(u).setType(EType.CROSS);
}
}
//至此,當前頂點訪問完畢
V.get(v).setStatus(VStatus.VISITED);
}
}
/**
* @param s
*/
public void bfs(int s){//s為初始頂點
BFSclock = 0;
int v = s;
do {
if(VStatus.UNDISCOVERED == V.get(v).getStatus())
BFS(v);//即從該頂點出發啟動一次BFS
}while (s != (v = (++v % n)));
}
private int DFSclock = 0;
/**
* 深度優先遍歷
* @param v
*/
public void DFS(int v){
V.get(v).setdTime(++DFSclock);
V.get(v).setStatus(VStatus.DISCOVERED);//發現當前頂點v
System.out.println(V.get(v).getData());
for (int u = firstNbr(v); -1 < u; u = nextNbr(v,u)){//列舉v的每一鄰居u
switch (V.get(u).getStatus()){//並視其狀態分別處理
case UNDISCOVERED://u尚未發現,意味著支撐樹可在此擴充套件
E.get(v).get(u).setType(EType.TREE);
V.get(u).setParent(v);
DFS(u);
break;
case DISCOVERED://u已被發現但尚未訪問完畢,應屬被後代指向的祖先
E.get(v).get(u).setType(EType.BACKWARD);
break;
default://u已訪問完畢(VISITED,有向圖),則視承襲關係分為前向邊或跨邊
E.get(v).get(u).setType(V.get(v).getdTime()<V.get(u).getdTime()?EType.FORWARD:EType.CROSS);
break;
}//switch
}
V.get(v).setStatus(VStatus.VISITED);
V.get(v).setfTime(++DFSclock);//至此,當前頂點v方告訪問完畢
}
public void dfs(int s){//s為初始頂點
DFSclock = 0;
int v = s;
do {
if(VStatus.UNDISCOVERED == V.get(v).getStatus())
DFS(v);//即從該頂點出發啟動一次BFS
}while (s != (v = (++v % n)));
}
}
頂點集使用一個ArrayList,這是一個數組,使用它的索引來標識不同的頂點,而邊則使用的是一個二維陣列來來表示,這樣會使他的空間複雜度為Θ(n^2)
。
這個導致這種結構所使用儲存空間和這個圖的邊數沒有關係,所以像這樣使用鄰接矩陣實現的圖更適合稠密圖。
在這裡我們首先看看這裡的exists()
方法
/**
* 判斷兩個頂點是否有邊
* @param i
* @param j
* @return
*/
public boolean exisits(int i,int j){
if((0 <= i) && (i < n) &&
(0 <= j) && (j < n) &&
(E.get(i).get(j) != null))
return true;
return false;
}
我們首先需要確定i和j這兩個表示頂點的序號是可用的,然後在去表示邊的矩陣中查詢從i指向j的這條邊是否存在,也就是我們是否把MyEdge的物件存放進這個矩陣中,為null則不存在,否則存在。
接下來,我們來看看firstNbr(int i)和nextNbr(int i)
這兩個方法
/**
* 獲得當前節點i的比j小的下一個鄰居
* @param i
* @param j
* @return
*/
public int nextNbr(int i,int j){
while (-1 < j && !exisits(i,--j));
return j;
}
/**
* 獲得頂點i的首個鄰居
* @param i
* @return
*/
public int firstNbr(int i){
return nextNbr(i,V.size());
}
呼叫firstNbr()可以獲得i頂點的從尾部開始的第一個鄰居,而nextNbr()可以循著這個方向向前再找一個新鄰居,這裡在遍歷中時非常有用的。
接下來是對圖的動態操作,分別為對邊的新增刪除,和對頂點的新增刪除,邊的新增刪除沒什麼問題,看看程式碼就夠了,這裡我們著重看一看頂點的新增刪除
/**
* 頂點插入
* @param vertexData
* @return
*/
public int insertVertex(VItem vertexData){
for (int j = 0;j < n;j++) E.get(j).add(null);
n++;
ArrayList<MyEdge<EItem>> lineEdges = new ArrayList<>(n);
for (int i = 0;i < n;i++){
lineEdges.add(null);
}
E.add(lineEdges);
V.add(new MyVertex<VItem>(vertexData));
return n-1;
}
/**
* 刪除頂點及其關聯邊,返回該頂點資訊
* @param i
* @return
* 索引所對應的頂點就改變了
*/
public VItem removeVertex(int i){
for (int j = 0;j < n;j++){
if(exisits(i,j)){
removeEdge(i,j);
}
}
E.remove(i);n--;//刪除第i行
VItem vBak = V.get(i).getData();V.remove(i);//備份之後,刪除頂點i
for (int j = 0;j < n;j++){//刪除所有入邊及第i列
E.get(j).remove(i);
V.get(j).setOutDegree(V.get(j).getOutDegree()-1);
}
return vBak;//返回被刪除頂點的資訊
}
頂點新增時,我們首先需要對應的在表示邊的矩陣中新增一列和一行,因為這兩個V頂點集和E邊集是對應的,所以我們看到首先遍歷每一行,為每一行新增一個為null的邊也就是這兩個頂點間的邊還不存在,讓頂點的數量n++;再接下來,我們為這個矩陣新增一行資料,當然這一行資料存放的也全是空,因為這個新頂點還沒有與任何頂點關聯。最後我們把這個新頂點放在V集的末尾。
此時,我們可以看到儘管我們對陣列做了這麼多動態操作,但實質上這些資料全是直接新增在陣列的最後一個位置,這個操作的時間複雜度是Ο(1)也就是常數時間。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
這個是add(E e)的實際操作,我們可以看到,他首先判斷容量是否足夠,不夠就擴容,夠就直接把資料存放在最後,然後size++
那麼刪除時也是同理,但是刪除的時間複雜度就與他的i有關係了,因為i後面的元素需要前移,同時又因為是一個二維陣列的,這裡時間複雜度應該是Ο(n^2),其他就是類似的操作了,這裡就不再說了。
最後測試一下個圖是否正確
@Test
public void testGraphBFS(){
MyGrapMatrix<String,Integer> myGrapMatrix = new MyGrapMatrix<>();
myGrapMatrix.insertVertex("S");
myGrapMatrix.insertVertex("A");
myGrapMatrix.insertVertex("D");
myGrapMatrix.insertVertex("E");
myGrapMatrix.insertVertex("C");
myGrapMatrix.insertVertex("B");
myGrapMatrix.insertVertex("F");
myGrapMatrix.insertVertex("G");
myGrapMatrix.insertEdge(1,1,0,1);
myGrapMatrix.insertEdge(1,1,0,4);
myGrapMatrix.insertEdge(1,1,0,2);
myGrapMatrix.insertEdge(1,1,1,0);
myGrapMatrix.insertEdge(1,1,1,4);
myGrapMatrix.insertEdge(1,1,1,3);
myGrapMatrix.insertEdge(1,1,2,0);
myGrapMatrix.insertEdge(1,1,2,5);
myGrapMatrix.insertEdge(1,1,3,1);
myGrapMatrix.insertEdge(1,1,3,6);
myGrapMatrix.insertEdge(1,1,3,7);
myGrapMatrix.insertEdge(1,1,4,0);
myGrapMatrix.insertEdge(1,1,4,1);
myGrapMatrix.insertEdge(1,1,4,5);
myGrapMatrix.insertEdge(1,1,5,2);
myGrapMatrix.insertEdge(1,1,5,4);
myGrapMatrix.insertEdge(1,1,6,3);
myGrapMatrix.insertEdge(1,1,6,7);
myGrapMatrix.insertEdge(1,1,7,3);
myGrapMatrix.insertEdge(1,1,7,5);
myGrapMatrix.insertEdge(1,1,7,6);
System.out.println(myGrapMatrix.getVertexNum());
System.out.println(myGrapMatrix.getEdgeNum());
myGrapMatrix.BFS(0);
}
這裡同時測試了樹的BFS,確實我們完成樹的同時也順便完成樹的DFS和BFS,這些東西,以後再說。