圖論(六)圖的深度優(yōu)先遍歷DFS(非遞歸方式)

一、深度優(yōu)先搜索

深度優(yōu)先搜索是一個(gè)針對(duì)圖和樹(shù)的遍歷算法。早在19世紀(jì)就被用于解決迷宮問(wèn)題。

對(duì)于下面的樹(shù)而言,DFS方法首先從根節(jié)點(diǎn)1開(kāi)始,其搜索節(jié)點(diǎn)順序是1,2,3,4,5,6,7,8(假定左分枝和右分枝中優(yōu)先選擇左分枝)。

DFS的非遞歸實(shí)現(xiàn)方式相比于BFS應(yīng)該說(shuō)大同小異,只是把queue換成了stack而已,stack具有后進(jìn)先出LIFO(Last Input First Output)的特性,DFS的操作步驟如下:

  • 1、把起始點(diǎn)放入stack;
  • 2、重復(fù)下述3步驟,直到stack為空為止:
    • 從stack中訪問(wèn)棧頂?shù)狞c(diǎn);
    • 找出與此點(diǎn)鄰接的且尚未遍歷的點(diǎn),進(jìn)行標(biāo)記,然后全部放入stack中;
    • 如果此點(diǎn)沒(méi)有尚未遍歷的鄰接點(diǎn),則將此點(diǎn)從stack中彈出。

二、深度優(yōu)先遍歷(非遞歸)圖文演示

下面結(jié)合一個(gè)圖(graph)的實(shí)例,說(shuō)明DFS的工作過(guò)程和原理:

  • 1、將起始節(jié)點(diǎn)1放入棧stack中,標(biāo)記為已遍歷。
  • 2、從stack中訪問(wèn)棧頂?shù)墓?jié)點(diǎn)1,找出與節(jié)點(diǎn)1鄰接的節(jié)點(diǎn),有2、9兩個(gè)節(jié)點(diǎn),我們可以選擇其中任何一個(gè),選擇規(guī)則可以人為設(shè)定,這里假設(shè)按照節(jié)點(diǎn)數(shù)字順序由小到大選擇,選中的是2,標(biāo)記為已遍歷,然后放入stack中。
  • 3、從stack中取出棧頂?shù)墓?jié)點(diǎn)2,找出與節(jié)點(diǎn)2鄰接的節(jié)點(diǎn),有1、3、5三個(gè)節(jié)點(diǎn),節(jié)點(diǎn)1已遍歷過(guò),排除;3、5中按照預(yù)定的規(guī)則選中的是3,標(biāo)記為已遍歷,然后放入stack中。
  • 4、從stack中取出棧頂?shù)墓?jié)點(diǎn)3,找出與節(jié)點(diǎn)3鄰接的節(jié)點(diǎn),有2、4兩個(gè)節(jié)點(diǎn),節(jié)點(diǎn)2已遍歷過(guò),排除;選中的是節(jié)點(diǎn)4,標(biāo)記為已遍歷,然后放入stack中。
  • 5、從stack中取出棧頂?shù)墓?jié)點(diǎn)4,找出與節(jié)點(diǎn)4鄰接的節(jié)點(diǎn),有3、5、6三個(gè)節(jié)點(diǎn),節(jié)點(diǎn)3已遍歷過(guò),排除;選中的是節(jié)點(diǎn)5,標(biāo)記為已遍歷,然后放入stack中。
  • 6、從stack中取出棧頂?shù)墓?jié)點(diǎn)5,找出與節(jié)點(diǎn)5鄰接的節(jié)點(diǎn),有2、4兩個(gè)節(jié)點(diǎn),節(jié)點(diǎn)2、4都已遍歷過(guò),因此節(jié)點(diǎn)5沒(méi)有尚未遍歷的鄰接點(diǎn),則將此點(diǎn)從stack中彈出。
  • 7、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是4,找出與節(jié)點(diǎn)4鄰接的節(jié)點(diǎn),有3、5、6三個(gè)節(jié)點(diǎn),節(jié)點(diǎn)3、5都已遍歷過(guò),排除;選中的是節(jié)點(diǎn)6,標(biāo)記為已遍歷,然后放入stack中。
  • 8、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是6,找出與節(jié)點(diǎn)6鄰接的節(jié)點(diǎn),有4、7、8三個(gè)節(jié)點(diǎn),4已遍歷,按照規(guī)則選中的是7,標(biāo)記為已遍歷,然后放入stack中。
  • 9、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是7,找出與節(jié)點(diǎn)7鄰接的節(jié)點(diǎn),只有節(jié)點(diǎn)6,已遍歷過(guò),因此沒(méi)有尚未遍歷的鄰接點(diǎn),將節(jié)點(diǎn)7從stack中彈出。
  • 10、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是6,找出與節(jié)點(diǎn)6鄰接的節(jié)點(diǎn),有節(jié)點(diǎn)7、8,7已遍歷過(guò),因此將節(jié)點(diǎn)8放入stack中。
  • 11、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是8,找出與節(jié)點(diǎn)8鄰接的節(jié)點(diǎn),有節(jié)點(diǎn)1、6、9,1、6已遍歷過(guò),因此將節(jié)點(diǎn)9放入stack中。
  • 12、當(dāng)前stack棧頂?shù)墓?jié)點(diǎn)是9,沒(méi)有尚未遍歷的鄰接點(diǎn),將節(jié)點(diǎn)9彈出,依次類(lèi)推,棧中剩余節(jié)點(diǎn)8、6、4、3、2、1都沒(méi)有尚未遍歷的鄰接點(diǎn),都將彈出,最后棧為空。

  • 13、DFS遍歷完成。

三、鄰接表進(jìn)行深度優(yōu)先遍歷

3.1 構(gòu)建數(shù)據(jù)結(jié)構(gòu)

public class Graph {

    //頂點(diǎn)個(gè)數(shù)
    private int V;

    //邊的條數(shù)
    private int E;

    //領(lǐng)接表的底層存儲(chǔ)結(jié)構(gòu)
    private TreeSet<Integer>[] adj;

}

3.2 通過(guò)該結(jié)構(gòu)定義,構(gòu)造一個(gè)圖(無(wú)向圖)

/**
 * @Author: huangyibo
 * @Date: 2022/3/28 1:02
 * @Description: 領(lǐng)接表, 目前只支持無(wú)向無(wú)權(quán)圖
 */

public class Graph {

    //頂點(diǎn)個(gè)數(shù)
    private int V;

    //邊的條數(shù)
    private int E;

    //領(lǐng)接表的底層存儲(chǔ)結(jié)構(gòu)
    private TreeSet<Integer>[] adj;

    public Graph(String filename){
        File file = new File(filename);
        try {
            Scanner scanner = new Scanner(file);
            V = scanner.nextInt();
            if(V < 0){
                throw new IllegalArgumentException("V must be non-negative");
            }
            adj = new TreeSet[V];
            //初始化領(lǐng)接表
            for (int i = 0; i < V; i++) {
                adj[i] = new TreeSet<>();
            }

            E = scanner.nextInt();
            if(E < 0){
                throw new IllegalArgumentException("E must be non-negative");
            }
            for (int i = 0; i < E; i++) {
                int a = scanner.nextInt();
                //校驗(yàn)頂點(diǎn)a是否合法
                validateVertex(a);

                int b = scanner.nextInt();
                //校驗(yàn)頂點(diǎn)b是否合法
                validateVertex(b);

                //校驗(yàn)是否是自環(huán)邊
                if(a == b){
                    throw new IllegalArgumentException("Self Loop is Detected!");
                }
                //校驗(yàn)是否是平行邊
                if(adj[a].contains(b)){
                    throw new IllegalArgumentException("Parallel Edges are Detected!");
                }
                adj[a].add(b);
                adj[b].add(a);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 校驗(yàn)頂點(diǎn)是否合法
     * @param v
     */
    private void validateVertex(int v){
        if(v < 0 || v >= V){
            throw new IllegalArgumentException("vertex " + v + " is invalid");
        }
    }

    /**
     * 獲取頂點(diǎn)個(gè)數(shù)
     * @return
     */
    public int V(){
        return V;
    }

    /**
     * 獲取邊的條數(shù)
     * @return
     */
    public int E(){
        return E;
    }

    /**
     * 圖中是否存在v到w的邊
     * @param v
     * @param w
     * @return
     */
    public boolean hasEdge(int v, int w){
        //校驗(yàn)頂點(diǎn)v是否合法
        validateVertex(v);
        //校驗(yàn)頂點(diǎn)w是否合法
        validateVertex(w);
        return adj[v].contains(w);
    }

    /**
     * 返回和v相鄰的頂點(diǎn)
     * @param v
     * @return
     */
    public Iterable<Integer> adj(int v){
        //校驗(yàn)頂點(diǎn)v是否合法
        validateVertex(v);
        return adj[v];
    }

    /**
     * 返回頂點(diǎn)v的度
     * 頂點(diǎn)v的度(Degree)是指在圖中與v相關(guān)聯(lián)的邊的條數(shù)
     * @param v
     * @return
     */
    public int degree(int v){
        //校驗(yàn)頂點(diǎn)v是否合法
        validateVertex(v);
        return adj[v].size();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("V = %d, E = %d\n", V, E));
        for (int v = 0; v < V; v++) {
            sb.append(String.format("%d : ", v));
            for (Integer w : adj[v]) {
                sb.append(String.format("%d ", w));
            }
            sb.append("\n");
        }
        return sb.toString();
    }
}

3.3 鄰接表的深度優(yōu)先算法(非遞歸)

/**
 * @Author: huangyibo
 * @Date: 2022/3/28 1:02
 * @Description: 圖的深度優(yōu)先遍歷, 非遞歸方式
 */

public class GraphDFS {

    private Graph G;

    /**
     * 圖的頂點(diǎn)是否已經(jīng)被遍歷過(guò)
     */
    private boolean[] visited;

    //圖的深度優(yōu)先遍歷結(jié)果
    private List<Integer> order = new ArrayList<>();

    public GraphDFS(Graph G){
        this.G = G;
        visited = new boolean[G.V()];
        //循環(huán)所有頂點(diǎn), 防止一個(gè)圖出現(xiàn)多個(gè)連通圖(連通分量)的情況
        for (int v = 0; v < G.V(); v++) {
            if(!visited[v]){
                dfs(v);
            }
        }
    }

    /**
     * 圖的深度優(yōu)先遍歷, 非遞歸方式
     * @param source
     */
    private void dfs(int source) {
        Stack<Integer> stack = new Stack<>();
        //將源結(jié)點(diǎn)壓入棧頂
        stack.push(source);
        //標(biāo)記為已訪問(wèn)
        visited[source] = true;
        //如果棧不為空
        while(!stack.isEmpty()){
            Integer v = stack.pop();
            //當(dāng)前出棧頂點(diǎn)添加到圖的深度優(yōu)先遍歷結(jié)果集
            order.add(v);
            //遍歷頂點(diǎn)V的相鄰頂點(diǎn)
            for (Integer w : G.adj((v))) {
                //如果沒(méi)有遍歷過(guò)
                if(!visited[w]){
                    //頂點(diǎn)w壓入棧頂
                    stack.push(w);
                    //標(biāo)記w為已訪問(wèn)
                    visited[w] = true;
                }
            }
        }
    }

    /**
     * 圖的深度優(yōu)先遍歷結(jié)果集
     * @return
     */
    public List<Integer> order(){
        return order;
    }

    public static void main(String[] args) {
        Graph graph = new Graph("src/main/resources/g1.txt");
        GraphDFS graphDFS = new GraphDFS(graph);
        System.out.println(graphDFS.order());
    }
}

g1.txt

7 6
0 1
0 2
1 3
1 4
2 3
2 6

參考:
https://blog.csdn.net/saltriver/article/details/54429068

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容