首頁 > 軟體

Java搜尋與圖論之DFS和BFS演演算法詳解

2022-11-23 14:00:34

本次我們介紹搜尋與圖論篇中DFS和BFS,我們會從下面幾個角度來介紹:

  • DFS和BFS簡介
  • DFS數位排序
  • DFS皇后排序
  • DFS樹的重心
  • BFS走迷宮
  • BFS八數碼
  • BFS圖層次

DFS和BFS簡介

首先我們先來介紹一下DFS和BFS:

  • DFS:深度優先遍歷演演算法,我們在進行演演算法運算時,優先將該路徑的當前路徑執行完畢,執行完畢或失敗後向上回溯嘗試其他途徑
  • BFS:廣度優先遍歷演演算法,我們在進行演演算法運算時,優先將當前路徑點的所有情況羅列出來,然後根據羅列出來的情況羅列下一層

DFS和BFS的演演算法依據:

  • 兩者均以樹的形式進行展開,可以採用樹的模型來進行DFS和BFS演示

DFS數位排序

我們首先給出DFS的一元問題:

  • 給定一個整數n,將數位1∼n排成一排,將會有很多種排列方法。
  • 現在,請你按照字典序將所有的排列方法輸出。

問題解析:

一元問題解析

我們目前採用DFS演演算法運算,我們需要一次得到資料,然後回溯

那麼我們目前的問題就是:

  • 如何判斷DFS演演算法結束:我們只需要記錄遍歷到第幾個數位然後與之判斷是否相等,若相等表示結束
  • 如何得知當前數位已經使用:我們只需要單列一個陣列來記錄該數是否被使用即可

我們給出演演算法程式碼:

import java.util.Scanner;

public class Main {

    public static final int N = 10;

    // 存放資料
    static int n;
    static int[] arr = new int[N];
    static int[] res = new int[N];

    // 判斷是否被使用
    static boolean[] isUsed = new boolean[N];

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();

        for (int i = 0; i < n; i++) {
            arr[i] = i+1;
        }

        dfs(0);
    }

    public static void dfs(int x){
        // 首先判斷是否可以輸出
        if (x == n){
            for (int i=0;i < n;i++){
                System.out.print(res[i]+ " ");
            }
            System.out.println();
        }

        // 開始dfs
        for (int i = 0; i < n; i++) {
            // 判斷是否被使用,若被使用,則不能使用;若未被使用,使用並進入下一層
            if (!isUsed[i]){
                // 未被使用,使用並進入下一層
                res[x] = arr[i];
                isUsed[i] = true;
                dfs(x+1);
                // 下面屬於回溯部分,我們需要恢復原樣,這裡的x已經回溯,不需要覆蓋res的值
                isUsed[i] = false;
            }
        }
    }
}

DFS皇后排序

我們首先給出DFS的二元問題:

  • n−皇后問題是指將n個皇后放在n×n的國際象棋棋盤上,使得皇后不能相互攻擊到
  • 即任意兩個皇后都不能處於同一行、同一列或同一斜線上。
  • 現在給定整數 nn,請你輸出所有的滿足條件的棋子擺法。

問題解析:

原始方法

首先我們採用最基本的思想,我們採用一元思想,針對n*n的棋盤上的每個位置都進行DFS操作,並對其判斷是否滿足條件

在滿足條件的位置上我們放上皇后並記錄數目,如果到最後皇后的數量足夠,那麼我們就將他輸出

升級方法

我們已經知道他們不能放在同一行和同一列,我們直接採用for將一行中的一個位置選出來,然後對每行DFS操作並判斷是否滿足條件

在滿足條件的位置上我們放上皇后並記錄數目,如果到最後皇后的數量足夠,那麼我們就將他輸出

注意點

我們的n-皇后問題還需要保證對角線上不具有相同棋子

我們採用二元函數的函數y=x以及y=n-x來給出對角線的位置

我們給出演演算法程式碼:

/*原始方法*/

import java.util.Scanner;

public class dfsDouble {

    static final int N = 20;

    // 記錄資料
    static int n;
    static char[][] arr = new char[N][N];

    // 記錄行,列,對角線,反對角線
    static boolean[] row = new boolean[N];
    static boolean[] col = new boolean[N];
    static boolean[] dg = new boolean[2*N-1];
    static boolean[] udg = new boolean[2*N-1];

    // 主函數
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();

        for (int i = 0;i < n;i++){
            for (int j = 0; j < n; j++) {
                arr[i][j] = '.';
            }
        }

        dfs(0,0,0);

    }

    // DFS
    private static void dfs(int x,int y,int u) {

        // y到頭,換行
        if(y == n){
            y = 0;
            x++;
        }

        // 老規矩判斷輸出條件
        if (x == n){
            if (u == n){
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < n; j++) {
                        System.out.print(arr[i][j]);
                    }
                    System.out.println();
                }
                System.out.println();
            }
            return;
        }

        // 進行dfs(不選的情況,選該行的其他點位)
        dfs(x, y + 1, u);

        // 判斷是否符合條件
        if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
        {
            arr[x][y] = 'Q';
            row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;

            // 進行dfs(符合條件選,繼續下一步)
            dfs(x, y + 1, u + 1);

            row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
            arr[x][y] = '.';
        }
    }
}

/*升級方法*/

import java.util.Scanner;

public class dfsDouble {

    static final int N = 20;

    // 記錄資料
    static int n;
    static char[][] arr = new char[N][N];

    // 記錄列,對角線,反對角線
    static boolean[] col = new boolean[N];
    static boolean[] dg = new boolean[2*N-1];
    static boolean[] udg = new boolean[2*N-1];

    // 主函數
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();

        for (int i = 0;i < n;i++){
            for (int j = 0; j < n; j++) {
                arr[i][j] = '.';
            }
        }

        dfs(0);

    }

    // DFS
    private static void dfs(int u) {

        // 我們採用每行取一個的策略,這裡的u就是x
        if (u == n){
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    System.out.print(arr[i][j]);
                }
                System.out.println();
            }
            System.out.println();
            return;
        }

        // 我們取滿足條件的位置
        for (int j = 0; j < n; j++) {
            if (!col[j] && !dg[u+j] && !udg[u - j + n]){
                arr[u][j] = 'Q';
                col[j] = dg[u+j] = udg[u-j+n] = true;
                dfs(u+1);
                col[j] = dg[u+j] = udg[u-j+n] = false;
                arr[u][j] = '.';
            }
        }
    }
}

DFS樹的重心

我們這裡利用DFS來求解一道難題:

  • 給定一顆樹,樹中包含 nn 個結點(編號 1∼n1∼n)和 n−1n−1 條無向邊。
  • 請你找到樹的重心,並輸出將重心刪除後,剩餘各個連通塊中點數的最大值。
  • 重心定義:重心是指樹中的一個結點,如果將這個點刪除後,剩餘各個連通塊中點數的最大值最小,那麼這個節點被稱為樹的重心。

我們給出一個簡單範例來表明重心:

我們來簡單介紹一下:

輸入資料

第一個是操作次數,然後後面成對書寫,表示兩者相連

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

重心介紹

我們上圖中的黑筆書寫部分是由上述資料所搭建出來的無向圖,我們上面用樹的形式寫出

我們的棕筆部分是指去掉該點之後,剩餘的聯通點分塊的個數中的最大塊,我們要測試全部的點位,並給出這些最大塊的最小快

思路分析

首先我們要遍歷所有的點,一一求解該點刪除後的最大塊

我們刪除該點後,其連通區域主要分為兩部分:該點的子點,該點的上一個點的個數去除該點以及該點子類的個數

我們給出相關程式碼:

import java.util.Scanner;

public class Main {

    final static int N = 100010;

    // 首先我們用單連結串列模擬圖
    static int n;
    static int idx;
    static int[] h = new int[N];
    static int[] e = new int[N*2];
    static int[] ne = new int[N*2];

    // 判定是否已經經過
    static boolean[] isPassed = new boolean[N*2];

    // 最大值
    static int ans = N;


    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();

        // 將頭節點設為-1,方便判斷
        for (int i = 1; i < N; i++) {
            h[i] = -1;
        }

        // 進行連線
        for (int i = 0; i < n-1; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();

            // 注意是無向邊,我們需要雙向連線
            add(a,b);
            add(b,a);
        }

        // 開始遍歷
        dfsMethod(1);

        // 最後輸出結果
        System.out.println(ans);
    }

    // dfs操作
    private static int dfsMethod(int u) {

        // 連通塊的最大值
        int res = 0;

        // 首先將自己設定為已經過點
        isPassed[u] = true;

        // 該點以及子類的點數(目前已包含自己點)
        int sum = 1;

        // 開始遍歷子點
        for (int i = h[u];i != -1;i = ne[i]){

            // 將每個點用變數表示出來
            int j = e[i];

            // 如果該點沒有經過,對其dfs遍歷
            if (!isPassed[j]){
                // 遍歷時需要返回sum來獲得下列點的大小,為了得到ans做準備
                int s = dfsMethod(j);

                // 和res比較,獲得連通塊最大值
                res = Math.max(res,s);

                // 將子類點新增到sum中
                sum += s;
            }
        }


        // 我們還需要與拋棄該點後上面的點所產生的res作比較
        res = Math.max(res,n-sum);

        // 返回最小的ans
        ans = Math.min(ans,res);

        return sum;

    }

    // 我們需要一個單連結串列連線的函數
    public static void add(int a,int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
}

BFS走迷宮

我們給出BFS走迷宮題目:

  • 給定一個n×m的二維整數陣列,用來表示一個迷宮,陣列中只包含0或1,其中0表示可以走的路,1表示不可通過的牆壁。
  • 最初,有一個人位於左上角 (1,1)處,已知該人每次可以向上、下、左、右任意一個方向移動一個位置。
  • 請問,該人從左上角移動至右下角 (n,m)處,至少需要移動多少次。
  • 資料保證 (1,1)處和 (n,m)處的數位為0,且一定至少存在一條通路。

問題解析:

BFS運作

  • 首先我們要知道BFS的運作形式
  • 首先我們BFS是根據距離或長度來進行分類遞增
  • 那麼在走迷宮時,我們距離為n+1的位置肯定是由距離為n的位置的上下左右方向的位置
  • 那麼我們就可以採用一個佇列來進行裝配,我們將獲得的可走的點位和距離儲存進去,然後根據這個點位和距離推算下一個點位和距離

我們給出演演算法程式碼:

import java.util.Scanner;

public class bfs {

    static final int N = 100;

    // 存放資料,存放是否使用
    static int n,m,hh,tt;
    static int[][] arr = new int[N][N];// 地圖
    static int[][] isPassed = new int[N][N];// 是否經過,若經過修改為距離
    static PII[] queue = new PII[N*N];// 佇列

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();

        m = scanner.nextInt();

        for (int i=1;i <= n;i++){
            for (int j=1;j <= m;j++){
                // 輸入0/1
                arr[i][j] = scanner.nextInt();
                // 全部設定為未pass
                isPassed[i][j] = -1;
            }
        }

        int res = bfsMethod();
        System.out.println(res);
    }

    private static int bfsMethod() {

        // 初始設定
        hh = 0 ; tt = -1; //佇列的頭節點=0,尾節點 = 0;
        isPassed[1][1] = 0; // 我們首先站在的是第一個點,所以值距離設定為0
        queue[++tt] = new PII(1,1); //然後將第一個點下標存入q佇列中

        // 提前設定好移動方向(分別對應方向)
        int[] xmove = {-1,0,1,0};
        int[] ymove = {0,1,0,-1};

        // 遍歷queue
        while (hh <= tt){
                PII t = queue[hh++]; //每一次將頭結點拿出來
                for(int i = 0 ; i < 4 ; i ++ ) {//然後進行下一步要往哪裡走,這裡可能有多重選擇可走
                    int x = t.x + xmove[i]; //這裡進行x軸向量判斷
                    int y = t.y + ymove[i];//這裡進行y軸向量的判斷
                    //如果x,y滿足在地圖中不會越界,然後地圖上的點g是0(表示可以走),
                    //然後這裡是沒走過的距離d是-1;
                    if (x > 0 && x <= n && y > 0 && y <= m && arr[x][y] == 0 && isPassed[x][y] == -1) {
                        //將現在可以走的點(x,y)加上上一個點計數距離的點加上一,就是現在走到的點的距離
                        isPassed[x][y] = isPassed[t.x][t.y] + 1;
                        queue[++tt] = new PII(x, y);//然後將這一個可以走的點存入佇列尾
                    }
                }
        }
        return isPassed[n][m]; //最後返回的是地圖走到盡頭最後一個位置的位置統計的距離
    }

    //這是一個用來儲存兩個座標的類Pair
    static class PII{
        int x,y;
        public PII(int x,int y){
            this.x  = x;
            this.y = y;
        }
    }
}

BFS八數碼

我們給出BFS八數碼題目:

  • 在一個3×3的網格中,1∼8這 88 個數位和一個 x 恰好不重不漏地分佈在這 3×3的網格中。
  • 在遊戲過程中,可以把 x 與其上、下、左、右四個方向之一的數位交換(如果存在)。

我們需要將八數碼從下列形式變成順序形式:

/*原八數碼*/

1 2 3
x 4 6
7 5 8
    
/*完善八數碼*/
    
1 2 3
4 5 6
7 8 x
    
/*變化順序*/
    
1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

問題解析:

八數碼問題解析

我們這裡要計算最小的移動步數,那麼我們就需要採用BFS來計算最近的

其實和之前的走迷宮非常相似,我們將x與上下左右四個方向的數進行對換,然後比較是否為最終結果即可

我們給出演演算法程式碼:

import java.util.*;

public class bfs {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        // 開始狀況
        String start = "";

        for(int i = 0 ; i < 9 ; i ++ ){
            String s = scanner.next();
            start += s;
        }

        // 結束狀況
        String end = "12345678x";

        // bfs迴圈
        System.out.println(bfsMethod(start,end));
    }

    public static int bfsMethod(String start,String end){
        // 雜湊表存放字串和對應的移動步數
        HashMap<String,Integer> hashMap = new HashMap<String, Integer>();
        // 佇列存放字串
        Queue<String> queue = new LinkedList<>();

        // 存放第一個點(還未開始,啟動步數為0)
        hashMap.put(start,0);
        queue.add(start);

        while (!queue.isEmpty()){
            // 將head資料拿出來
            String s = queue.poll();

            // 首先判斷是否符合條件
            if (s.equals(end)) return hashMap.get(s);

            // 找到x座標
            int index = s.indexOf("x");

            // 計算對應位置
            int x = index/3;
            int y = index%3;

            // 然後上下左右移動判斷
            int[] xmove = {1,0,-1,0};
            int[] ymove = {0,1,0,-1};

            for (int i=0;i<4;i++){

                int a = x + xmove[i];
                int b = y + ymove[i];

                //如果這種情況沒有超出邊界
                if(a >= 0 && a < 3 && b >= 0 && b < 3){

                    //將這種情況的字串轉化成字元陣列,能夠有下標進行交換
                    char[] arr = s.toCharArray();

                    //然後交換x跟沒有超出邊界的值進行交換,二維轉成一維下標x*3+y;
                    swap(arr, index, a * 3 + b);

                    //然後將字元陣列轉化成字串
                    String str = new String(arr);

                    //如果這種情況對應的value值是null,說明還沒有走過
                    if(hashMap.get(str) == null){

                        //然後將這種情況對應進行上一步的距離加上1
                        hashMap.put(str,hashMap.get(s) + 1);

                        //然後將新的情況插入到隊尾中
                        queue.offer(str);
                    }
                }
            }
        }
        return -1;
    }

    // 交換演演算法
    public static void swap(char[] arr,int x,int y){
        char temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
}

BFS圖層次

我們這裡利用BFS來求解一道難題:

  • 給定一個n個點m條邊的有向圖,圖中可能存在重邊和自環。
  • 所有邊的長度都是1,點的編號為1∼n。
  • 請你求出1號點到n號點的最短距離,如果從1號點無法走到n號點,輸出 −1。

我們採用BFS來逐層遞進,其原理其實和前面兩道題相同:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class bfsssss {

    final static int N = 100010;

    // 單連結串列模擬圖
    static int n,m;
    static int hh,tt;
    static int idx;
    static int[] h = new int[N];
    static int[] e = new int[N];
    static int[] ne = new int[N];

    // 距離儲存以及佇列
    static int[] distance = new int[N];
    static int[] queue = new int[N];

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();
        m = scanner.nextInt();

        // 初始化
        for (int i = 1; i < N; i++) {
            h[i] = -1;
            distance[i] = -1;
        }

        // 賦值
        for (int i = 0;i < m;i++ ){
            int a = scanner.nextInt();
            int b = scanner.nextInt();

            add(a,b);
        }

        // BFS操作
        int res = bfsFind();

        // 輸出
        System.out.println(res);
    }

    // bfs操作
    public static int bfsFind(){

        // 設定hh,tt
        hh = 0;
        tt = -1;

        // 第一個點距離為0
        distance[1] = 0;

        // 將第一個點加入佇列
        queue[++tt] = 1;

        // 開始佇列迴圈
        while (hh <= tt){
            int t = queue[hh++];
            // 取得該點,對其ne進行處理
            for (int i = h[t]; i != -1; i = ne[i]) {
                // 得到該子點,進行處理
                int s = e[i];
                if (distance[s] == -1){
                    // 如果沒有經過就設定dis,並且加入佇列
                    distance[s] = distance[t] + 1;
                    queue[++tt] = s;
                }
            }
        }
        return distance[n];
    }

    // 經典單連結串列新增方法
    public static void add(int a,int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx;
        idx++;
    }
}

以上就是Java搜尋與圖論之DFS和BFS演演算法詳解的詳細內容,更多關於Java DFS BFS的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com