圖的搜索

前言

圖的搜索指的是系統(tǒng)化地跟隨圖中的邊來(lái)訪問(wèn)圖中每一個(gè)結(jié)點(diǎn),并不是隨意地訪問(wèn)圖中的結(jié)點(diǎn)。圖的搜索算法可以用來(lái)發(fā)現(xiàn)圖的結(jié)構(gòu),許多圖的算法都要求先搜索全圖,可以說(shuō),圖的搜索是整個(gè)圖算法的核心。

圖的搜索有兩種類(lèi)型:

  • 廣度優(yōu)先搜索
  • 深度優(yōu)先搜索

本文中將用到兩個(gè)圖,一個(gè)有向圖,一個(gè)無(wú)向圖,分別如下:

廣度優(yōu)先搜索

廣度優(yōu)先搜索是指:給定圖 G = (V, E)和一個(gè)可識(shí)別的源結(jié)點(diǎn)s,對(duì)圖中的邊進(jìn)行探索,找到s能到達(dá)的所有結(jié)點(diǎn)。

舉例說(shuō)明,給定結(jié)點(diǎn)s,v為s能到達(dá)的所有結(jié)點(diǎn)的集合,先尋找s能到達(dá)的所有結(jié)點(diǎn),再找集合v中結(jié)點(diǎn)能到達(dá)的所有結(jié)點(diǎn)。

隊(duì)列和棧有什么不同呢?它們的不同或者說(shuō)精髓用法體現(xiàn)在圖的搜索上,細(xì)細(xì)體會(huì)就能理解隊(duì)列和棧的精髓。廣度優(yōu)先搜索需要用到隊(duì)列,因?yàn)橄日襰能到的頂點(diǎn),再找v集合能到的頂點(diǎn),需要先入先出,否則就不是廣度優(yōu)先搜索了

鄰接鏈表下的廣度優(yōu)先搜索:

  /*
 * 廣度優(yōu)先算法
 * 廣度優(yōu)先算法可以計(jì)算出到遍歷起點(diǎn)的最短路徑,因?yàn)樽疃搪窂揭欢ㄊ谴隧旤c(diǎn)的前驅(qū)距離+1得到的
 * 有權(quán)值的后續(xù)討論,如果無(wú)權(quán)圖這個(gè)結(jié)論是對(duì)的
 */
public void bfs(Vertex vertex){
    if (vertex == null) {
        return;
    }
    for (Vertex v : mList) {
        v.color = COLOR.WHITE;
    }
    int dis = 0;
    int index = mList.indexOf(vertex);
    if (index == -1) {
        System.out.println("error vertex");
        return;
    }
    Queue<Vertex> queue = new LinkedBlockingDeque<Vertex>();
    queue.add(vertex);
    vertex.color = COLOR.GRAY;
    vertex.d = dis;
    System.out.println(vertex);
    Arc arc = null;
    Vertex temp = null;
    while (!queue.isEmpty()) {
        temp = queue.peek();
        arc = temp.firstArc;
        while (arc != null) {
            if (arc.vertex.color == COLOR.WHITE) {
                arc.vertex.color = COLOR.GRAY;
                arc.vertex.d = temp.d + 1;
                System.out.println(arc.vertex);
                queue.add(arc.vertex);
                break;
            }
            arc = arc.next;
        }
        if (arc == null) {
            temp.color = COLOR.BLACK;
            queue.remove(temp);
        }
        if (queue.isEmpty()) {
            for (Vertex vertex2 : mList) {
                if (vertex2.color == COLOR.WHITE) {
                    queue.add(vertex2);
                    vertex2.color = COLOR.GRAY;
                    System.out.println(vertex2);
                    break;
                }
            }
        }
    }
}

結(jié)點(diǎn)中包含顏色,通過(guò)顏色判斷結(jié)點(diǎn)是否被訪問(wèn)。先將第一個(gè)節(jié)點(diǎn)V添加到隊(duì)列,再遍歷以V為起點(diǎn)的鏈表,如果鏈表中有某個(gè)結(jié)點(diǎn)未被染色,則訪問(wèn)并添加到隊(duì)列當(dāng)中,當(dāng)以V為起點(diǎn)的鏈表的所有結(jié)點(diǎn)均已被染色,則將V從隊(duì)列中移除。

特別值得一提的是,廣度優(yōu)先搜索的副產(chǎn)品非常有用,它能夠計(jì)算最短路徑。假設(shè)結(jié)點(diǎn)V的前驅(qū)結(jié)點(diǎn)是S,那么到某個(gè)點(diǎn)的最短路徑V.d = S.d + 1。

δ(s,v),表示s到v的最短距離。u.π表示u的前驅(qū),所以上述算法中的u.d就等于到搜索起點(diǎn)的最短距離。

再看看鄰接矩陣情況下的廣度優(yōu)先搜索算法:

  public void bfs(Vertex vertex){
    if (vertex == null) {
        return;
    }
    int index = mList.indexOf(vertex);
    if (index == -1) {
        return;
    }
    for (Vertex v : mList) {
        v.color = COLOR.WHITE;
    }
    Queue<Vertex> queue = new LinkedBlockingDeque<>();
    queue.add(vertex);
    vertex.color = COLOR.GRAY;
    vertex.d = 0;
    System.out.println(vertex);
    Vertex temp = null;
    while (!queue.isEmpty()) {
        temp = queue.peek();
        int tempIndex = mList.indexOf(temp);
        for (int i = 0; i < maxLength; i++) {
            if (mEdges[tempIndex][i] != null && mEdges[tempIndex][i].v2.color == COLOR.WHITE) {
                mEdges[tempIndex][i].v2.color = COLOR.GRAY;
                mEdges[tempIndex][i].v2.d = temp.d + 1;
                System.out.println(mEdges[tempIndex][i].v2);
                queue.add(mEdges[tempIndex][i].v2);
            }
        }
        queue.remove(temp);
    }
}

鄰接矩陣下的廣度優(yōu)先搜索變化也不大,主要在尋找結(jié)點(diǎn)v有邊相連的未被訪問(wèn)的結(jié)點(diǎn)邏輯上,稍有不同。

深度優(yōu)先算法

深度優(yōu)先搜索,顧名思義,只要可能就在圖中盡量深入。深度優(yōu)先搜索,總是對(duì)最近才發(fā)現(xiàn)的結(jié)點(diǎn)v的出發(fā)邊進(jìn)行搜索。

深度優(yōu)先搜索有兩種寫(xiě)法,本文中采用遞歸方式,不使用棧方法,有興趣的同學(xué)們自己嘗試使用棧實(shí)現(xiàn)。

鄰接鏈表情況下的深度優(yōu)先搜索:

int time = 0;
/*
 * 深度優(yōu)先算法,下面的深度優(yōu)先算法是最正確的,以棧來(lái)實(shí)現(xiàn)深度優(yōu)先算法有可能有問(wèn)題
 * 下面的計(jì)算方法最容易理解,訪問(wèn)完自己后立即訪問(wèn)自己的非訪問(wèn)后續(xù)結(jié)點(diǎn)
 * 同時(shí)深度優(yōu)先算法,可以計(jì)算每個(gè)結(jié)點(diǎn)訪問(wèn)順序,最終實(shí)現(xiàn)拓?fù)渑判颉? * 即f最大的,放在前面
 */
public void dfs(){
    for (Vertex vertex : mList) {
        vertex.color = COLOR.WHITE;
    }
    time = 0;
    for (Vertex vertex : mList) {
        if (vertex.color == COLOR.WHITE) {
            dfsVisit(vertex);
        }
    }
}
public void dfsVisit(Vertex vertex){
    if (vertex == null) {
        return;
    }
    time++;
    vertex.color = COLOR.GRAY;
    vertex.d = time;
    Arc arc = vertex.firstArc;
    while (arc != null) {
        if (arc.vertex.color == COLOR.WHITE) {
            dfsVisit(arc.vertex);
        }
        arc = arc.next;
    }
    time++;
    vertex.f = time;
    System.out.println(vertex);
    vertex.color = COLOR.BLACK;
}

深度優(yōu)先搜索,先取頂點(diǎn)數(shù)組中的每一個(gè)頂點(diǎn),如果頂點(diǎn)未被訪問(wèn),則繼續(xù)訪問(wèn)與該頂點(diǎn)有邊相連的頂點(diǎn),再遞歸訪問(wèn)新的頂點(diǎn)。

當(dāng)一個(gè)頂點(diǎn)被訪問(wèn)時(shí),它的顏色為灰色,只有當(dāng)與它相連的所有頂點(diǎn)都被訪問(wèn)完成了,此頂點(diǎn)顏色被置為黑色。
顏色為灰色時(shí),記錄時(shí)間,顏色為黑色時(shí)再次記錄時(shí)間,將時(shí)間打印下來(lái),非常有意思,因?yàn)榇蛴〉慕Y(jié)果就是拓?fù)渑判蚪Y(jié)果。

拓?fù)渑判蚴轻槍?duì)有向無(wú)環(huán)圖的概念。舉例說(shuō)明,穿襪子和穿鞋子是圖的兩個(gè)頂點(diǎn),穿襪子指向穿鞋子,日常生活中一定要先穿襪子才能穿鞋子,這就是拓?fù)渑判蚋傻幕睢?/p>

如果v1和v2有邊相連,并且是由v1指向v2,那么一定要先訪問(wèn)v1,再來(lái)訪問(wèn)v2。針對(duì)前言中的有向圖,拓?fù)渑判虻慕Y(jié)果是 1 2 4 3 5,即f值最大的應(yīng)該先訪問(wèn)。

鄰接矩陣情況下的深度優(yōu)先搜索:

public void dfs(){
    for (Vertex v : mList) {
        v.color = COLOR.WHITE;
    }
    for (Vertex v : mList) {
        if (v.color == COLOR.WHITE) {
            dfsVisit(v);
        }
    }
}

int time = 0;
public void dfsVisit(Vertex vertex){
    time++;
    vertex.color = COLOR.GRAY;
    vertex.d = time;
    int index = mList.indexOf(vertex);
    Vertex v2 = null;
    for (int i = 0; i < maxLength; i++) {
        v2 = mEdges[index][i] == null ? null : mEdges[index][i].v2;
        if (v2 != null && v2.color == COLOR.WHITE) {
            dfsVisit(v2);
        }
    }
    time++;
    vertex.color = COLOR.BLACK;
    vertex.f = time++;
    System.out.println(vertex);
}

和鄰接鏈表類(lèi)似,不再詳述

結(jié)語(yǔ)

圖的兩種搜索比較簡(jiǎn)單,但搜索過(guò)程中的額外兩個(gè)信息是非常重要的。廣度優(yōu)先搜索能夠查找最短路徑,而深度優(yōu)先搜索結(jié)果中,可以知道拓?fù)渑判蚪Y(jié)果,f值最大先訪問(wèn),即是拓?fù)渑判蚪Y(jié)果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容