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() + "-->");
}
}
}