Java實現(xiàn)無向連通圖中的“割點”問題

直接上代碼,詳細請見注釋或者下方留言。

package cut.point;

import java.util.Scanner;
import java.util.Stack;

/**
 * "轟炸重要城市"問題:
 * 假設(shè)當前我們擁有一個 地區(qū)的城市地圖,但是只有一個原子彈,為了讓這顆原子彈發(fā)揮最大的效果,要阻斷這個地區(qū)各個城市中最關(guān)鍵的一個交通要塞,那么這個原子彈該投放在哪里?
 * 其實真有原子彈就不會考慮這么多了(~……~),扯回正題。這種問題模型化之后,就是讓我們從一個無向連通圖中選擇一個“割點”,去掉這個點之后,不再是連通圖。
 * 關(guān)鍵詞:DFS、割點、無向連通圖、重要城市
 * 特殊說明:有些方法的參數(shù)比較多,主要是這次不想用一些全局靜態(tài)變量來處理,所以就全部通過傳參解決!
 * @author XZP
 *一組測試數(shù)據(jù):
6 7 
1 4 
1 3 
4 2
3 2
2 5 
2 6
5 6
 */
public class BombingImportantCity {

    public static void main(String[] args) {
        int INF = 99999; // 人為設(shè)定的最大值
        Scanner sc = new Scanner(System.in);
        int i; // 游標
        int v1, v2; //暫時存儲邊兩個頂點的編號
        int n = sc.nextInt(); // 頂點數(shù)
        int m = sc.nextInt(); // 邊的條數(shù)
//      Edge[] edges = new Edge[m + 1]; // 存儲邊的數(shù)組,下標從1開始
        int[][] edges = new int[n + 1][n +1]; // 存放邊的鄰接矩陣
        int[] num = new int[n + 1]; // 存儲第一遍從頂點1dfs遍歷情況下的時間戳,數(shù)組下標代表頂點編號,值表示第幾個訪問到(時間戳)
        int[] low = new int [n + 1]; // 表示每個頂點在不經(jīng)過父頂點能回到的最小時間戳(比較拗口,大致可以理解為:根據(jù)num數(shù)組找到父節(jié)點,然后用dfs遍歷,能夠達到的最小時間戳)
        // 初始化low數(shù)組,比較重要
        for (i = 1; i <= n; i++) {
            low[i] = INF;
        }
        
        // 讀入m條邊
        for (i =1; i <= m; i++) {
            v1 = sc.nextInt();
            v2 = sc.nextInt();
            edges[v1][v2] = 1; // 其他都為零表示兩點之間不可達或者是自身
        }
        // 求num數(shù)組(每個頂點對應(yīng)的時間戳)
        dfs(1, edges, num, n);
        
        // 得到割點
        int cutPoint = judgeCutPoint(low, edges, n, num, 1);
        if (cutPoint == 0) {
            System.out.println("沒有割點!");
        } else {
            System.out.println("割點編號為: " + cutPoint);
        }
    }
    /**
     * 以當前節(jié)點為起始點排除父節(jié)點的情況下深度優(yōu)先遍歷以獲取當前點能訪問的最小時間戳
     * @param low 待計算、更新的最小訪問時間戳
     * @param child 當前節(jié)點
     * @param parent 當前節(jié)點的父節(jié)點(根據(jù)num矩陣來的)
     * @param edges 邊的鄰接矩陣
     * @param num 時間戳矩陣
     * @param n 頂點個數(shù)
     */
    public static void dfsExParent(int[] low, int child,int parent, int[][] edges, int[] num, int n) {
        int[] book = new int[n + 1]; // 用于判斷一個頂點是否已經(jīng)被放到棧里面去過了,避免環(huán)引起的錯誤
        Stack<Integer> search = new Stack<Integer>();
        search.push(child);
        book[child] = 1;
        while (!search.isEmpty()) {
            // 首先從棧頂去一個元素,并將這個頂點相連的頂點入棧
            int top = search.pop();
            // 用更小的時間戳替換掉
            if (num[top] < low[child]) {
                low[child] = num[top];
            }
            for (int i = 1; i <=n; i++) {
                if (i !=parent && num[top] < i && edges[top][i] == 1 && book[i] == 0) {
                    search.push(i);
                    book[i] = 1;
                }
            }
        }
    }
    /**
     * 判斷一個點是否是割點的方法
     * @param low low數(shù)組,存儲當前節(jié)點在不經(jīng)過父節(jié)點的情況下能夠訪問節(jié)點的最小時間戳
     * @param edges 邊的鄰接矩陣
     * @param n 頂點個數(shù)
     * @param num 時間戳數(shù)組
     * @param start 起始點
     * @return
     */
    public static int judgeCutPoint(int[] low, int[][] edges, int n, int[] num, int start) {
        for (int i = 1; i <= n; i++) { // 依次計算節(jié)點i的low[i]值
            int parent = findParent(i, num, n);
            // 排除掉父節(jié)點的情況下,使用dfs遍歷,將遍歷到的時間戳值用更小的替換大的時間戳值,不斷更新low[i]的值
            dfsExParent(low, i, parent, edges, num, parent);
            if (i != start && low[i] >= num[parent]) { // 要注意排除起始點,否則程序在第一個點處就返回了,顯然這是不對的
                return i;
            }
        }
        return 0;
    }
    /**
     * 根據(jù)時間戳數(shù)組找一個節(jié)點的父節(jié)點
     * @param i 當前節(jié)點編號
     * @param num 時間戳數(shù)組
     * @param n 頂點個數(shù)
     * @return 正確情況下返回父節(jié)點的編號,如果返回為0表示出錯了!
     */
    public static int findParent(int i, int[] num, int n) {
        if (num[i] == i) {
            return i;
        } else {
            int parent = num[i] - 1; // 父節(jié)點的時間戳為當前的時間戳減一
            for (int j = 1; j <= n; j++) {
                if (num[j] == parent) {
                    return j;
                }
            }
            return 0; // 是一種出錯的返回
        }
    }
    /**
     * 計算num數(shù)組的dfs方法
     * @param start 起始點
     * @param edges 邊數(shù)組
     * @param num num數(shù)組
     * @param n 頂點個數(shù)
     */
    public static void dfs(int start, int[][] edges, int[] num, int n) {
        int[] book = new int[n + 1]; // 用于判斷一個頂點是否已經(jīng)被放到棧里面去過了,避免環(huán)引起的錯誤
        int number = 1; // 被訪問到的編號
        Stack<Integer> search = new Stack<Integer>();
        search.push(start);
        book[start] = 1;
        while (!search.isEmpty()) {
            // 首先從棧頂去一個元素,并將這個頂點相連的頂點入棧
            int top = search.pop();
            num[top] = number; // 為num數(shù)組賦值
            number++;
            for (int i = 1; i <=n; i++) {
                if (edges[top][i] == 1 && book[i] == 0) {
                    search.push(i);
                    book[i] = 1;
                }
            }
        }
    }
}

//***********實踐證明:這種存儲方式在某些情況下比較優(yōu)化,但是由于dfs便于找到下一個頂點,最好還是用鄰接矩陣*******************
/**
 * 表示一條邊的對象
 * @author XZP
 *
 */
class Edge {
    private int v1;
    private int v2;
    public Edge(int v1, int v2) {
        this.v1 = v1;
        this.v2 = v2;
    }
    // getter
    public int getV1() {
        return v1;
    }
    public int getV2() {
        return v2;
    }
}

*********************************************************更新*******************************************************
昨天到后面沒啥效率了,今早更新一下一個小bug(所以效率才是生產(chǎn)力,囧,今早三分鐘搞定,昨天實在寫的有些疲乏了),代碼:

package cut.point;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;

import javax.swing.text.AsyncBoxView.ChildState;

/**
 * "轟炸重要城市"問題:
 * 假設(shè)當前我們擁有一個 地區(qū)的城市地圖,但是只有一個原子彈,為了讓這顆原子彈發(fā)揮最大的效果,要阻斷這個地區(qū)各個城市中最關(guān)鍵的一個交通要塞,那么這個原子彈該投放在哪里?
 * 其實真有原子彈就不會考慮這么多了(~……~),扯回正題。這種問題模型化之后,就是讓我們從一個無向連通圖中選擇一個“割點”,去掉這個點之后,不再是連通圖。
 * 關(guān)鍵詞:DFS、割點、無向連通圖、重要城市
 * 特殊說明:有些方法的參數(shù)比較多,主要是這次不想用一些全局靜態(tài)變量來處理,所以就全部通過傳參解決!
 * @author XZP
 *一組測試數(shù)據(jù):
6 7 
1 4 
1 3 
4 2
3 2
2 5 
2 6
5 6
 */
public class BombingImportantCity {

    public static void main(String[] args) {
        int INF = 99999; // 人為設(shè)定的最大值
        Scanner sc = new Scanner(System.in);
        int i; // 游標
        int v1, v2; //暫時存儲邊兩個頂點的編號
        int n = sc.nextInt(); // 頂點數(shù)
        int m = sc.nextInt(); // 邊的條數(shù)
//      Edge[] edges = new Edge[m + 1]; // 存儲邊的數(shù)組,下標從1開始
        int[][] edges = new int[n + 1][n +1]; // 存放邊的鄰接矩陣
        int[] num = new int[n + 1]; // 存儲第一遍從頂點1dfs遍歷情況下的時間戳,數(shù)組下標代表頂點編號,值表示第幾個訪問到(時間戳)
        int[] low = new int [n + 1]; // 表示每個頂點在不經(jīng)過父頂點能回到的最小時間戳(比較拗口,大致可以理解為:根據(jù)num數(shù)組找到父節(jié)點,然后用dfs遍歷,能夠達到的最小時間戳)
        // 初始化low數(shù)組,比較重要
        for (i = 1; i <= n; i++) {
            low[i] = INF;
        }
        
        // 讀入m條邊
        for (i =1; i <= m; i++) {
            v1 = sc.nextInt();
            v2 = sc.nextInt();
            edges[v1][v2] = 1; // 其他都為零表示兩點之間不可達或者是自身
            edges[v2][v1] = 1; // 無向圖對稱
        }
        // 求num數(shù)組(每個頂點對應(yīng)的時間戳)
        dfs(1, edges, num, n, low);
        
        // 得到割點
        int cutPoint = judgeCutPoint(low, edges, n, num, 1);
        if (cutPoint == 0) {
            System.out.println("沒有割點!");
        } else {
            System.out.println("割點編號為: " + cutPoint);
        }
    }
    /**
     * 以當前節(jié)點為起始點排除父節(jié)點的情況下深度優(yōu)先遍歷以獲取當前點能訪問的最小時間戳
     * @param low 待計算、更新的最小訪問時間戳
     * @param child 當前節(jié)點
     * @param parent 當前節(jié)點的父節(jié)點(根據(jù)num矩陣來的)
     * @param edges 邊的鄰接矩陣
     * @param num 時間戳矩陣
     * @param n 頂點個數(shù)
     */
    public static void dfsExParent(int[] low, int child,int parent, int[][] edges, int[] num, int n, int start) {
        int[] book = new int[n + 1]; // 用于判斷一個頂點是否已經(jīng)被放到棧里面去過了,避免環(huán)引起的錯誤
        Stack<Integer> search = new Stack<Integer>();
        search.push(child);
        book[child] = 1;
        while (!search.isEmpty()) {
            // 首先從棧頂去一個元素,并將這個頂點相連的頂點入棧
            int top = search.pop();
            // 用更小的時間戳替換掉
            if (num[top] < low[child]) {
                low[child] = num[top];
            }
            for (int i = 1; i <=n; i++) {
                if (i !=parent && edges[top][i] == 1 && book[i] == 0) { // 不經(jīng)過父節(jié)點訪問其他子節(jié)點不等同于不訪問父節(jié)點?。?!
                    search.push(i);
                    book[i] = 1;
                }
            }
        }
    }
    /**
     * 判斷一個點是否是割點的方法
     * @param low low數(shù)組,存儲當前節(jié)點在不經(jīng)過父節(jié)點的情況下能夠訪問節(jié)點的最小時間戳
     * @param edges 邊的鄰接矩陣
     * @param n 頂點個數(shù)
     * @param num 時間戳數(shù)組
     * @param start 起始點
     * @return
     */
    public static int judgeCutPoint(int[] low, int[][] edges, int n, int[] num, int start) {
        for (int i = 1; i <= n; i++) { // 依次計算節(jié)點i的low[i]值
            int parent = findParent(i, num, n);
            // 排除掉父節(jié)點的情況下,使用dfs遍歷,將遍歷到的時間戳值用更小的替換大的時間戳值,不斷更新low[i]的值
            dfsExParent(low, i, parent, edges, num, n, start);
            if (low[i] >= num[parent]) { // 要注意排除起始點,否則程序在第一個點處就返回了,顯然這是不對的
                // 還要判斷是否是根節(jié)點
                if (parent != start) {
                    return parent;
                } else {
                    // 判斷根節(jié)點且至少有兩個孩子,這兩個孩子沒有其他可達到的的其他路徑的情況下才是割點
                    if (isRootCutPoint(edges, start, n)) {
                        return start;
                    }
                }
            }
        }
        return 0;
    }
    public static boolean isRootCutPoint(int[][] edges, int root, int n) {
        boolean flag = false;
        List<Integer> child = new ArrayList<>();
        // 計算孩子數(shù)
        for (int i = 1; i <= n; i++) {
            if (edges[root][i] == 1) {
                child.add(i);
            }
        }
        int size = child.size();
        for (int i = 0; i< size; i++) {
            for (int j = i + 1; j < size; j ++) {
                if (!isReachable(child.get(i), child.get(j), edges, n, root)) {
                    return true;
                }
            }
        }
        return flag;
    }
    public static boolean isReachable(int a, int b, int[][] edges, int n, int root) {
        int[] book = new int[n + 1]; // 用于判斷一個頂點是否已經(jīng)被放到棧里面去過了,避免環(huán)引起的錯誤
        Stack<Integer> search = new Stack<Integer>();
        search.push(a);
        book[a] = 1;
        while (!search.isEmpty()) {
            // 首先從棧頂去一個元素,并將這個頂點相連的頂點入棧
            int top = search.pop();
            if (top == b) {
                return true;
            }
            for (int i = 1; i <=n; i++) {
                if (i != root && edges[top][i] == 1 && book[i] == 0) {
                    search.push(i);
                    book[i] = 1;
                }
            }
        }
        return false;
    }
    /**
     * 根據(jù)時間戳數(shù)組找一個節(jié)點的父節(jié)點
     * @param i 當前節(jié)點編號
     * @param num 時間戳數(shù)組
     * @param n 頂點個數(shù)
     * @return 正確情況下返回父節(jié)點的編號,如果返回為0表示出錯了!
     */
    public static int findParent(int i, int[] num, int n) {
        if (num[i] == i) {
            return i;
        } else {
            int parent = num[i] - 1; // 父節(jié)點的時間戳為當前的時間戳減一
            for (int j = 1; j <= n; j++) {
                if (num[j] == parent) {
                    return j;
                }
            }
            return 0; // 是一種出錯的返回
        }
    }
    /**
     * 計算num數(shù)組的dfs方法
     * @param start 起始點
     * @param edges 邊數(shù)組
     * @param num num數(shù)組
     * @param n 頂點個數(shù)
     */
    public static void dfs(int start, int[][] edges, int[] num, int n, int[] low) {
        int[] book = new int[n + 1]; // 用于判斷一個頂點是否已經(jīng)被放到棧里面去過了,避免環(huán)引起的錯誤
        int number = 1; // 被訪問到的編號
        Stack<Integer> search = new Stack<Integer>();
        search.push(start);
        book[start] = 1;
        while (!search.isEmpty()) {
            // 首先從棧頂去一個元素,并將這個頂點相連的頂點入棧
            int top = search.pop();
            num[top] = number; // 為num數(shù)組賦值
            low[top] = number; // 最開始就是自己
            number++;
            for (int i = 1; i <=n; i++) {
                if (edges[top][i] == 1 && book[i] == 0) {
                    search.push(i);
                    book[i] = 1;
                }
            }
        }
    }
}

//***********實踐證明:這種存儲方式在某些情況下比較優(yōu)化,但是由于dfs便于找到下一個頂點,最好還是用鄰接矩陣*******************
/**
 * 表示一條邊的對象
 * @author XZP
 *
 */
class Edge {
    private int v1;
    private int v2;
    public Edge(int v1, int v2) {
        this.v1 = v1;
        this.v2 = v2;
    }
    // getter
    public int getV1() {
        return v1;
    }
    public int getV2() {
        return v2;
    }
}

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

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

  • 注:都是在百度搜索整理的答案,如有侵權(quán)和錯誤,希告知更改。 一、哪些情況下的對象會被垃圾回收機制處理掉 ?當對象對...
    Jenchar閱讀 3,311評論 3 2
  • 01 最近在十點讀書會,共讀模塊中看了一本書《查令十字街84號》。一共是十天讀一本書,看完后很感動,腦海中也一直出...
    r染染閱讀 685評論 0 0
  • 1.今日事今日畢,迴向給自己,建立對自己的信任感。為自己點贊。 學(xué)堂學(xué)新詩《古朗月行》;新家儲藏間置物架安裝完畢;...
    李豪亮閱讀 104評論 0 0
  • 小雅急匆匆走在路上,一輛摩托車從身邊疾馳而過,小雅還在嘮叨著:“開這么快,不怕撞著人啊?!?突然,前方傳來一個急剎...
    王蓮荷閱讀 325評論 0 1

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