圖的概念
圖是一種非線性的數據結構,一個圖中有兩類東西,一種是結點,一種是邊.我們用V這個集合來表示節點(vertex),還需要另一個集合來存儲所有的邊,我們用E來表示(Edge),那么一個圖就可以表示為:G=(V,E);
帶箭頭的稱為有向圖,否則稱為無向圖.
如果一個圖的任意兩個結點之間有且只有一條邊,則稱此圖為無向完全圖,若任意兩個結點之間有且只有方向相反的兩條邊,則稱為有向完全圖.
度是針對結點來說的, 又分為出度和入度,對于有向圖來說,出度就是指以這個結點為起始的邊的條數(箭頭向外),入度則是以這個點為終點的邊的條數(箭頭向內).
權是指一條邊所附帶的數據信息,比如說一個結點到另一個結點的距離,或者花費的時間等等都可以用權來表示。
圖的存儲結構
存儲一個圖可以采用鄰接矩陣或者鄰接表.
一個無向圖的鄰接矩陣如下:
它消耗的空間為Θ|V|2,適合圖很稠密的時候.
同樣的無向圖用鄰接表表示如下
消耗的空間為O(|E|+|V|).適合圖稀疏的時候.
先來用代碼實現有向圖鄰接矩陣的圖模型,無向圖差不多的不寫了,根據下圖來實現:
package com.fredal.structure;
import java.util.ArrayList;
public class MyGraphy {
private ArrayList vList;//用來存節點
private int[][] edges;//鄰接矩陣 用來存儲邊
private int numOfEdges;//邊的條數
public MyGraphy(int n){
//初始化矩陣
edges=new int[n][n];
vList=new ArrayList(n);
numOfEdges=0;
}
public int[][] getEdges() {
return edges;
}
//得到節點個數
public int getNumOfVertex(){
return vList.size();
}
//得到邊的個數
public int getNumOfEdges(){
return numOfEdges;
}
//返回節點i的值
public Object get(int i){
return vList.get(i);
}
//返回v1,v2節點之間邊的權值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//插入節點
public void insertVertext(Object vertex){
vList.add(vList.size(),vertex);
edges=new int[vList.size()][vList.size()];
}
//插入邊
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
numOfEdges++;
}
//刪除節點
public void deleteVertex(int v){
vList.remove(v);
for(int i=0;i<vList.size();i++){
edges[v][i]=0;
numOfEdges--;
}
}
//刪除邊
public void deleteEdge(int v1,int v2){
edges[v1][v2]=0;
numOfEdges--;
}
//得到第一個鄰接節點的下標
public int getFirstneighbor(int i){
for(int j=0;j<vList.size();j++){
if(edges[i][j]>0){
return j;
}
}
return -1;
}
//根據前一個鄰接節點的下標來取得下一個鄰接節點
public int getNextNeighbor(int v1,int v2){
for(int j=v2+1;j<vList.size();j++){
if(edges[v1][j]>0){
return j;
}
}
return -1;
}
//測試程序
public static void main(String[] args) {
String labels[]={"V1","V2","V3","V4"};//結點的標識
MyGraphy g=new MyGraphy(4);
for(String label:labels) {
g.insertVertext(label);//插入結點
}
g.insertEdge(0, 1, 2);
g.insertEdge(0, 2, 5);
g.insertEdge(2, 3, 8);
g.insertEdge(3, 0, 7);
System.out.println("結點個數是:"+g.getNumOfVertex());
System.out.println("邊的個數是:"+g.getNumOfEdges());
g.deleteEdge(0, 1);//刪除<V1,V2>邊
System.out.println("刪除<V1,V2>邊后...");
System.out.println("結點個數是:"+g.getNumOfVertex());
System.out.println("邊的個數是:"+g.getNumOfEdges());
}
}
接下來我們用鄰接表來實現圖的存儲,同樣是前面那張圖
package com.fredal.structure;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.fredal.structure.MyGraphyL.Node;
import com.fredal.structure.MyGraphyL.Vertex;
public class MyGraphyL {
class Vertex{
private Object value;//頂點的名稱
private int vvalue;//定點的額值
public LinkedList<Node> elist;//鏈表 用來存放邊的信息
public Vertex(Object value,int vvalue){
this.value=value;
this.vvalue=vvalue;
elist=new LinkedList<Node>();
}
}
class Node{
private int evalue;//邊的節點的值
private int weight;//權重
public Node(int evalue,int weight){
this.evalue=evalue;
this.weight=weight;
}
public int getEvalue() {
return evalue;
}
public int getWeight() {
return weight;
}
}
private List<Vertex> vertexs;//存儲節點
private int numOfEdges;//邊的條數
public MyGraphyL(int n){
//初始化鄰接表
vertexs=new ArrayList<Vertex>(n);
numOfEdges=0;
}
public List<Vertex> getVertexs() {
return vertexs;
}
//得到節點個數
public int getNumOfVertex(){
return vertexs.size();
}
//得到邊的個數
public int getNumOfEdges(){
return numOfEdges;
}
//返回節點i的值
public Object get(int i){
Vertex vertex = vertexs.get(i);
if(vertex!=null){
return vertex.value;
}
return null;
}
//返回v1,v2節點之間邊的權值
public int getWeight(int v1,int v2){
Node search = search(v1, v2);
if(search!=null) return search.weight;
else return -1;
}
//搜索邊節點
public Node search(int v1,int v2){
Iterator<Node> it = vertexs.get(v1).elist.iterator();
while(it.hasNext()){
Node node = (Node) it.next();
if(node.evalue==v2){
return node;
}
}
return null;
}
//插入節點
public void insertVertext(Object value,int vvalue){
Vertex vertex=new Vertex(value,vvalue);
vertexs.add(vertex);
}
//插入邊節點
public void insertEdge(int v1,int v2,int weight){
LinkedList<Node> elist = vertexs.get(v1).elist;
Node node = new Node(v2, weight);
elist.add(node);
numOfEdges++;
}
//刪除節點
public void deleteVertex(int vvalue){
vertexs.remove(vvalue);
}
//刪除邊節點
public void deleteEdge(int v1,int v2){
LinkedList<Node> elist = vertexs.get(v1).elist;
Node search = search(v1, v2);
if(search!=null) elist.remove(search);
numOfEdges--;
}
//得到節點的第一個邊節點的下標
public int getFirstneighbor(int i){
LinkedList<Node> elist = vertexs.get(i).elist;
if(elist.size()==0) return -1;
Node node = elist.get(0);
if(node!=null)
return node.evalue;
return -1;
}
//根據前一個邊節點的下標來取得下一個邊節點
public int getNextNeighbor(int v1,int v2){
LinkedList<Node> elist = vertexs.get(v1).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
Node next = iterator.next();
if (next.evalue==v2) {
try {
return iterator.next().evalue;
} catch (Exception e) {
System.out.println("沒有下一個");
}
}
}
return -1;
}
//測試程序
public static void main(String args[]) {
int i=0;
int n=4,e=4;//分別代表結點個數和邊的數目
String labels[]={"V1","V2","V3","V4"};//結點的標識
MyGraphyL graph=new MyGraphyL(n);
for(String label:labels) {
graph.insertVertext(label, i);//插入結點
i++;
}
//插入四條邊
graph.insertEdge(0, 1, 2);
graph.insertEdge(0, 2, 5);
graph.insertEdge(2, 3, 8);
graph.insertEdge(3, 0, 7);
System.out.println("結點個數是:"+graph.getNumOfVertex());
System.out.println("邊的個數是:"+graph.getNumOfEdges());
// graph.deleteEdge(0, 1);//刪除<V1,V2>邊
// System.out.println("刪除<V1,V2>邊后...");
// System.out.println("結點個數是:"+graph.getNumOfVertex());
// System.out.println("邊的個數是:"+graph.getNumOfEdges());
System.out.println(graph.getFirstneighbor(0));
System.out.println(graph.getNextNeighbor(0, 1));
System.out.println("---------------------");
List<Vertex> vlist = graph.vertexs;
for(Vertex v:vlist){
System.out.println(v.value+" "+v.vvalue+":");
LinkedList<Node> elist = v.elist;
for (Node node : elist) {
System.out.println(" "+node.evalue+"--"+node.weight);
}
}
}
}
深度優先遍歷與廣度優先遍歷
-
深度優先遍歷
所謂深度優先遍歷,就是首先訪問第一個鄰接結 點,然后再以這個被訪問的鄰接結點作為初始結點,訪問它 的第一個鄰接結點。總結起來可以這樣說:每次都在訪問完當前結點后首先訪問當前結點的第一個鄰接結點。
如下圖,深度優先遍歷的順序為1->2->4->8->5->3->6->7
我們先用鄰接矩陣的形式實現上圖的深度優先遍歷,圖結構采用上面自己寫的
package com.fredal.structure;
public class DeepTravel {
//深度優先遍歷 參數為要遍歷的圖,訪問過的標記以及從哪開始遍歷
public static void deepTravel(MyGraphy a,boolean [] visited,int k){
int[][] edges = a.getEdges();
visited[k]=true;
System.out.println(a.get(k));
for(int i=0;i<edges[k].length;i++){
if(edges[k][i]==1&&visited[i]==false) deepTravel(a, visited, i);
}
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7","8"};//結點的標識
MyGraphy a=new MyGraphy(8);
for(String label:labels){
a.insertVertext(label);
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 2, 1);
a.insertEdge(1, 3, 1);
a.insertEdge(1, 4, 1);
a.insertEdge(3, 7, 1);
a.insertEdge(4, 7, 1);
a.insertEdge(2, 5, 1);
a.insertEdge(2, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(1, 0, 1);
a.insertEdge(2, 0, 1);
a.insertEdge(3, 1, 1);
a.insertEdge(4, 1, 1);
a.insertEdge(7, 3, 1);
a.insertEdge(7, 4, 1);
a.insertEdge(4, 2, 1);
a.insertEdge(5, 2, 1);
a.insertEdge(6, 5, 1);
boolean [] visited=new boolean[a.getNumOfVertex()];
deepTravel(a, visited, 0);
}
}
用鄰接表實現深度優先遍歷并沒有多大區別:
public class DeepTravel {
public static void deepTravelL(MyGraphyL a,boolean [] visited,int k){
List<Vertex> vertexs = a.getVertexs();
visited[k]=true;
System.out.println(a.get(k));
LinkedList<Node> elist = vertexs.get(k).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
Node node = iterator.next();
if(visited[node.getEvalue()]==false) deepTravelL(a, visited, node.getEvalue());
}
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7","8"};//結點的標識
MyGraphyL a=new MyGraphyL(8);
int i=0;
for(String label:labels) {
a.insertVertext(label, i);//插入結點
i++;
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 2, 1);
a.insertEdge(1, 3, 1);
a.insertEdge(1, 4, 1);
a.insertEdge(3, 7, 1);
a.insertEdge(4, 7, 1);
a.insertEdge(2, 5, 1);
a.insertEdge(2, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(1, 0, 1);
a.insertEdge(2, 0, 1);
a.insertEdge(3, 1, 1);
a.insertEdge(4, 1, 1);
a.insertEdge(7, 3, 1);
a.insertEdge(7, 4, 1);
a.insertEdge(4, 2, 1);
a.insertEdge(5, 2, 1);
a.insertEdge(6, 5, 1);
boolean [] visited=new boolean[a.getNumOfVertex()];
deepTravelL(a, visited, 0);
}
}
-
廣度優先遍歷
廣度優先遍歷(BFS)從根節點開始,沿著樹的寬度遍歷樹的節點。如果所有節點均被訪問,則算法中止。
上圖按廣度優先遍歷的順序應為:1->2->3->4->5->6->7->8
同樣采用上面的圖先使用鄰接矩陣進行模擬
package com.fredal.structure;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class WidthTravel {
public static void widthTravel(MyGraphy a,Set visited,List layer){
int[][] edges = a.getEdges();
//遍歷當前層
for(int i=0;i<layer.size();i++){
System.out.println(a.get((Integer) layer.get(i)));
visited.add(layer.get(i));
}
//下一層
List next_layer=new ArrayList();
for(int i=0;i<layer.size();i++){
int node=(Integer) layer.get(i);
for(int j=0;j<edges[node].length;j++){
if(edges[node][j]==1&&next_layer.indexOf(j)<0) next_layer.add(j);
}
}
next_layer.removeAll(visited);
if(next_layer.isEmpty()==false) widthTravel(a, visited, next_layer);
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7","8"};//結點的標識
MyGraphy a=new MyGraphy(8);
for(String label:labels){
a.insertVertext(label);
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 2, 1);
a.insertEdge(1, 3, 1);
a.insertEdge(1, 4, 1);
a.insertEdge(3, 7, 1);
a.insertEdge(4, 7, 1);
a.insertEdge(2, 5, 1);
a.insertEdge(2, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(1, 0, 1);
a.insertEdge(2, 0, 1);
a.insertEdge(3, 1, 1);
a.insertEdge(4, 1, 1);
a.insertEdge(7, 3, 1);
a.insertEdge(7, 4, 1);
a.insertEdge(4, 2, 1);
a.insertEdge(5, 2, 1);
a.insertEdge(6, 5, 1);
Set visited=new HashSet();
List layer=new ArrayList();
layer.add(0);
widthTravel(a, visited, layer);
}
}
用鄰接表實現廣度遍歷基本沒區別
public class WidthTravel {
public static void widthTravelL(MyGraphyL a,Set visited,List layer){
List<Vertex> vertexs = a.getVertexs();
//遍歷當前層
for(int i=0;i<layer.size();i++){
System.out.println(a.get((Integer) layer.get(i)));
visited.add(layer.get(i));
}
//下一層
List next_layer=new ArrayList();
for(int i=0;i<layer.size();i++){
int node=(Integer) layer.get(i);
LinkedList<Node> elist = vertexs.get(node).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
Node next = iterator.next();
if(next_layer.indexOf(next.getEvalue())<0) next_layer.add(next.getEvalue());
}
}
next_layer.removeAll(visited);
if(next_layer.isEmpty()==false) widthTravelL(a, visited, next_layer);
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7","8"};//結點的標識
MyGraphyL a=new MyGraphyL(8);
int i=0;
for(String label:labels) {
a.insertVertext(label, i);//插入結點
i++;
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 2, 1);
a.insertEdge(1, 3, 1);
a.insertEdge(1, 4, 1);
a.insertEdge(3, 7, 1);
a.insertEdge(4, 7, 1);
a.insertEdge(2, 5, 1);
a.insertEdge(2, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(1, 0, 1);
a.insertEdge(2, 0, 1);
a.insertEdge(3, 1, 1);
a.insertEdge(4, 1, 1);
a.insertEdge(7, 3, 1);
a.insertEdge(7, 4, 1);
a.insertEdge(4, 2, 1);
a.insertEdge(5, 2, 1);
a.insertEdge(6, 5, 1);
Set visited=new HashSet();
List layer=new ArrayList();
layer.add(0);
widthTravelL(a, visited, layer);
}
}
拓撲排序(kahn&DFS)
關于拓撲排序,是一種對于關聯性的排序,可以參考維基百科的解釋:
在圖論中,由一個有向無環圖的頂點組成的序列,當且僅當滿足下列條件時,稱為該圖的一個拓撲排序(Topological sorting)。
a. 每個頂點出現且只出現一次;
b. 若A在序列中排在B的前面,則在圖中不存在從B到A的路徑。
也可以定義為:拓撲排序是對有向無環圖的頂點的一種排序,它使得如果存在一條從頂點A到頂點B的路徑,那么在排序中B出現在A的后面
我們根據下圖進行排序
首先來最簡單的和自然的kahn算法,得出的結果應該是
1->5->2->4->6->7->3
,基于鄰接表
package com.fredal.structure;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import com.fredal.structure.MyGraphyL.Node;
import com.sun.xml.internal.messaging.saaj.packaging.mime.util.QEncoderStream;
public class TopSort {
public static void topSort(MyGraphyL a){
int[] ins;//入度數組
int [] tops;//拓撲排序的結果數組
Queue<Integer> queue;//輔助隊列
int num=a.getNumOfVertex();//頂點個數
int index=0;
ins=new int[num];
tops=new int[num];
queue=new LinkedList<Integer>();
//統計每個頂點的入度數
for(int i=0;i<num;i++){
LinkedList<Node> elist = a.getVertexs().get(i).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
ins[iterator.next().getEvalue()]++;
}
}
//將入度為0的頂點入隊列
for(int i=0;i<num;i++){
if(ins[i]==0) queue.offer(i);//入隊列
}
while(!queue.isEmpty()){
int j=queue.poll().intValue();//出隊列 j是頂點序號 tops[index++]=Integer.parseInt((String) a.get(j));
LinkedList<Node> elist = a.getVertexs().get(j).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
Node node = iterator.next();
ins[node.getEvalue()]--;//將入度減一
if(ins[node.getEvalue()]==0) queue.offer(node.getEvalue());
}
}
if(index!=num){
System.out.println("有個環");
return;
}
//輸出排序結果
System.out.print("拓撲排序: ");
for(int i=0;i<num;i++){
System.out.print(tops[i]+" ");
}
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7"};//結點的標識
MyGraphyL a=new MyGraphyL(8);
int i=0;
for(String label:labels) {
a.insertVertext(label, i);//插入結點
i++;
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 3, 1);
a.insertEdge(0, 5, 1);
a.insertEdge(1, 6, 1);
a.insertEdge(3, 2, 1);
a.insertEdge(4, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(6, 2, 1);
topSort(a);
}
}
除了上面那種算法我們還可以基于深度優先遍歷(DFS)進行拓撲排序,同樣基于鄰接表,首先保證圖是有向無環圖
package com.fredal.structure;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import com.fredal.structure.MyGraphyL.Node;
import com.fredal.structure.MyGraphyL.Vertex;
public class TopSort {
static boolean [] visited;
private static Stack<Integer> stack=new Stack<Integer>();
public static void topSortDFS(MyGraphyL a){
int num=a.getNumOfVertex();
visited=new boolean[num];
for(int i=0;i<num;i++){//循環調用DFS
if(!visited[i]) DFS(a, i);
}
}
private static void DFS(MyGraphyL a,int k){
visited[k]=true;
List<Vertex> vertexs = a.getVertexs();
LinkedList<Node> elist = vertexs.get(k).elist;
Iterator<Node> iterator = elist.iterator();
while(iterator.hasNext()){
Node node = iterator.next();
if(!visited[node.getEvalue()]) {
DFS(a, node.getEvalue());
}
}
stack.push(Integer.parseInt((String) a.get(k)));//在dfs方法快結束時壓入棧中
}
public static void main(String[] args) {
String labels[]={"1","2","3","4","5","6","7"};//結點的標識
MyGraphyL a=new MyGraphyL(8);
int i=0;
for(String label:labels) {
a.insertVertext(label, i);//插入結點
i++;
}
a.insertEdge(0, 1, 1);
a.insertEdge(0, 3, 1);
a.insertEdge(0, 5, 1);
a.insertEdge(1, 6, 1);
a.insertEdge(3, 2, 1);
a.insertEdge(4, 6, 1);
a.insertEdge(5, 6, 1);
a.insertEdge(6, 2, 1);
topSortDFS(a);
while(!stack.isEmpty()){
System.out.println(stack.pop());
}
}
}
這種算法得出得到排序結果為5->1->6->4->2->7->3
,與上面的有些區別,但是仍然是對的排序結果.
這有關到解的唯一性問題,有哈密頓路徑的圖,即每一對頂點都能確定向后關系的圖的解是唯一的.
至于如何確定有唯一解呢,最簡單的想法是把每一對頂點兩兩比較看看有無先后關系,但是這樣很低效,我們可以用類似插入排序的算法進行判斷.詳細的以后再講.
最短路徑問題(Dijkstra&Floyd)
最短路徑問題,先講單源最短路徑,就是求從指定頂點出發到其他各點的最短距離.經典的算法有(Dijkstra)算法.
我們根據下圖來模擬該算法:
public class Dijstra {
public static void dijstra(MyGraphyL a,int k){
Map map=new HashMap();//存儲節點號--最小路徑值
List<Vertex> vertexs = a.getVertexs();
while(true){
int min=Integer.MAX_VALUE;
int min_no=-1;
Vertex first = vertexs.get(k);//獲得初始節點
Iterator<Node> iterator = first.elist.iterator();
while(iterator.hasNext()){
Node node = iterator.next();
//選出路徑最短的節點
if(map.get(node.getEvalue())==null&&node.getWeight()<min){
min_no=node.getEvalue();
min=node.getWeight();
}
}
Iterator it = map.keySet().iterator();
while(it.hasNext()){
//取出集合中存在的最短路徑節點
int index=(Integer) it.next();
int weight=(Integer) map.get(index);
Iterator<Node> iterator2 = vertexs.get(index).elist.iterator();
while(iterator2.hasNext()){
Node node2 = iterator2.next();
//將集合中的節點自身的權值加上下一個節點的權值與外面非集合中的權值比較 取最小值 循環
if(map.get(node2.getEvalue())==null&&node2.getWeight()+weight<min){
min_no=node2.getEvalue();
min=node2.getWeight()+weight;
}
}
}
//出口
if(min<Integer.MAX_VALUE)
map.put(min_no, min);
else
break;
}
System.out.println(map);
}
public static void main(String[] args) {
String labels[]={"0","1","2","3","4"};//結點的標識
MyGraphyL a=new MyGraphyL(5);
int i=0;
for(String label:labels) {
a.insertVertext(label, i);//插入結點
i++;
}
a.insertEdge(0, 1, 10);
a.insertEdge(0, 3, 30);
a.insertEdge(0, 4, 100);
a.insertEdge(1, 2, 50);
a.insertEdge(2, 4, 10);
a.insertEdge(3, 2, 20);
a.insertEdge(3, 4, 60);
dijstra(a, 0);
}
}
模擬結果為:
從0出發到1的最短路徑為:0-->1
從0出發到2的最短路徑為:0-->3-->2
從0出發到3的最短路徑為:0-->3
從0出發到4的最短路徑為:0-->3-->2-->4
從0出 發到1的最短距離為:10
從0出 發到2的最短距離為:50
從0出 發到3的最短距離為:30
從0出 發到4的最短距離為:60
對于最短路徑問題,還有著名的算法,Floyd算法,比起單源,它對于任意兩點間的最短路徑問題計算更方便.
通過Floyd計算圖G=(V,E)中各個頂點的最短路徑時,需要引入一個矩陣S,矩陣S中的元素a[i][j]表示頂點i(第i個頂點)到頂點j(第j個頂點)的距離。
基本思想:
假設圖G中頂點個數為N,則需要對矩陣S進行N次更新。初始時,矩陣S中頂點a[i][j]的距離為頂點i到頂點j的權值;如果i和j不相鄰,則a[i][j]=∞。 接下來開始,對矩陣S進行N次更新。第1次更新時,如果"a[i][j]的距離" > "a[i][0]+a[0][j]"(a[i][0]+a[0][j]表示"i與j之間經過第1個頂點的距離"),則更新a[i][j]為"a[i][0]+a[0][j]"。 同理,第k次更新時,如果"a[i][j]的距離" > "a[i][k]+a[k][j]",則更新a[i][j]為"a[i][k]+a[k][j]"。更新N次之后,操作完成!
我們還是用代碼模擬更易于理解,同樣采用上面那張圖,基于鄰接矩陣:
package com.fredal.structure;
import java.util.ArrayList;
import java.util.List;
public class Floyd {
// path[i][j]=k表示 頂點i到頂點j的最短路徑經過k
// dis[i][j]=s表示 頂點i到頂點j的最短路徑長度為s
public static void floyd(MyGraphy a) {
int num = a.getNumOfVertex();
int[][] edges = a.getEdges();
int INF = Integer.MAX_VALUE;
int[][] dis = new int[num][num];
List[][] path = new List[num][num];//用list來存儲具體的路徑
// 初始化
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
dis[i][j] = ((edges[i][j] == 0) ? INF : edges[i][j]);//用inf表示沒有通路
if (path[i][j] == null) {
path[i][j] = new ArrayList();
path[i][j].add(j);
}
}
}
// 計算最短路徑
for (int k = 0; k < num; k++) {
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
// 如果經過下標為k的頂點路徑比原來兩點間的路徑更短,則更新dis[i][j]和path[i][j]
int tmp = (dis[i][k] == INF || dis[k][j] == INF) ? INF
: (dis[i][k] + dis[k][j]);
if (dis[i][j] > tmp) {
dis[i][j] = tmp;//更新路徑長度
//更新路徑 用list存儲
List list = new ArrayList();
list.addAll(path[i][k]);
list.addAll(path[k][j]);
path[i][j]=list;
}
}
}
}
//輸出路徑長度和具體的最短路徑
System.out.println("floyd:");
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
System.out.print((dis[i][j]==INF?0:dis[i][j]) + " ");//INF太難看了 我還是換回0表示沒有通路
}
System.out.println();
}
System.out.println("path:");
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
System.out.print(path[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
String labels[] = { "0", "1", "2", "3", "4" };// 結點的標識
MyGraphy a = new MyGraphy(5);
for (String label : labels) {
a.insertVertext(label);// 插入結點
}
a.insertEdge(0, 1, 10);
a.insertEdge(0, 3, 30);
a.insertEdge(0, 4, 100);
a.insertEdge(1, 2, 50);
a.insertEdge(2, 4, 10);
a.insertEdge(3, 2, 20);
a.insertEdge(3, 4, 60);
floyd(a);
}
}
運行結果為:
最小生成樹(Prim&Kruskal)
最小生成樹問題,在含有n個頂點的連通圖中選擇n-1條邊,構成一棵極小連通子圖,并使該連通子圖中n-1條邊上權值之和達到最小,則稱其為連通網的最小生成樹。
普里姆(Prim)算法,是用來求加權連通圖的最小生成樹的算法。
基本思想為從一個起點開始,選取連接這個點的所有邊中權值最小的那條邊,然后把邊的終點當成新的起點,繼續選取權值最小的邊,直到把所有頂點都選了一遍..
對于上圖而言,采用Prim算法生成最小生成樹的步驟為:
初始狀態:V是所有頂點的集合,即V={A,B,C,D,E,F,G};U和T都是空!
第1步:將頂點A加入到U中。
此時,U={A}。
第2步:將頂點B加入到U中。
上一步操作之后,U={A}, V-U={B,C,D,E,F,G};因此,邊(A,B)的權值最小。將頂點B添加到U中;此時,U={A,B}。
第3步:將頂點F加入到U中。
上一步操作之后,U={A,B}, V-U={C,D,E,F,G};因此,邊(B,F)的權值最小。將頂點F添加到U中;此時,U={A,B,F}。
第4步:將頂點E加入到U中。
上一步操作之后,U={A,B,F}, V-U={C,D,E,G};因此,邊(F,E)的權值最小。將頂點E添加到U中;此時,U={A,B,F,E}。
第5步:將頂點D加入到U中。
上一步操作之后,U={A,B,F,E}, V-U={C,D,G};因此,邊(E,D)的權值最小。將頂點D添加到U中;此時,U={A,B,F,E,D}。
第6步:將頂點C加入到U中。
上一步操作之后,U={A,B,F,E,D}, V-U={C,G};因此,邊(D,C)的權值最小。將頂點C添加到U中;此時,U={A,B,F,E,D,C}。
第7步:將頂點G加入到U中。
上一步操作之后,U={A,B,F,E,D,C}, V-U={G};因此,邊(G,E)的權值最小。將頂點G添加到U中;此時,U=V。
此時,最小生成樹構造完成!它包括的頂點依次是:A B F E D C G,權值為36。
我們采用代碼模擬該步驟,基于鄰接矩陣:
首先我們要在之前寫的MyGraohy類中加一個方法
//返回頂點在的位置
public int getPosition(Object v){
return vList.indexOf(v);
}
開始模擬Prim算法
package com.fredal.structure;
public class Prim {
public static void prim(MyGraphy a,int start){
int num=a.getNumOfVertex();//頂點個數
int[][] edges = a.getEdges();
int index=0;//數組的索引
String[] prims=new String[num];//結果數組
int[] weight=new int[num];//頂點邊的權值
int INF=Integer.MAX_VALUE;
//把0變成INF
for(int i=0;i<num;i++){
for(int j=0;j<num;j++){
edges[i][j]=(edges[i][j]==0?INF:edges[i][j]);
}
}
//prim樹中第一個數是"圖中第start個頂點"
prims[index++]= (String) a.get(start);
//將每個頂點的權值初始化為"第start個頂點"到"該定點"的權值
for(int i=0;i<num;i++)
weight[i]=edges[start][i];
//到自身的距離為0
weight[start]=0;
for(int i=0;i<num;i++){
if(start==i) continue;
int j=0;
int k=0;
int min=Integer.MAX_VALUE;
while(j<num){
//找到權值最小的頂點
if(weight[j]!=0&&weight[j]<min){
min =weight[j];
k=j;
}
j++;
}
//經過上面的處理 權值最小的頂點是第k個頂點,將第k個頂點加入到最小生成樹的結果數組中
prims[index++]=(String) a.get(k);
//將第k個節點的權值標記為0 意味著第k個頂點已經排序了
weight[k]=0;
//更新下一輪節點的權值
for(j=0;j<num;j++){
if(weight[j]!=0&&edges[k][j]<weight[j]){
weight[j]=edges[k][j];
}
}
}
//計算最小生成樹的權值
int sum=0;
for(int i=1;i<index;i++){
int min=Integer.MAX_VALUE;
int n=a.getPosition(prims[i]);
for(int j=0;j<i;j++){
int m=a.getPosition(prims[j]);
if(edges[m][n]<min) min=edges[m][n];
}
sum+=min;
}
//打印最小生成樹
System.out.println("prim:");
System.out.println("sum:"+sum);
System.out.println("path:");
for(int i=0;i<index;i++){
System.out.print(prims[i]+" ");
}
}
public static void main(String[] args) {
String labels[]={"A","B","C","D","E","F","G"};//結點的標識
MyGraphy g=new MyGraphy(7);
for(String label:labels) {
g.insertVertext(label);//插入結點
}
g.insertEdge(0, 1, 12);
g.insertEdge(0, 5, 16);
g.insertEdge(0, 6, 14);
g.insertEdge(1, 0, 12);
g.insertEdge(1, 2, 10);
g.insertEdge(1, 5, 7);
g.insertEdge(2, 1, 10);
g.insertEdge(2, 3, 3);
g.insertEdge(2, 4, 5);
g.insertEdge(2, 5, 6);
g.insertEdge(3, 2, 3);
g.insertEdge(3, 4, 4);
g.insertEdge(4, 2, 5);
g.insertEdge(4, 3, 4);
g.insertEdge(4, 5, 2);
g.insertEdge(4, 6, 8);
g.insertEdge(5, 0, 16);
g.insertEdge(5, 1, 7);
g.insertEdge(5, 2, 6);
g.insertEdge(5, 4, 2);
g.insertEdge(5, 6, 9);
g.insertEdge(6, 0, 14);
g.insertEdge(6, 4, 8);
g.insertEdge(6, 5, 9);
prim(g, 0);
}
}
除了Prim算法外,還有一種思路迥異的kruskal算法,基本思想是按照權值從小到大排列所有的邊,依次選取并且保證不構成回路.
同樣根據上面的圖作為基礎,步驟為:
第1步:將邊(E,F)加入R中。
邊(E,F)的權值最小,因此將它加入到最小生成樹結果R中。
第2步:將邊(C,D)加入R中。
上一步操作之后,邊(C,D)的權值最小,因此將它加入到最小生成樹結果R中。
第3步:將邊(D,E)加入R中。
上一步操作之后,邊(D,E)的權值最小,因此將它加入到最小生成樹結果R中。
第4步:將邊(B,F)加入R中。
上一步操作之后,邊(C,E)的權值最小,但(C,E)會和已有的邊構成回路;因此,跳過邊(C,E)。同理,跳過邊(C,F)。將邊(B,F)加入到最小生成樹結果R中。
第5步:將邊(E,G)加入R中。
上一步操作之后,邊(E,G)的權值最小,因此將它加入到最小生成樹結果R中。
第6步:將邊(A,B)加入R中。
上一步操作之后,邊(F,G)的權值最小,但(F,G)會和已有的邊構成回路;因此,跳過邊(F,G)。同理,跳過邊(B,C)。將邊(A,B)加入到最小生成樹結果R中。
此時,最小生成樹構造完成!它包括的邊依次是:(E,F) (C,D) (D,E) (B,F) (E,G) (A,B)。
我們還是選擇基于鄰接表實現該算法
首先為了方便,我們還往MyGraph類中增加一個內部類和三個方法,方便邊的排序
class Edge{
Object from;
Object to;
int weight;
public Edge(Object from, Object to, int weight) {
super();
this.from = from;
this.to = to;
this.weight = weight;
}
}
//得到圖中的邊
public Edge[] getEgdes(){
int index=0;
int num=getNumOfVertex();
Edge[] edgel=new Edge[numOfEdges/2];
for(int i=0;i<num;i++){
for(int j=i+1;j<num;j++){
if(edges[i][j]!=0){
edgel[index++]=new Edge(vList.get(i), vList.get(j), edges[i][j]);
}
}
}
return edgel;
}
//按照權值從小到大排列
public void sortEdges(Edge[] edgel,int elen){
for(int i=0;i<elen;i++){
for(int j=i+1;j<elen;j++){
if(edgel[i].weight>edgel[j].weight){
//交換
Edge temp=edgel[i];
edgel[i]=edgel[j];
edgel[j]=temp;
}
}
}
}
//獲取節點i的通路的終點
public int getEnd(int[] ends,int i){
while(ends[i]!=0) i=ends[i];
return i;
}
該算法實現很簡單,主要在于如何判斷環路是否存在
package com.fredal.structure;
import com.fredal.structure.MyGraphy.Edge;
public class Kruskal {
public static void kruskal(MyGraphy a){
int index=0;
int num=a.getNumOfEdges()/2;
int[] ends=new int[num];
Edge[] res=new Edge[num];//結果數組
Edge[] edges=a.getEgdes();//圖對應的所有邊
a.sortEdges(edges, num);
for(int i=0;i<num;i++){
int p1=a.getPosition(edges[i].from);//第i條邊的起點
int p2=a.getPosition(edges[i].to);//第i條邊的終點
int m=a.getEnd(ends, p1);
int n=a.getEnd(ends, p2);
if(m!=n){//意味著通路的終點不一樣,即沒有形成環路
ends[m]=n;//設置m的通路的終點為n
res[index++]=edges[i];//保存結果
}
}
//打印最小生成樹
int sum=0;
for(int i=0;i<index;i++){
sum+=res[i].weight;
}
System.out.println("kruskal:");
System.out.println("weight:"+sum);
System.out.println("path:");
for(int i=0;i<index;i++){
System.out.println(res[i].from+"-"+res[i].to);
}
}
public static void main(String[] args) {
String labels[]={"A","B","C","D","E","F","G"};//結點的標識
MyGraphy g=new MyGraphy(7);
for(String label:labels) {
g.insertVertext(label);//插入結點
}
g.insertEdge(0, 1, 12);
g.insertEdge(0, 5, 16);
g.insertEdge(0, 6, 14);
g.insertEdge(1, 0, 12);
g.insertEdge(1, 2, 10);
g.insertEdge(1, 5, 7);
g.insertEdge(2, 1, 10);
g.insertEdge(2, 3, 3);
g.insertEdge(2, 4, 5);
g.insertEdge(2, 5, 6);
g.insertEdge(3, 2, 3);
g.insertEdge(3, 4, 4);
g.insertEdge(4, 2, 5);
g.insertEdge(4, 3, 4);
g.insertEdge(4, 5, 2);
g.insertEdge(4, 6, 8);
g.insertEdge(5, 0, 16);
g.insertEdge(5, 1, 7);
g.insertEdge(5, 2, 6);
g.insertEdge(5, 4, 2);
g.insertEdge(5, 6, 9);
g.insertEdge(6, 0, 14);
g.insertEdge(6, 4, 8);
g.insertEdge(6, 5, 9);
kruskal(g);
}
}
更多文章與相關下載請看擴展閱讀