圖的操作集(java實現)
阿新 • • 發佈:2018-11-24
本篇部落格主要介紹圖的建立、遍歷、最短路徑問題以及最小生成樹問題,包含了深度優先搜尋、廣度優先搜尋、Dijkstra演算法解決單源最短路徑問題、Floyd演算法解決多源最短路徑問題、Prim演算法和Kruskal解決最小生成樹問題。(詳細的說明一般喜歡嵌入到程式碼當中)
1、先看一下頂點型別
//圖的頂點型別 class Vertex { public char value; //頂點值 public boolean visited; //頂點是否被訪問過 public Vertex(char v) { value=v; visited=false; } }
2、然後再看邊的型別
//儲存邊的兩個頂點及邊的權值
class Edge{
public int u; //頂點
public int v; //頂點
public int w; //權值
public Edge(int u,int v,int w) {
this.u=u;
this.v=v;
this.w=w;
}
}
3、介紹了頂點和邊,就要說怎樣建立圖了。一般先建立一個沒有邊的圖,這裡用鄰接矩陣儲存頂點的權值,若沒有邊,則權值設定為無窮大。
//有權無向圖 public class Graph { private Vertex[] vertexList; //存放頂點 private int[][] mGraph; //用矩陣表示邊 private MyStack stack; //棧 private MyQueue queue; //佇列 private int size; //當前頂點數 private int[] distance; //記錄到起點的距離 private int[] path; //記錄最短路徑經過的頂點 //比如path[w]=v,表示從起點到頂點w需要先經過w的父頂點v private int[][] dist; //dist[i][j]記錄i到j的最短距離 private int[][] prev; //prev[i][j]=k表示i到j的最短路徑會經過頂點k //並查集中指向父頂點的陣列 int[] parent; public Graph(int maxSize) { vertexList=new Vertex[maxSize]; mGraph=new int[maxSize][maxSize]; stack=new MyStack(maxSize); queue=new MyQueue(maxSize); size=-1; distance=new int[maxSize]; path=new int[maxSize]; dist=new int[maxSize][maxSize]; prev=new int[maxSize][maxSize]; parent=new int[vertexList.length]; init(); //初始化為沒有邊的圖 } //初始化為沒有邊的圖 public void init() { for(int i=0;i<mGraph.length;i++) { for(int j=0;j<mGraph[i].length;j++) { if(i==j) { mGraph[i][j]=0; //自己到自己的權重為0 }else { //還沒有邊的時候,權重初始化為無窮大 mGraph[i][j]=Integer.MAX_VALUE; } } } } }
4、先圖中新增頂點和邊
//新增頂點 public boolean addVertex(char v) { if(size==vertexList.length) { System.out.println("滿了,無法新增頂點"); return false; } vertexList[++size]=new Vertex(v); return true; } //新增邊 public boolean addEdge(int start,int end,int weight) { if(start<0||start>=mGraph.length||end<0||end>=mGraph.length) { System.out.println("輸入的頂點序號不符合要求"); return false; } mGraph[start][end]=weight; mGraph[end][start]=weight; return true; }
5、遍歷圖中的頂點(包括深度優先搜尋和廣度優先搜尋)
//深度優先搜尋depthFirstSearch,有點類似於樹的先序遍歷
public void DFS() {
System.out.print(vertexList[0].value+" "); //訪問第一個頂點
vertexList[0].visited=true; //表示第一個頂點已經訪問過了
stack.push(0); //將第一個頂點入棧
//當棧中還有元素
while(!stack.isEmpty()) {
//找到棧當前頂點鄰接且未被訪問的頂點
int v=getUnvisitedVertex(stack.peek());
//如果當前頂點值為-1,則表示沒有鄰接且未被訪問頂點,那麼出棧頂點
if(v == -1) {
stack.pop();
}else { //否則訪問下一個鄰接頂點
vertexList[v].visited = true;
System.out.print(vertexList[v].value+" ");
stack.push(v);
}
}
//恢復visited為false,方便下次訪問
for(int i=0;i<vertexList.length;i++) {
vertexList[i].visited=false;
}
}
//找到與某一頂點鄰接且未被訪問的頂點
public int getUnvisitedVertex(int v) {
for(int i = 0; i < mGraph.length; i++) {
//v頂點與i頂點相鄰(鄰接矩陣值為1)且未被訪問 wasVisited==false
if(v!=i && mGraph[v][i] < Integer.MAX_VALUE && vertexList[i].visited == false) {
return i;
}
}
return -1;
}
//廣度優先搜尋breadthFirstSearch,有點類似於樹的層次遍歷
public void BFS() {
System.out.print(vertexList[0].value+" "); //訪問第一個頂點
vertexList[0].visited=true; //標誌已經訪問過了
queue.add(0); //將第一個頂點入隊
//當佇列不為空
while(!queue.isEmpty()) {
//尋找當前頂點沒有訪問的鄰接點
int v=getUnvisitedVertex(queue.peek());
if(v!=-1) {
System.out.print(vertexList[v].value+" ");
vertexList[v].visited=true;
queue.add(v);
}else {
queue.remove();
}
}
//恢復visited為false,方便下次訪問
for(int i=0;i<vertexList.length;i++) {
vertexList[i].visited=false;
}
}
6、Dijkstra解決單源最短路徑問題
//Dijkstra解決單源最短路徑問題
//它的主要特點是以起始點為中心向外層層擴充套件(廣度優先搜尋思想),直到擴充套件到終點為止
//s表示的是以第幾個頂點為起點,從0開始
public void dijkstra(int s) {
//初始化距離和路徑
for(int i=0;i<vertexList.length;i++) {
distance[i]=Integer.MAX_VALUE; //到起點的距離設定為無窮大
path[i]=-1; //到起點的路徑初始化為-1
}
distance[s]=0; //到起點本身的距離為0
//每次取最小值,其實可以用最小堆來實現,這裡就直接比較了
//進行n次迴圈
for(int i=0;i<vertexList.length;i++) {
int k=0; //記錄最小路徑的頂點序號
int min=Integer.MAX_VALUE;
//從未被訪問過的頂點中找一個距離最小的頂點
for(int j=0;j<vertexList.length;j++) {
//如果還沒有訪問過,並且比當前值要小
if(vertexList[j].visited==false && distance[j]<min) {
vertexList[j].visited=true; //設定為已訪問
min=distance[j]; //更新最小值
k=j; //記錄最小路徑的頂點序號
}
}
// 修正當前最短路徑和前驅頂點
// 即,當已知"頂點k的最短路徑"之後,更新"未獲取最短路徑的頂點的最短路徑和前驅頂點"。
for (int j = 0; j < vertexList.length; j++) {
//找頂點k的鄰接點j,並更新它的鄰接點到起點的最短路徑
int tmp = (mGraph[k][j]==Integer.MAX_VALUE ? Integer.MAX_VALUE : (min + mGraph[k][j]));
//min + mGraph[k][j]就表示頂點k的最短路徑加上<k,j>邊的權重,即為頂點j可能的最短路徑
if (vertexList[j].visited==false && (tmp<distance[j]) ) {
distance[j] = tmp; //更新頂點j到起點的最短路徑
path[j] = k; //設定它的父頂點為k
}
}
}
// 列印Dijkstra最短路徑的結果
printDijkstra(s);
//恢復visited為false,方便下次訪問
for(int i=0;i<vertexList.length;i++) {
vertexList[i].visited=false;
}
}
// 列印Dijkstra最短路徑的結果
public void printDijkstra(int s) {
//利用棧後進先出的特性,將路徑逆序
MyStack st=new MyStack(vertexList.length);
System.out.printf("dijkstra(%c): \n", vertexList[s].value);
for (int i=0; i < vertexList.length; i++) {
System.out.printf(" shortest(%c, %c)=%d 路徑為:", vertexList[s].value, vertexList[i].value, distance[i]);
//這裡可以用一個棧來儲存頂點,然後出棧就是順序輸出了,而不是反向輸出
//列印路徑
st.push(i); //終點
int tmp=path[i];
while(tmp!=-1) {
st.push(tmp);
tmp=path[tmp];
}
while(!st.isEmpty()) {
System.out.printf("%c-->",vertexList[st.pop()].value);
}
System.out.println();
}
}
7、Floyd演算法求解任意兩個頂點的最短距離問題,也就是多源最短路徑問題
//Floyd演算法求解任意兩個頂點的最短距離問題,也就是多源最短路徑問題
public void floyd() {
//初始化
System.out.println("初始化的值:");
for(int i=0;i<vertexList.length;i++) {
for(int j=0;j<vertexList.length;j++) {
dist[i][j]=mGraph[i][j]; //儲存的是權值
prev[i][j]=j; //i到j一定會經過j
}
}
//三重迴圈,最外層的是頂點的個數,中間兩層是遍歷整個矩陣
//思想是:當k=0時,就藉助於第k個頂點,如果i到j的距離可以變小,則更新最小距離
//其實就是藉助於前k個頂點,如果i到j的距離可以變小,則更新最小距離
for(int k=0;k<vertexList.length;k++) {
for(int i=0;i<vertexList.length;i++) {
for(int j=0;j<vertexList.length;j++) {
// 如果經過下標為k頂點路徑比原兩點間路徑更短,則更新dist[i][j]和prev[i][j]
int tmp = (dist[i][k]==Integer.MAX_VALUE || dist[k][j]==Integer.MAX_VALUE) ? Integer.MAX_VALUE : (dist[i][k] + dist[k][j]);
if (dist[i][j] > tmp) {
// "i到j最短路徑"對應的值設,為更小的一個(即經過k)
dist[i][j] = tmp;
// "i到j最短路徑"對應的路徑,經過k
prev[i][j] = prev[i][k];
}
}
}
}
// 列印floyd最短路徑的結果
System.out.printf("floyd: \n");
for (int i = 0; i < vertexList.length; i++) {
for (int j = 0; j < vertexList.length; j++)
System.out.printf("%2d ", dist[i][j]);
System.out.printf("\n");
}
}
8、Prim演算法解決最小生成樹問題,以頂點為思考物件
//Prim演算法解決最小生成樹問題,以頂點為思考物件
public void prim(int start) {
int[] prims=new int[vertexList.length]; //記錄最小生成樹的頂點序號
//初始化
for(int i=0;i<vertexList.length;i++) {
distance[i]=mGraph[start][i]; //到起點的權值
prims[i]=-1;
}
distance[start]=0; //自己到自己的距離為0
int index=0; //最小生成樹的索引
prims[index++]=start;
// vertexList[start].visited=true; //表示已經在最小生成樹中
for(int i=0;i<vertexList.length;i++) {
if(i==start) {
continue;
}
int min=Integer.MAX_VALUE;
int k=-1;
for(int j=0;j<vertexList.length;j++) {
//distance[j]==0,表示已經在最小生成樹中
//如果不在最小生成樹中,並且與最小生成樹中的某個頂點組成的邊的權值更小
if(distance[j]!=0 && distance[j]<min) {
min=distance[j];
k=j;
}
}
//迴圈結束後,第k個頂點就是和已經收錄的頂點構成邊權值最小的頂點
prims[index++]=k;
//vertexList[k].visited=true;
distance[k]=0; //表示已經在最小生成樹中
//更新第k個頂點到未被收錄進最小生成樹中鄰接點的權值
for(int j=0;j<vertexList.length;j++) {
//如果j是k的未被收錄的鄰接點
if(distance[j]!=0 && mGraph[k][j]+distance[k]<distance[j]) {
distance[j]=mGraph[k][j]+distance[k];
}
}
}
//列印最小生成樹
for(int i=0;i<vertexList.length;i++) {
System.out.print(vertexList[prims[i]].value+" ");
}
//列印最小權值:從最小生成樹的第二個頂點開始,找它到前驅頂點的最小權值
int sum=0;
//一共n-1條邊
for(int i=1;i<vertexList.length;i++) {
int min=Integer.MAX_VALUE;
for(int j=0;j<i;j++) {
if(mGraph[prims[j]][prims[i]]<min) {
//prims[j]表示已經在最小生成樹中的頂點
min=mGraph[prims[j]][prims[i]];
}
}
sum+=min;
}
System.out.println("最小權值和為:"+sum);
}
9、Kruskal演算法:求最小生成樹
//Kruskal演算法:求最小生成樹
public void kruskal() {
ArrayList<Edge> list=new ArrayList<>();
//初始化邊
for(int i=0;i<vertexList.length;i++) {
for(int j=0;j<vertexList.length;j++) {
//如果兩個頂點有邊
if(mGraph[i][j]!=0 && mGraph[i][j]<Integer.MAX_VALUE) {
list.add(new Edge(i,j,mGraph[i][j]));
}
}
}
//對邊按權值排序
Collections.sort(list, new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.w-o2.w; //權值小的在前
}
});
//初始化並查集,parent[i]=-1;表示這棵樹只有它自己,一開始是n棵樹
for(int i=0;i<parent.length;i++) {
parent[i]=-1;
}
//下面才是kruskal演算法
//list.size()就是邊的數量
int u,v,num=0,sum=0,index=0;
char[] result=new char[2*vertexList.length-2]; //記錄結果的陣列,邊的順序
System.out.println("下面是kruskal演算法:");
for(int i=0;i<list.size();i++) {
Edge e=list.get(i);
u=e.u;
v=e.v;
//如果頂點不屬於同一個集合
if(findRoot(u)!=findRoot(v)) {
sum+=e.w;
result[index++]=vertexList[u].value;
result[index++]=vertexList[v].value;
num++;
union(u, v);
}
//如果有n-1條邊,就退出了
if(num==vertexList.length-1) {
break;
}
}
//列印邊的資訊
System.out.println("kruskal包括的邊依次是:");
for(int i=0;i<result.length;i+=2) {
System.out.println(result[i]+"--"+result[i+1]);
}
System.out.println("kruskal的最小權值:"+sum);
}
//查詢某個頂點屬於哪個集合
public int findRoot(int v) {
int root; //集合的根節點
for(root=v;parent[root]>=0;root=parent[root]);
//路徑壓縮
while(root!=v) {
int tmp=parent[v];
parent[v]=root;
v=tmp;
}
return root;
}
//將兩個不同集合的元素進行合併,使兩個集合中任兩個元素都連通
void union( int u, int v)
{
int r1 = findRoot(u), r2 = findRoot(v); //r1 為 u 的根結點,r2 為 v 的根結點
int tmp = parent[r1] + parent[r2]; //兩個集合結點個數之和(負數)
//如果 R2 所在樹結點個數 > R1 所在樹結點個數(注意 parent[r1]是負數)
if( parent[r1] > parent[r2] ) //優化方案――加權法則
{
parent[r1] = r2;
parent[r2] = tmp;
}
else
{
parent[r2] = r1;
parent[r1] = tmp;
}
}
10、測試各個部分
public static void main(String[] args) {
Graph g=new Graph(7);
g.addVertex('A');
g.addVertex('B');
g.addVertex('C');
g.addVertex('D');
g.addVertex('E');
g.addVertex('F');
g.addVertex('G');
//下面是邊的關係,有邊的值為權重,無邊的值為無窮大
// int matrix[][] = {
// /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
// /*A*/ { 0, 12, INF, INF, INF, 16, 14},
// /*B*/ { 12, 0, 10, INF, INF, 7, INF},
// /*C*/ { INF, 10, 0, 3, 5, 6, INF},
// /*D*/ { INF, INF, 3, 0, 4, INF, INF},
// /*E*/ { INF, INF, 5, 4, 0, 2, 8},
// /*F*/ { 16, 7, 6, INF, 2, 0, 9},
// /*G*/ { 14, INF, INF, INF, 8, 9, 0}};
g.addEdge(0, 1,12); //AB相連
g.addEdge(0, 5, 16); //AF
g.addEdge(0, 6, 14); //AG
g.addEdge(1, 2,10); //BC相連
g.addEdge(1, 5, 7); //BF
g.addEdge(2, 3,3); //CD相連
g.addEdge(2, 4,5); //CE相連
g.addEdge(2, 5, 6); //CF
g.addEdge(3, 4, 4); //DE
g.addEdge(4, 5, 2); //EF
g.addEdge(4, 6, 8); //EG
g.addEdge(5, 6, 9); //FG
//深度優先訪問
System.out.println("深度優先:");
g.DFS();
System.out.println();
//廣度優先搜尋
System.out.println("廣度優先:");
g.BFS();
System.out.println();
//Dijkstra演算法
g.dijkstra(3);
System.out.println("路徑:");
for(int i=0;i<g.vertexList.length;i++) {
System.out.print(g.path[i]+" ");
}
//Floyd演算法
System.out.println();
g.floyd();
//最小生成樹:prim演算法
System.out.println("最小生成樹:prim演算法");
g.prim(0);
//最小生成樹:kruskal演算法
g.kruskal();
}