圖的實現(鄰接矩陣)及DFS、BFS


@author QYX

寫作時間:2013/0302 最近准備noi比賽,加油!!!

因為近期學習任務太多太緊,所以我主要維護Github,博客園可能會停更幾天。----2020年2月9日

 圖(graph)是用線連接在一起的頂點或節點的集合,即兩個要素:邊和頂點。每一條邊連接個兩個頂點,用(i,j)表示頂點為 i 和 j 的邊。

​ 如果用圖示來表示一個圖,一般用圓圈表示頂點,線段表示邊。有方向的邊稱為有向邊,對應的圖成為有向圖,沒有方向的邊稱為無向邊,對應的圖叫無向圖。對於無向圖,邊(i, j)和(j,i)是一樣的,稱頂點 i 和 j 是鄰接的,邊(i,j)關聯於頂點 i 和 j ;對於有向圖,邊(i,j)表示由頂點 i 指向頂點 j 的邊,即稱頂點 i 鄰接至頂點 j ,頂點 i 鄰接於頂點 j ,邊(i,j)關聯至頂點 j 而關聯於頂點 i 。

​ 對於很多的實際問題,不同頂點之間的邊的權值(長度、重量、成本、價值等實際意義)是不一樣的,所以這樣的圖被稱為加權圖,反之邊沒有權值的圖稱為無權圖。所以,圖分為四種:加權有向圖,加權無向圖,無權有向圖,無權無向圖。

 

圖的表現有很多種,鄰接表法,臨接矩陣等。

圖經常是以這種形式出現的[weight,from,to]的n*3維數組出現的,見名知意,三個元素分別為邊的權重,從哪兒來,到哪兒去。

 

 如上圖所示,由一條邊連接在一起的頂點稱為相鄰頂點,A和B是相鄰頂點,A和D是相鄰頂點,A和C是相鄰頂點......A和E是不相鄰頂點。一個頂點的是其相鄰頂點的數量,A和其它三個頂點相連,所以A的度為3,E和其它兩個頂點相連,所以E的度為2......路徑是一組相鄰頂點的連續序列,如上圖中包含路徑ABEI、路徑ACDG、路徑ABE、路徑ACDH等。簡單路徑要求路徑中不包含有重復的頂點,如果將的最后一個頂點去掉,它也是一個簡單路徑。例如路徑ADCA是一個環,它不是一個簡單路徑,如果將路徑中的最后一個頂點A去掉,那么它就是一個簡單路徑。如果圖中不存在環,則稱該圖是無環的。如果圖中任何兩個頂點間都存在路徑,則該圖是連通的,如上圖就是一個連通圖。如果圖的邊沒有方向,則該圖是無向圖,上圖所示為無向圖,反之則稱為有向圖,下圖所示為有向圖:

 

 

 在有向圖中,如果兩個頂點間在雙向上都存在路徑,則稱這兩個頂點是強連通的,如上圖中C和D是強連通的,而A和B是非強連通的。如果有向圖中的任何兩個頂點間在雙向上都存在路徑,則該有向圖是強連通的,非強連通的圖也稱為稀疏圖

  此外,圖還可以是加權的。前面我們看到的圖都是未加權的,下圖為一個加權的圖:

 

 

可以想象一下,前面我們介紹的鏈表也屬於圖的一種特殊形式。圖在計算機科學中的應用十分廣泛,例如我們可以搜索圖中的一個特定頂點或一條特定的邊,或者尋找兩個頂點間的路徑以及最短路徑,檢測圖中是否存在環等等。

  存在多種不同的方式來實現圖的數據結構,下面介紹幾種常用的方式。

鄰接矩陣

  在鄰接矩陣中,我們用一個二維數組來表示圖中頂點之間的連接,如果兩個頂點之間存在連接,則這兩個頂點對應的二維數組下標的元素的值為1,否則為0。下圖是用鄰接矩陣方式表示的圖:

 

 

 如果是加權的圖,我們可以將鄰接矩陣中二維數組里的值1改成對應的加權數。鄰接矩陣方式存在一個缺點,如果圖是非強連通的,則二維數組中會有很多的0,這表示我們使用了很多的存儲空間來表示根本不存在的邊。另一個缺點就是當圖的頂點發生改變時,對於二維數組的修改會變得不太靈活。

鄰接表

  圖的另外一種實現方式是鄰接表,它是對鄰接矩陣的一種改進。鄰接表由圖中每個頂點的相鄰頂點列表所組成。如下圖所示,我們可以用數組、鏈表、字典或散列表來表示鄰接表。

 

 

關聯矩陣

  我們還可以用關聯矩陣來表示圖。在關聯矩陣中,矩陣的行表示頂點,列表示邊。關聯矩陣通常用於邊的數量比頂點多的情況下,以節省存儲空間。如下圖所示為關聯矩陣方式表示的圖:

 

 

深度優先搜索#

深度優先搜索,我們以無向圖為例。

圖的深度優先搜索(Depth First Search),和樹的先序遍歷比較類似。

它的思想:假設初始狀態是圖中所有頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點,然后依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。 若此時尚有其他頂點未被訪問到,則另選一個未被訪問的頂點作起始點,重復上述過程,直至圖中所有頂點都被訪問到為止。

顯然,深度優先搜索是一個遞歸的過程。

廣度優先搜索#

廣度優先搜索,我們以有向圖為例。

廣度優先搜索算法(Breadth First Search),又稱為”寬度優先搜索”或”橫向優先搜索”,簡稱BFS。

它的思想是:從圖中某頂點v出發,在訪問了v之后依次訪問v的各個未曾訪問過的鄰接點,然后分別從這些鄰接點出發依次訪問它們的鄰接點,並使得“先被訪問的頂點的鄰接點先於后被訪問的頂點的鄰接點被訪問,直至圖中所有已被訪問的頂點的鄰接點都被訪問到。如果此時圖中尚有頂點未被訪問,則需要另選一個未曾被訪問過的頂點作為新的起始點,重復上述過程,直至圖中所有頂點都被訪問到為止。

換句話說,廣度優先搜索遍歷圖的過程是以v為起點,由近至遠,依次訪問和v有路徑相通且路徑長度為1,2…的頂點。

package com.qyx;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
//使用鄰接矩陣來實現圖
public class Graph {
    private ArrayList<String> vertexList; //存儲頂點的集合
    private int[][] edges;//存儲圖對應的鄰接矩陣
    private int numOfEdges;//表示邊的數目
    private boolean isVisited[];
    public static void main(String[] args) {
        int n=5;//節點的個數
        String vertex[]={"A","B","C","D","E"};
        //創建圖對象
        Graph graph=new Graph(n);
        for (String s:vertex)
        {
            graph.vertexList.add(s);
        }
        //添加邊
        graph.insertEdge(0,1,1);
        graph.insertEdge(0,2,1);
        graph.insertEdge(1,2,1);
        graph.insertEdge(1,3,1);
        graph.insertEdge(1,4,1);
        //顯示鄰接矩陣
        graph.showGraph();
        //graph.dfs();
        graph.bfs();
    }
    //構造器
    public Graph(int n)
    {
        //初始化鄰接矩陣和vertexList
        edges=new int[n][n];
        vertexList=new ArrayList<String>();
        numOfEdges=0;
        isVisited=new boolean[5];
    }
    //插入頂點
    public void insertVertex(String vertex)
    {
        vertexList.add(vertex);
    }

    /**
     *
     * @param v1 表示點的下標 即是第幾個頂點
     * @param v2 第二個頂點對應的下標
     * @param weight 表示是否連接 0不相連 1相連
     */
    //添加邊
    public void insertEdge(int v1,int v2,int weight)
    {
        edges[v1][v2]=weight;
        edges[v2][v1]=weight;
        numOfEdges++;
    }
    //圖中常用的方法
    //返回節點的個數
    public int getNumOfVertex()
    {
        return vertexList.size();
    }
    //得到邊的數目
    public int getNumOfEdges()
    {
        return numOfEdges;
    }
    //返回節點i對應的值 0-A 1-B 2-C
    public String getValue(int index)
    {
        return vertexList.get(index);
    }
    //返回v1和v2的權值
    public int getWeight(int v1,int v2)
    {
        return edges[v1][v2];
    }
    //顯示圖對應的鄰接矩陣
    public void showGraph()
    {
        for (int[] arrs:edges)
        {
            System.out.println(Arrays.toString(arrs));
        }
    }

    /**
     *
     * @param index
     * @return 如果存在返回對應的下標,否則返回-1
     */
    //得到第一個鄰接節點的下標
    public int getFirstNeighbor(int index)
    {
        for (int j=index;j<vertexList.size();j++)
        {
            if (edges[index][j]>0)
            {
                return j;
            }
        }
        return -1;
    }
    //根據前一個鄰接節點的下標來獲取下一個鄰接節點
    public int getNextNeighbor(int v1,int v2) {
        for (int i = v2 + 1; i < vertexList.size(); i++) {
                if (edges[v1][i] > 0) {
                    return i;
                }
        }
        return -1;
    }
        //深度優先遍歷算法
        //i 第一次就是0
        private void dfs ( boolean[] isVisited, int i)
        {
            //首先我們訪問該節點
            System.out.print(getValue(i) + "->");
            //將該鄰接節點設置為已訪問
            isVisited[i] = true;
            int w = getFirstNeighbor(i);//查找節點w的第一個鄰接節點w
            while (w != -1) {
                if (!isVisited[w]) {
                    dfs(isVisited,w);
                }
                //如果已經被訪問過
                w=getNextNeighbor(i,w);
            }
        }
    //對dfs進行重載,遍歷所有的節點並進行dfs
    public void dfs()
    {
        //遍歷所有的節點進行dfs
        for (int i=0;i<getNumOfVertex();i++)
        {
            if (!isVisited[i])
            {
                dfs(isVisited,i);
            }
        }
    }
    //對一個節點進行深度優先遍歷的方法
    public void bfs(boolean[] isVisited,int i)
    {
        int u;//表示隊列的頭結點對應下標
        int w;//鄰接節點
        //隊列,記錄節點訪問的順序
        LinkedList queue = new LinkedList();
        //訪問節點
        System.out.print(getValue(i)+"->");
        //標記為已訪問
        isVisited[i]=true;
        //將節點加入隊列
        queue.addLast(i);
        while (!queue.isEmpty())
        {
            //取出隊列的頭結點下標
            u =(Integer)queue.removeFirst();
            //得到第一個鄰接節點的下標w
            w =getFirstNeighbor(u);
            while (w!=-1)
            {
                //找到
                //是否訪問
                if (!isVisited[w])
                {
                    System.out.print(getValue(w)+"->");
                    //入隊
                    queue.addLast(w);
                    isVisited[w]=true;
                }
                //找w后面的下一個鄰接節點
                w=getNextNeighbor(u,w); //體現出廣度優先

            }
        }
    }
    //遍歷所有的節點都進行廣度優先搜索
    public void bfs()
    {
        for (int i=0;i<getNumOfVertex();i++)
        {
            if (!isVisited[i])
            {
                bfs(isVisited,i);
            }
        }
    }
}

 2020年2月9日對原有博客進行了修改,參考博客:

https://www.cnblogs.com/jaxu/p/11338294.html

 https://www.cnblogs.com/DarrenChan/p/9547869.html

感謝幫助!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM