數據結構基礎(五)圖以及圖的遍歷

概念

定義

圖是一種較線性表和樹更為復雜的數據結構
相較于線性表的一對一(每個結點只有一個前驅后驅)和樹的一對多(層級結構,上層結點可以與多個下層結點相關),圖形結構的元素關系是多對多(即結點之間的關系可以是任意的)

圖可分為有向圖和無向圖

術語

連通圖:對于無向圖,如果任意兩個結點之間都是通的,則稱之為連通圖
連通分量:對于無向非連通圖,極大連通子圖稱為其連通分量
強連通圖:對于有向圖,任意兩個結點有路徑
強連通分量:對于有向圖非連通圖,極大連通子圖稱為其強連通分量
連通圖的生成樹:該圖的極小連通子圖,它含有圖中全部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. 訪問頂點v;visited[v]=1;//算法執行前visited[n]=0
  2. w=頂點v的第一個鄰接點;
  3. while(w存在)
    if(w未被訪問)
    從頂點w出發遞歸執行該算法;
    w=頂點v的下一個鄰接點;

非遞歸實現:

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

BFS(廣度優先算法)

即每次時遍歷分層搜索,先搜索完每個頂點的領接結點,再對領接結點重復這一步。

非遞歸實現

  1. 初始化隊列Q;visited[n]=0;
  2. 訪問頂點v;visited[v]=1;頂點v入隊列Q;
  3. while(隊列Q非空)
    v=隊列Q的對頭元素出隊;
    w=頂點v的第一個鄰接點;
    while(w存在)
    如果w未訪問,則訪問頂點w;
    visited[w]=1;
    頂點w入隊列Q;
    w=頂點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;
    }

參考
圖(2)—— 鄰接矩陣表示法
圖(3)——鄰接鏈表法
深度優先遍歷與廣度優先遍歷

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 第一章 緒論 什么是數據結構? 數據結構的定義:數據結構是相互之間存在一種或多種特定關系的數據元素的集合。 第二章...
    SeanCheney閱讀 5,807評論 0 19
  • -DFS(Depth First Search):深度優先搜索 訪問完一個頂點的所有鄰接點之后,會按原路返回,對應...
    Spicy_Crayfish閱讀 2,853評論 1 0
  • VisuAlgo!一,Date Structure的核心技術是分解和抽象二,基本概念和常用術語 三,邏輯結構1,邏...
    斜杠青年許晏銘閱讀 908評論 0 0
  • https://zh.visualgo.net/graphds 淺談圖形結構https://zh.visualgo...
    狼之獨步閱讀 4,187評論 0 0
  • 第一次接觸蕭紅是在圖書館,那是一本紅色的小書,忘記當時是怎樣的心情,莫名其妙手伸到那里就把它拿下來。拿它的時候我并...
    小紅丫頭閱讀 452評論 3 11