1. 程式人生 > >資料結構基礎(五)圖以及DFS、BFS

資料結構基礎(五)圖以及DFS、BFS

概念

定義

圖是一種較線性表和樹更為複雜的資料結構
相較於線性表的一對一(每個結點只有一個前驅後驅)和樹的一對多(層級結構,上層結點可以與多個下層結點相關),圖形結構的元素關係是多對多(即結點之間的關係可以是任意的)

圖可分為有向圖和無向圖

術語

連通圖:對於無向圖,如果任意兩個結點之間都是通的,則稱之為連通圖
連通分量:對於無向非連通圖,極大連通子圖稱為其連通分量
強連通圖:對於有向圖,任意兩個結點有路徑,則稱之為強連通圖
強連通分量:對於有向圖非連通圖,極大連通子圖稱為其強連通分量
連通圖的生成樹:該圖的極小連通子圖,它含有圖中全部n個頂點和只有足以構成一棵樹的n-1條邊,稱為圖的生成樹


例如上圖a中無向圖G3,圖b便為其三個連通分量


上圖為連通分量圖的生成樹

圖的儲存結構

圖的常用的儲存結構有:鄰接矩陣鄰接連結串列十字連結串列鄰接多重表邊表,其中鄰接矩陣和鄰接連結串列是較常用的表示方法。

鄰接矩陣

即用一維陣列放置其頂點,二維陣列放置頂點之間的關係
例如對於無向圖,A[i][j]=0表示i結點和j結點之間不連通,A[i][j]=1表示連通;對於有向圖,A[i][j]表示i結點和j結點之間的權值。
該二維陣列又稱為鄰接矩陣

鄰接連結串列

即用一個數組儲存所有頂點,而每個頂點有一個域指向一個單鏈表,該單鏈表儲存該頂點所有鄰接頂點及其相關資訊。


如上圖,頭結點(頂點)中data儲存該頂點相關資訊,firstarc儲存單鏈表第一個結點的位置;
表結點(連結串列結點)中adjvex儲存該領接頂點位置,nextarc儲存連結串列下一個結點位置,info儲存邊相關資訊(例如有向圖中的權值)
下圖為鄰接連結串列的圖解
20130708131719421.png

實現程式碼

給出手動實現的鄰接矩陣程式碼:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 弧
 */
class Arc{
    //弧連線的兩個頂點
    Object v1,v2;
    //圖:-1 無連線  >=0有連線
int Type; //弧的資訊 String info; public Arc() {} public Arc(Object v1, Object v2, int type, String info) { super(); this.v1 = v1; this.v2 = v2; Type = type; this.info = info; } @Override public String toString() { return v1+" "+v2+" "+Type+" "+info; } } /** * 領接矩陣圖 */ public class MatrixGraph { /**預設頂點最大個數*/ final static int defaultSize=10; /**頂點集合*/ List<Object> vertexs; /**邊的關係矩陣*/ Arc[][] arcs; /**最大頂點個數*/ int maxLen; /**弧的個數*/ int arcNum; /** * 預設建構函式 */ public MatrixGraph() { this.maxLen = defaultSize; this.vertexs = new ArrayList<Object>(maxLen); this.arcs = new Arc[maxLen][maxLen]; this.arcNum = 0; } /** * 帶參建構函式 * @param vers 頂點陣列 */ public MatrixGraph(Object[] vers) { this.maxLen=vers.length; this.vertexs=new ArrayList<Object>(Arrays.asList(vers)); this.arcs = new Arc[maxLen][maxLen]; this.arcNum = 0; } /** * 新增頂點 * @param v */ public void addVertex(Object v) { vertexs.add(v); if(vertexs.size()>maxLen) extendSize(); } /** * 擴充套件最大頂點個數和領接矩陣 */ public void extendSize() { maxLen*=2; arcs=Arrays.copyOf(arcs, maxLen); } /** * 新增一條弧 * @param a * @param b */ public void addArc(Object a,Object b) { addArc(a, b, 0); } /** * 新增一條帶權弧 * @param a * @param b * @param weight */ public void addArc(Object a,Object b,int weight) { int i=vertexs.indexOf(a); int j=vertexs.indexOf(b); if(i==-1||j==-1){ throw new ArrayIndexOutOfBoundsException("該頂點不存在"); } arcs[i][j]=new Arc(a, b, weight, ""); arcNum++; } /** * 刪除a到b的弧 * @param a * @param b */ public void removeArc(Object a,Object b) { int i=vertexs.indexOf(a); int j=vertexs.indexOf(b); if(i==-1||j==-1){ throw new ArrayIndexOutOfBoundsException("該頂點不存在"); } if(arcs[i][j]!=null){ arcs[i][j]=null; arcNum--; } else { System.out.println("該弧已經不存在"); } } /** * 刪除一個頂點 * @param a */ public void removeVertexs(Object a) { int i=vertexs.indexOf(a); if(i==-1) throw new ArrayIndexOutOfBoundsException("該頂點不存在"); vertexs.remove(i); //重新生成鄰接矩陣 int length=arcs.length; for (int j = 0; j < length-1; j++) { for (int z = 0; z < length-1; z++) { if(z>=i) arcs[j][z]=arcs[j][z+1]; } arcs[j][length-1]=null; if(j>=i) arcs[j]=arcs[j+1]; } arcs[length-1]=new Arc[length]; } /** * 清空圖 */ public void clear() { vertexs=null; arcs=null; arcNum=0; maxLen=defaultSize; } /** * 列印圖的資訊 */ public void printGraph() { for (int i = 0; i < arcs.length; i++) { for (int j = 0; j < arcs[i].length; j++) { if(arcs[i][j]!=null) System.out.println(arcs[i][j]); } } } public static void main(String[] args) { Object obj[] = { 'A', 'B', 'C', 'D', 'E', 'F' }; MatrixGraph graph = new MatrixGraph(obj); graph.addArc('A','C',5); graph.addArc('B','A',2); graph.addArc('C','B',15); graph.addArc('E','D',4); graph.addArc('F','E',18); graph.addArc('A', 'F', 60); graph.addArc('C', 'F', 70); graph.printGraph(); System.out.println("--------------"); graph.removeVertexs('A'); graph.printGraph(); System.out.println("--------------"); graph.removeArc('C', 'B'); graph.printGraph(); } }/**Output: A C 5 A F 60 B A 2 C B 15 C F 70 E D 4 F E 18 -------------- C B 15 C F 70 E D 4 F E 18 -------------- C F 70 E D 4 F E 18 */

圖的遍歷

圖的遍歷有兩種:DFS(Deep First Search)和BFS(Breadth First Search)
這兩個演算法算是我在ACM呆的時候做題最多的演算法了。

DFS(深度優先演算法)

即每次遍歷時偏向縱深方向搜尋,類似樹的先序遍歷,可用遞迴或者棧實現。

遞迴實現:

1. 訪問頂點vvisited[v]=1;//演算法執行前visited[n]=0
2. w=頂點v的第一個鄰接點;
3. whilew存在)  
    ifw未被訪問)
                從頂點w出發遞迴執行該演算法;  
                w=頂點v的下一個鄰接點;

棧實現:

 1.S初始化;visited[n]=0;
 2. 訪問頂點vvisited[v]=1;頂點v入棧S
 3. while(棧S非空)
        x=棧S的頂元素(不出棧);
            if(存在並找到未被訪問的x的鄰接點w)
                    訪問wvisited[w]=1;
                    w進棧;
            else
                     x出棧;

BFS(廣度優先演算法)

即每次遍歷時分層搜尋,先搜尋完每個頂點的領接結點,再對領接結點重複這一步。可用佇列實現

佇列實現

1. 初始化佇列Qvisited[n]=0;
2. 訪問頂點vvisited[v]=1;頂點v入佇列Q;
3.  while(佇列Q非空)   
             v=佇列Q的對頭元素出隊;
              w=頂點v的第一個鄰接點;
             whilew存在) 
                     如果w未訪問,則訪問頂點wvisited[w]=1;
                     頂點w入佇列Qw=頂點v的下一個鄰接點。



下面是兩種遍歷的具體程式碼

    /**
     * 深度優先遍歷
     * @param o
     * @return
     */
    public String dfs(Object o) {
        StringBuilder result=new StringBuilder();
        Stack<Object> stack=new Stack<Object>();
        //訪問標記陣列
        Boolean[] visit=new Boolean[vertexs.size()];
        //初始化所有頂點為未訪問
        for (int i = 0; i < visit.length; i++) {
            visit[i]=false;
        }
        //放入起始結點入棧
        stack.push(o);
        visit[vertexs.indexOf(o)]=true;
        result.append(o);
        //利用棧進行深度優先遍歷
        while (!stack.isEmpty()) {
            //訪問棧的頂點
            Object out=stack.peek();
            Object next=getNext(out, visit);
            if(next!=null){
                stack.push(next);
                visit[vertexs.indexOf(next)]=true;
                result.append("->"+next);
            }
            else{
                stack.pop();
            }
        }

        return result.toString();
    }

    /**
     * 廣度優先遍歷
     * @param o
     * @return
     */
    public String bfs(Object o) {
        StringBuilder result = new StringBuilder();
        Queue queue = new LinkedList<Object>();
        // 訪問標記陣列
        Boolean[] visit = new Boolean[vertexs.size()];
        // 初始化所有頂點為未訪問
        for (int i = 0; i < visit.length; i++) {
            visit[i] = false;
        }
        //放入起始結點入佇列
        queue.add(o);
        visit[vertexs.indexOf(o)]=true;
        result.append(o);
        //利用佇列進行廣度優先遍歷
        while (!queue.isEmpty()) {
            //訪問堆頂點
            Object out=queue.peek();
            Object next=getNext(out, visit);
            if(next!=null){
                queue.add(next);
                visit[vertexs.indexOf(next)]=true;
                result.append("->"+next);
            }else{
                queue.poll();
            }   
        }
        return result.toString();
    }

    /**
     * 獲取o結點的下一個未被訪問的領接結點
     * @param o
     * @param visit
     * @return
     */
    private Object getNext(Object o,Boolean[] visit){

        int index=vertexs.indexOf(o);
        for (int j = 0; j < arcs.length; j++) {
            if(arcs[index][j]!=null&&visit[j]==false)
                return vertexs.get(j);
        }   
        return null;
    }