1. 程式人生 > >dfs_素數環

dfs_素數環

1. 問題描述:

 輸入正整數n,對1-n進行排列,使得相鄰兩個數之和均為素數,
 輸出時從整數1開始,逆時針排列。同一個環應恰好輸出一次。
 n<=16

 如輸入:6
 輸出:
 1 4 3 2 5 6
 1 6 5 2 3 4

2. 我們這裡使用dfs來進行解決,嘗試走每一條線路,發現這條路徑不適合的話退回到上一層,繼續搜尋它的兄弟,這樣便會搜尋出每一條可能的路徑,在呼叫dfs之前需要進行剪枝,判斷這個數字原來是否使用過,而且沒有使用過的這個數字需要和陣列的上一個元素加起來之和判斷結果是否為素數(使用整形變數k記錄記錄到陣列中存放元素的數量可以知道陣列的上一個元素),假如是素數才使用dfs進行搜尋。整型變數k記錄了當前陣列中放了多少個元素,當陣列放置的元素的長度等於陣列一開始定義的長度,那麼此時應該return,而且在return之前判斷陣列中的首尾元素加起來是否為素數,假如是素數輸出陣列中的放置的素數環的結果,然後進行return(因為這個環相鄰兩個數字加起來都是素數)

我們在呼叫dfs的時候都需要考慮是否需要進行回溯,對於這道題目而言,可回溯也可以不回溯,因為在呼叫完某個dfs結束之後,那麼它仍然在for迴圈之中,嘗試其它可能的元素放入到當前層的元素,所以對當前層的陣列沒有什麼影響,因為新的可能的元素會覆蓋掉原來的元素,所以不需要進行回溯也可以,程式碼片段如下:

for(int i = 2; i <= n; i++){
            if(check(rec, i, k)){
                rec[k] = i;
                dfs(rec, k + 1);
                //不用回溯,因為回到這一層的時候對陣列沒有什麼影響,選擇的同一層的其他元素放入到rec[k], k沒有變化


            }
 }    

上面可以看出後面放的值會覆蓋掉原來的值,而且假如退回到這一層的時候其他的元素都不符合那麼退到再上一層發現合適的再dfs再把原來的不符合的數字給覆蓋掉,所以回不回溯都無所謂,從中我們知道並不是改變了陣列的元素都是需要回溯的,需要看看上一個狀態是否對進入到下一個狀態有影響,假如沒有影響那麼可以不用進行回溯

其中需要考慮傳入dfs方法中的引數,包括存放元素的陣列,記錄當前存放到陣列中元素的數量,方便對素數環的上一個元素和當前元素之和是否為素數的判斷,初始化陣列的第一個元素為1,

剩下來的就是一些輔助dfs的方法了,包括判斷是否為素數的方法和輸出陣列元素的方法了

3. 具體的程式碼如下:

import java.util.Scanner;
public class Main{
    static int n;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int rec[] = new int[n];
        rec[0] = 1;
        dfs(rec, 1);
    }

    private static void dfs(int[] rec, int k){
        if(k == n){
            if(isPrime(rec[0], rec[n - 1])){
                for(int i = 0; i < n; i++){
                    System.out.print(rec[i] + " ");
                }
                System.out.print("\n");
            }
            return;
        }
        for(int i = 2; i <= n; i++){
            if(check(rec, i, k)){
                rec[k] = i;
                dfs(rec, k + 1);
                //不用回溯,因為回到這一層的時候對陣列沒有什麼影響,選擇的同一層的其他元素放入到rec[k],k沒有變化

                //假如這一層的元素都不符合它會退到再上一層發現合適的數字dfs後會把原來的元素給覆蓋掉
            }
        }    
    }

    private static boolean check(int rec [], int cur, int k) {

        //判斷當前元素是否以前以前已經使用
        for(int i = 0; i < k; i++){
            if(rec[i] == cur){
                return false;
            }
        }
        //注意傳遞的是陣列的上一個元素,為rec[k - 1],千萬不能夠傳錯
        boolean flag = isPrime(cur, rec[k - 1]);
        if(flag){
            return true;
        }else{
            return false;
        }
    }
    //判斷相鄰兩數之和是否為素數
    private static boolean isPrime(int cur1, int cur2){
        int result = cur1 + cur2; 
        for(int i = 2; i * i <= result; i++){
            if(result % i == 0) return false;
        }
        return true;
    }
}

下面從控制檯輸入5,模擬其中深搜的過程:

                     1

             2                  

          3              

       4      

     5 

進入dfs(4),發現5不合適那麼這一層的dfs呼叫已經結束,那麼退到4這一層迴圈中的dfs中,此時i = 5但是5不適合繼續退回去,退回到3這一層,看一下4是否符合發現4符合不符合繼續下一個數字5,發現5符合繼續繼續dfs,但是發現後面無論如何都不能構成素數環那麼結束迴圈結束退回到2這一層,發現3不行,4可以那麼繼續進行下去...