1. 程式人生 > >第四章圖演算法總結

第四章圖演算法總結

該部落格原地址http://blog.csdn.net/ntt5667781/article/details/52743342

其實就是對書上的內容做了個總結

在開始各類圖演算法之前,先將圖的結構進行分類。 
圖的表示,在實際實現過程中,有以下幾種基本的方式可以來表示圖。 
1) 鄰接矩陣:對於較小或者中等規模的圖的構造較為適用,因為需要V*V大小的空間。 
2) 邊的陣列:使用一個簡單的自定義edge類,還有兩個變數,分別代表邊的兩個端點編號,實現簡單,但是在求每個點的鄰接點的時候實現較為困難。 
3) 鄰接表陣列:較為常用,使用一個以頂點為索引的陣列,陣列每個元素都是和該頂點相鄰的頂點列表,這種陣列佔空間相對於鄰接矩陣少了很多,並且能很好的找到某個給定點的所有鄰接點。 

 
按照圖中邊的方向將圖分成有向圖和無向圖: 
1)無向圖:圖中的邊沒有方向。 
2)有向圖:圖中的邊有方向。 
對於有向圖和無向圖的具體實現表示可以使用前面介紹的三種方法,兩種圖在表示的時候大部分的實現程式碼都是一致的。 
普通無向圖的鄰接陣列表示方法的具體實現程式碼:

public class Graph {
    private int V;  //圖中的頂點數目
    private int E;  //圖中的邊數目
    private List<Integer>[] adj;  //鄰接陣列
    private int[][] a;   //鄰接矩陣
    public
CreatGraph(int V) { this.E = 0; this.V = V; adj = new ArrayList[V]; a=new int[V][V]; for (int i = 0; i < V; i++) adj[i] = new ArrayList<>(); } //由於無向圖中的邊是沒有方向的,所以新增邊的時候需要在邊的兩個頂點對應的鄰接列表中都新增頂點資訊。 public void addEdge(int v1, int v2) { a[v1][v2]=1
; a[v2][v1]=1; adj[v1].add(v2); adj[v2].add(v1); E++; } public int V() { return V; } public int E() { return E; } //鄰接陣列返回給定點的所有鄰接點 public List<Integer> adj(int i) { return adj[i]; } //鄰接矩陣返回給定點的所有鄰接點 public List<Integer> adj1(int i){ List<Integer> list=new ArrayList<>(); int[] adj1=new int[V]; adj1=a[i]; for(int v:adg1) if(v!+0)list.add(v); return list; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

無權有向圖的具體實現程式碼:

public class DirectedGraph {
    private int V;  //圖中的頂點數目
    private int E;  //圖中的邊數目
    private List<Integer>[] adj;  //鄰接陣列
    private int[][] a;   //鄰接矩陣

    public DirectedGraph(int V) {
        this.E = 0;
        this.V = V;
        adj = new ArrayList[V];
        a=new int[V][V];
        for (int i = 0; i < V; i++) 
            adj[i] = new ArrayList<>();
    }
    //由於無向圖中的邊是有方向的,所以新增邊的時候需要只需要在起始點的鄰接列表中新增頂點資訊。
    public void addEdge(int v1, int v2) { 
        a[v1][v2]=1;
        adj[v1].add(v2);
        E++;
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }
    //鄰接陣列返回給定點的所有鄰接點
    public List<Integer> adj(int i) {
        return adj[i];
    }
    //鄰接矩陣返回給定點的所有鄰接點
    public List<Integer> adj1(int i){
    List<Integer> list=new ArrayList<>();
    int[] adj1=new int[V];
    adj1=a[i];
    for(int v:adg1)
        if(v!+0)list.add(v);
    return list; 
    }   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

一 圖的遍歷演算法:

介紹兩種比較基礎的圖遍歷演算法,廣度優先搜尋和深度優先搜尋。 
1)深度優先搜尋:這是一種典型的遞迴演算法用來搜尋圖(遍歷所有的頂點); 
思想:從圖的某個頂點i開始,將頂點i標記為已訪問頂點,並將訪問頂點i的鄰接列表中沒有被標記的頂點j,將頂點j標記為已訪問,並在訪問頂點j的鄰接列表中未被標記的頂點k依次深度遍歷下去,直到某個點的所有鄰接列表中的點都被標記為已訪問後,返回上層。重複以上過程直到圖中的所有頂點都被標記為已訪問。 
深度優先遍歷和樹的先序訪問非常類似,儘可能深的去訪問節點。深度優先遍歷的大致過程(遞迴版本): 
a)在訪問一個節點的時候,將其設定為已訪問。 
b)遞迴的訪問被標記頂點的鄰接列表中沒有被標記的所有頂點 
(非遞迴版本): 
圖的非遞迴遍歷我們藉助棧來實現。 
a)如果棧為空,則退出程式,否則,訪問棧頂節點,但不彈出棧點節點。 
b)如果棧頂節點的所有直接鄰接點都已訪問過,則彈出棧頂節點,否則,將該棧頂節點的未訪問的其中一個鄰接點壓入棧,同時,標記該鄰接點為已訪問,繼續步驟a。 
該演算法訪問頂點的順序是和圖的表示有關的,而不只是和圖的結構或者是演算法有關。

深度優先探索是個簡單的遞迴演算法(當然藉助棧也可以實現非遞迴的版本),但是卻能有效的處理很多和圖有關的任務,比如: 
a) 連通性:ex:給定的兩個頂點是否聯通 or 這個圖有幾個聯通子圖。 
b) 單點路徑:給定一幅圖和一個固定的起點,尋找從s到達給定點的路徑是否存在,若存在,找出這條路徑。

尋找路徑: 
為了實現這個功能,需要在上面實現的深度優先搜尋中中增加例項變數edgeTo[],它相當於繩索的功能,這個陣列可以找到從每個與起始點聯通的頂點回到起始點的路徑(具體實現的思路非常巧妙: 從邊v-w第一次訪問w的時候,將edgeTo[w]的值跟新為v來記住這條道路,換句話說,v-w是從s到w的路徑上最後一條已知的邊,這樣搜尋結果就是一條以起始點為根結點的樹,也就是edgeTo[]是個有父連結表示的樹。)

深度優先搜尋的遞迴實現版本和非遞迴版本(遞迴是接住了遞迴中的隱藏棧來實現的。非遞迴,藉助棧實現)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DepthFirstSearch {
    //用來記錄頂點的標記狀態 true表示為已訪問,false表示為未被訪問。
    private boolean[] marked; 
    private int count;  
    //用來記錄頂點索引所對應的父結點,假設遍歷是從s到達的t那麼edgeTo[s所對的所用]=t;
    private int[] edgeTo; 
    //起始點
    private int s;  
    private Deque<Integer> dq=new Deque<>();

    public DepthFirstSearch(Graph G, int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s; 
        dq.push(s);
        dfs(G, s);
    }

    //遞迴形式實現
    public void dfs(Graph G, int s) { 
        marked[s] = true;
        count++;
        for (int temp : G.adj(s))
            if (!marked[temp]) {
                edgeTo[temp] = s;
                dfs(G, temp);
            }
    }
    //非遞迴形式實現
    private void dfs(Graph G){
    while(!dp.isEmpty()){
        s=dp.peek();
        needPop=true;
        marked[s] = true;
        for (int temp : G.adj(s))
            if (!marked[temp]) {
                dp.push(temp);
                edgeTo[temp] = s;
                needPop=false;
                break;
            }
        }
        if(needPop)
        dp.pop();
    }
    public boolean hasPathTo(int v) {
        return marked[v];
    }

    public List<Integer> pathTo(int v) {
        if (hasPathTo(v))
            return null;
        List<Integer> list = new ArrayList<>();
        v = edgeTo[v];
        while (v != s) {
            list.add(v);
            v = edgeTo[v];
        }
        list.add(s);
        Collections.reverse(list);
        return list;
    }

    public int count() {
        return count;
    }
    public static void main(String[] args){
        int V = 0,E = 0;
        Graph G=new Graph(V,E);
        int s=0;
         DepthFirstSearch dfs=new  DepthFirstSearch(G,s);
         for(int v=0;v<G.V();v++){
             if(dfs.hasPathTo(v))
                 for(int x:dfs.pathTo(v))
                     if(x==s)
                         System.out.print(x);
                     else
                         System.out.print("-"+x);
             System.out.println();
             }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

深度優先搜尋的實現軌跡(選取頂點0為起始頂點) 
已經使用DFS解決了一些問題,DFS其實還可以解決很多在無向圖中的基礎性問題,譬如: 
1)計算圖中的連通分支的數量;

public class ConnectComponent {
    private boolean[] marked;
    private int[] id;  //標記結點所在的連通分支編號
    private int count; //計算連通分支的個數

    public ConnectComponent(Graph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for (int s = 0; s < G.V(); s++) {
            if (!marked[s]) {
                dfs(G, s);
                count++;
            }
        }
    }

    public void dfs(Graph G, int s) {
        marked[s] = true;
        id[s] = count;
        for (int temp : G.adj(s))
            if (!marked[temp]) {
                dfs(G, temp);
            }
    }

    //判斷點v和w是否在一個連通分支中
    public boolean connected(int v, int w) {
        if (id[v] == id[w])
            return true;
        else
            return false;
    }

    public int id(int v) {
        return id[v];
    }

    public int count() {
        return count;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34