JAVA——圖的遍歷

1.圖遍歷的定義

從圖中某個頂點出發訪問遍圖中的所有頂點,並且每個頂點僅僅被訪問一次。
其中圖的遍歷分爲兩種,一種是圖的深度優先遍歷算法,一種是圖的廣度優先遍歷算法。

2.連通圖的深度優先遍歷算法(DFS)和廣度優先遍歷算法(BFS)

圖的深度遍歷算法步驟:
(1)首先選定一個未被訪問過的頂點V作爲起始頂點(或者訪問指定的起始頂點V),並將其標記爲已訪問過;

(2)然後搜索與頂點V鄰接的所有頂點,判斷這些頂點是否被訪問過,如果有未被訪問過的頂點,則任選一個頂點W進行訪問;再選取與頂點W鄰接的未被訪問過的任一個頂點並進行訪問,依次重複進行。當一個頂點的所有的鄰接頂點都被訪問過時,則依次回退到最近被訪問的頂點。若該頂點還有其他鄰接頂點未被訪問,則從這些未被訪問的頂點中取出一個並重覆上述過程,直到與起始頂點V相通的所有頂點都被訪問過爲止。

(3)若此時圖中依然有頂點未被訪問,則再選取其中一個頂點作爲起始頂點並訪問之,轉(2)。反之,則遍歷結束。
圖的廣度遍歷算法步驟:
圖的廣度優先遍歷有點像樹的層次遍歷,首先把節點的相鄰的節點全部存入隊列。然後再出隊,尋找該節點的相鄰節點。
【實現代碼】

package Graph;

import java.util.LinkedList;
import java.util.Queue;

//創建圖類
class Graph_1{
	final static int INF=100000;
	final int max=100;
	//頂點座標
	int[] vexs=new int[max];
	//矩陣
	int[][] edges=new int[max][max];
	
	//創建圖的鄰接矩陣
	public void createGraph(Graph_1 graph,int[][] A,int[] vs ){
		vexs=vs;
		for(int i=0;i<A.length;i++){
			for(int j=0;j<A.length;j++){
				graph.edges[i][j]=A[i][j];
			}
		}
	}
	
	//輸出鄰接矩陣
	public void print_Graph(Graph_1 graph){
		for(int i=0;i<graph.vexs.length;i++){
			for(int j=0;j<graph.vexs.length;j++){
				if(graph.edges[i][j]==INF){
					System.out.printf("%4s", "/");
				}else{
					System.out.printf("%4d", graph.edges[i][j]);
				}
			}
			System.out.println("\n");
		}
	}
	
	//找到和v點相連的鄰接點
	public int getFirst(Graph_1 graph,int v){
		for(int i=0;i<graph.vexs.length;i++){
			if(graph.edges[v][i]==1){
				return i;
			}
		}
		return -1;
	}
	
	//若v的相鄰點k已經訪問過,則從下一個點開始遍歷
	public int getNext(Graph_1 graph,int v,int k) {
		for(int i=k+1;i<graph.vexs.length;i++) {
			if(graph.edges[v][i]==1) {
				return i;
			}
		}
		return -1;
	}
	
	
	//深度優先遍歷
	public void DFS(Graph_1 graph,int v,int[] visited){
		int next;
		System.out.println(v);
		//把已經遍歷的點設置爲1
		visited[v]=1;
		next=graph.getFirst(graph, v);
		while(next!=-1){
			//相鄰點沒有訪問過則繼續遍歷
			if(visited[next]==0){
				graph.DFS(graph, next, visited);
			}
			//假如訪問過則尋找下一相鄰點
			next=graph.getNext(graph, v, next);
		}
		
	}
	
	//廣度優先遍歷,類似於樹的層次遍歷
	public void BFS(Graph_1 graph,int v,int[] visited){
		Queue<Integer> queue=new LinkedList<>();
		int next;
		queue.add(v);
		visited[v]=1;
		while(!queue.isEmpty()){
			next=queue.remove();
			System.out.println(next);
			int vex=graph.getFirst(graph, next);
			while(vex!=-1){
				if(visited[vex]==0){
					queue.add(vex);
					visited[vex]=1;
				}
				vex=graph.getNext(graph, next, vex);
			}
		}
	}
}

public class depthSearch {

	private static final int INF = 100000;

	public static void main(String[] args){
		int[] vs={0,1,2,3,4};
		int[][] A= {
				{INF,1,INF,1,INF},
				{1,INF,1,INF,INF},
				{INF,1,INF,1,1},
				{1,INF,1,INF,1},
				{INF,INF,1,1,INF}
		};
		Graph_1 graph=new Graph_1();
		graph.createGraph(graph, A, vs);
		graph.print_Graph(graph);
		int[] visited=new int[100];
		int[] visited_1=new int[100];
		graph.DFS(graph, 0, visited);
		System.out.println("------------");
		graph.BFS(graph, 0, visited_1);
	}
	
}

3.最小生成樹(Prime算法)

Prime算法的核心步驟是:在帶權連通圖中V是包含所有頂點的集合, U已經在最小生成樹中的節點,從圖中任意某一頂點v開始,此時集合U={v},重複執行下述操作:在所有u∈U,w∈V-U的邊(u,w)∈E中找到一條權值最小的邊,將(u,w)這條邊加入到已找到邊的集合,並且將點w加入到集合U中,當U=V時,就找到了這顆最小生成樹。
其實,算法的核心步驟就是:在所有u∈U,w∈V-U的邊(u,w)∈E中找到一條權值最小的邊。
【實現代碼】

//prime最小生成樹
	//從start開始
	public void prim(int start){
		int num=vexs.length; //頂點個數
		int index=0; //prim最小樹的索引,即prims數組的索引
		int[] prims=new int[num]; //prim最小數的結果數組
		int[] weights=new int[num]; //頂點間的權重
		
		prims[index++]=vexs[start];
		
		//初始化頂點的權重數組
		for(int i=0;i<num;i++)
			weights[i]=edges[start][i];
		
		//第start個頂點的權值初始化爲0
		weights[start]=0;
		
		for(int i=0;i<num;i++){
			//從start開始,不需要在對第start個頂點進行處理
			if(start==i)
				continue;
			
			int j=0;
			int k=0;
			int min=INF;
			
			//從未被加入到最小生成樹的頂點中,找出權重最小的頂點
			while(j<num){
				//若weights[j]=0意味着第j個節點已經排序過,已經加入最小生成樹中
				if(weights[j]!=0&&weights[j]<min){
					min=weights[j];
					k=j;
				}
				j++;
			}
			
			//經過上面處理找到權重最小的頂點第k個頂點
			//將第k個頂點加入到最小深耕書的結果數組中
			prims[index++]=vexs[k];
			//已排序過設置爲0
			weights[k]=0;
			//當第k個頂點被加入到最小成樹的結果數組中之後,更新其它的權重
			for(j=0;j<num;j++){
				if(weights[j]!=0&&edges[k][j]<weights[j])
					weights[j]=edges[k][j];
			}
		}
		
		//就散最小生成樹的權值
		int sum=0;
		for(int i=1;i<index;i++){
			int min=INF;
			//獲取prims[i]在edges中的位置
			int n=getPosition(prims[i]);
			//在vexs中找到j的權重最小的頂點
			for(int j=0;j<i;j++){
				int m=getPosition(prims[j]);
				if(edges[m][n]<min){
					min=edges[m][n];
				}
			}
			sum+=min;
		}
		//打印最小生成樹

        System.out.printf("PRIM(%d)=%d: ", vexs[start], sum);
        for (int i = 0; i < index; i++)
            System.out.printf("%d ", prims[i]);
        System.out.printf("\n");
	}
4.迪傑斯特拉(求最短路徑)

1)算法思想:設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組爲已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,算法就結束了),第二組爲其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點爲中間頂點的當前最短路徑長度。

(1) 初始時,S只包含起點s;U包含除s外的其他頂點,且U中頂點的距離爲"起點s到該頂點的距離"[例如,U中頂點v的距離爲(s,v)的長度,然後s和v不相鄰,則v的距離爲∞]。

(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

(3) 更新U中各個頂點到起點s的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。

(4) 重複步驟(2)和(3),直到遍歷完所有頂點。
【實現代碼】

	//vs記錄頂點,pre[i]數組記錄從vs已經到i節點之前的點,也就是這個時候已經在S集合中的點
	//dist[i]是頂點vs到頂點i的最短路徑長度

	public void dijkstra(int vs,int[] prev,int[] dist){
		//flag[i]=true表示頂點vs到頂點i的最短路徑已獲取
		boolean[] flag=new boolean[vexs.length];
		
		//初始化
		for(int i=0;i<vexs.length;i++){
			flag[i]=false;
			prev[i]=0;
			dist[i]=edges[vs][i];
		}
		//對頂點vs自身進行初始化
		flag[vs]=true;
		dist[vs]=0;
		
		//遍歷vexs.length-1次,每次找出一個頂點的最短路徑
		int k=0;
		for(int i=1;i<vexs.length;i++){
			//尋找當前最小的路徑
			//即,在未獲取最短路徑的頂點中,找到離vs最近的頂點k
			int min=INF;
			for(int j=0;j<vexs.length;j++){
				if(flag[j]==false&&dist[j]<min){
					min=dist[j];
					k=j;
				}
			}
			//標記頂點k已獲取到最短路徑
			flag[k]=true;
			
			//修改當前最短路徑和前驅頂點
			//即,當已經“頂點k的最短路徑”之後,更新未獲取最短路徑的頂點的
			//最短路徑和前驅頂點
			for(int j=0;j<vexs.length;j++){
				int tmp=(edges[k][j]==INF?INF:(min+edges[k][j]));
				if(flag[j]==false&&(tmp<dist[j])){
					dist[j]=tmp;
					prev[j]=k;
				}
			}
		
		}
		//打印dijkstra最短路徑的結果
		System.out.print(vexs[vs]);
		for(int i=0;i<vexs.length;i++){
			 System.out.printf("  shortest(%d, %d)=%d\n", vexs[vs], vexs[i], dist[i]);
		}
	}
5.拓撲排序

【實現代碼】

package Graph;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
 
/**
 * 拓撲排序,當前方案並沒有在節點類中加入過多的內容
 * 但是在圖類中加入了邊的集合adjaNode
 */
public class ToposortSort {
 
	/**
	 * 拓撲排序節點類
	 */
	private static class Node {
		public Object val;
		public int pathIn = 0; // 入鏈路數量
		public Node(Object val) {
			this.val = val;
		}
	}
 
	/**
	 * 拓撲圖類
	 */
	private static class Graph {
		// 圖中節點的集合
		public Set<Node> vertexSet = new HashSet<Node>();
		// 相鄰的節點,紀錄邊
		public Map<Node, Set<Node>> adjaNode = new HashMap<Node, Set<Node>>();
 
		// 將節點加入圖中
		public boolean addNode(Node start, Node end) {
			if (!vertexSet.contains(start)) {
				vertexSet.add(start);
			}
			if (!vertexSet.contains(end)) {
				vertexSet.add(end);
			}
			if (adjaNode.containsKey(start)
					&& adjaNode.get(start).contains(end)) {
				return false;
			}
			if (adjaNode.containsKey(start)) {
				adjaNode.get(start).add(end);
			} else {
				Set<Node> temp = new HashSet<Node>();
				temp.add(end);
				adjaNode.put(start, temp);
			}
			end.pathIn++;
			return true;
		}
	}
 
	//Kahn算法
	private static class KahnTopo {
		private List<Node> result; // 用來存儲結果集
		private Queue<Node> setOfZeroIndegree; // 用來存儲入度爲0的頂點
		private Graph graph;
 
		//構造函數,初始化
		public KahnTopo(Graph di) {
			this.graph = di;
			this.result = new ArrayList<Node>();
			this.setOfZeroIndegree = new LinkedList<Node>();
			// 對入度爲0的集合進行初始化
			for(Node iterator : this.graph.vertexSet){
				if(iterator.pathIn == 0){
					this.setOfZeroIndegree.add(iterator);
				}
			}
		}
 
		//拓撲排序處理過程
		private void process() {
			while (!setOfZeroIndegree.isEmpty()) {
				Node v = setOfZeroIndegree.poll();
				
				// 將當前頂點添加到結果集中
				result.add(v);
				
				if(this.graph.adjaNode.keySet().isEmpty()){
					return;
				}
				
				// 遍歷由v引出的所有邊
				for (Node w : this.graph.adjaNode.get(v) ) {
					// 將該邊從圖中移除,通過減少邊的數量來表示
					w.pathIn--;
					if (0 == w.pathIn) // 如果入度爲0,那麼加入入度爲0的集合
					{
						setOfZeroIndegree.add(w);
					}
				}
				this.graph.vertexSet.remove(v);
				this.graph.adjaNode.remove(v);
			}
			
			// 如果此時圖中還存在邊,那麼說明圖中含有環路
			if (!this.graph.vertexSet.isEmpty()) {
				throw new IllegalArgumentException("Has Cycle !");
			}
		}
 
		//結果集
		public Iterable<Node> getResult() {
			return result;
		}
	}
	
	//測試
	public static void main(String[] args) {
		Node A = new Node("A");
		Node B = new Node("B");
		Node C = new Node("C");
		Node D = new Node("D");
		Node E = new Node("E");
		Node F = new Node("F");
		
		Graph graph = new Graph();
		graph.addNode(A, B);
		graph.addNode(B, C);
		graph.addNode(B, D);
		graph.addNode(D, C);
		graph.addNode(E, C);
		graph.addNode(C, F);
		
		KahnTopo topo = new KahnTopo(graph);
		topo.process();
		for(Node temp : topo.getResult()){
			System.out.print(temp.val.toString() + "-->");
		}
	}
	
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章