連通子圖問題(DFS的遞歸和非遞歸實現)

問題定義(以下均爲Java實現)

  輸入一個mmnn列的字符矩陣, 統計字符“@”組成多少個八連塊。 如果兩個字符“@”所在的格子相鄰( 橫、 豎或者對角線方向) , 就說它們屬於同一個八連塊。 例如, 下圖有33個八連塊。
在這裏插入圖片描述
解題思路:深/廣度優先遍歷,記錄已經遍歷過的字符。深度優先遍歷有不同的實現,下面是非遞歸(棧)或者遞歸的兩種解法。

解法一

  基於棧的DFS,當遍歷到某個字符時,入棧並標記該字符,然後繼續判斷該字符的相鄰字符,當該字符沒有相鄰字符爲“@”則出棧。代碼如下:

  1. 首先構造一個描述圖節點的類Dot,包含實例屬性r,c分別表示該字符所在行,所在列。其中關於get、set以及構造方法已經省略,此外爲了判斷是否已經遍歷過,重寫了equals和hashCode方法。
class Dot{
        private int r;
        private int c;
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Dot dot = (Dot) o;
            return getR() == dot.getR() &&
                    getC() == dot.getC();
        }

        @Override
        public int hashCode() {
            return Objects.hash(getR(), getC());
        }

    }
  1. 下面是算法實現:
      方法有一個參數二維數組graph,即字符圖。
      集合exists用於保存已經遍歷過的字符。首先是兩層循環(多個八連塊),遍歷到某一字符時,如果該字符爲“@”且未遍歷過(則意味着新的八連塊出現),則以當前節點爲根進行DFS,這裏聲明瞭一個棧,根節點首先入棧,並加入exists中,然後判斷其周圍是否有符合條件的字符,有則入棧並繼續進行DFS,否則執行出棧,最後直至棧爲空,則當前的一個八連塊完畢,繼續下一個八連塊,直至整個圖都遍歷完畢,程序最後返回圖中八連塊的個數。
    /**
     * @param graph 圖
     * @return 八連塊個數
     */
    private int solver1(char[][] graph){
        if(graph == null){
            return 0;
        }
        Set<Dot> exists = new HashSet<>();
        int rnum = graph.length;
        int cNum = graph[0].length;
        int count = 0;

        for(int i = 0;i<rnum;i++){
            for(int j = 0;j<cNum;j++){
                Dot dot = new Dot(i,j);
                if(graph[i][j] == '@' && !exists.contains(dot)){
                    Stack<Dot> stack = new Stack<>();
                    stack.push(dot);
                    exists.add(dot);
                    while(!stack.isEmpty()){
                        Dot cur = stack.peek();
                        int r1 = Math.max(0, cur.getR()-1);
                        int r2 = Math.min(cur.getR()+1, rnum-1);
                        int c1 = Math.max(0, cur.getC() - 1);
                        int c2 = Math.min(cur.getC()+1, cNum-1);
                        /* flag:當存在相鄰且未遍歷的字符“@”時,需要跳出循環*/
                        boolean flag = false;
                        for(int r = r1;r<=r2;r++){
                            for(int c = c1;c<=c2;c++){
                                Dot d = new Dot(r,c);
                                if(graph[r][c] == '@' && !exists.contains(d)){
                                    stack.push(d);
                                    exists.add(d);
                                    flag = true;
                                    break;
                                }
                            }
                            if(flag){
                                break;
                            }
                        }
                        /* 如果不存在相鄰且未遍歷的@字符,則出棧 */
                        if(cur == stack.peek()){
                            stack.pop();
                        }
                    }
                    count++;
                }
            }
        }
        return count;
    }

解法二

  基於遞歸的DFS,對於當前節點,首先判斷是否越界,是否爲“@”,是否已經遍歷過,否則符合條件,標記當前節點屬於哪個八連塊(這裏二維數組exists用於記錄節點屬於哪個八連塊),然後判斷周圍字符。

    /**
     *
     * @param graph 圖
     * @param r 字符縱座標
     * @param c 字符橫座標
     * @param id 八連塊序號
     * @param exists 判斷是否已經遍歷過
     */
    private void solver2(char[][] graph, int r, int c, int id, int[][] exists){
        if(r<0 || c<0 || r>=graph.length || c>=graph[0].length){
            return ;
        }
        if(exists[r][c] != 0 || !(graph[r][c]=='@')){
            return;
        }
        exists[r][c] = id;
        for(int i  = -1;i<2;i++){
            for(int j = -1;j<2;j++){
                if(i != 0 || j != 0){
                    solver2(graph, r+i, c+j, id, exists);
                }
            }
        }
    }

輔助輸入輸出的代碼如下所示:

public class OilDeposits {
	private int solver1(char[][] graph){{/*代碼在上面*/}
	private void dfs(char[][] graph, int r, int c, int id, int[][] exists){/*代碼在上面*/}
	public void solution(char[][] graph){
        System.out.println("解法一:" + solver1(graph));


        int[][] exists = new int[graph.length][graph[0].length];
        int count = 0;
        for(int i = 0;i<graph.length;i++){
            for(int j = 0;j<graph[0].length;j++){
                if(graph[i][j] == '@' && exists[i][j] == 0){
                    solver2(graph, i, j, ++count, exists);
                }
            }
        }
        System.out.println("解法二:" + count);
    }
    public static void main(String[] args){
        char graph[][] = {
                {'*','*','*','*','*','@',},
                {'*','*','@','@','*','@',},
                {'*','*','@','*','*','@',},
                {'@','@','@','*','*','@',},
                {'*','*','@','*','*','@',},
                {'*','*','*','*','*','@',},
                {'*','*','@','@','*','@',},};
        OilDeposits oilDeposits = new OilDeposits();
        oilDeposits.solution(graph);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章