【算法】遍歷1到N的全排列

前言

昨天去面試,遇到一個筆試題。題目大體意思是N男的,N個女的,他們是N對夫妻。他們述說本身或別人是否爲夫妻關係,而這些敘述都是假的。求這N對男女的正確夫妻關係。c++

看到題目後立刻想到了解題思路,生成1到N的全排列,每個排列爲一個長度爲N的數組A。若是A[i]==k,則表示第i個男人與第k個女人爲夫妻關係。若是這些男女的敘說與數組A中的相應值都不匹配,這數組A是一個可行解。面試

解題思路有了,結果在生成1到N的全排列時掉鏈子了,想了一下沒想出來,感受一會兒也想不出來。而後也想了一個稍笨的辦法,但感受代碼亮會更大些,試卷空間可能也不夠,最終題目沒作了。感受這類題屬於基礎題,沒作出來很是不舒服,很是恥辱。回來後從新作了一下。數組

解決思路

問題化簡思路

問題的一個重要化簡思路爲:1到N的全排列 == 1到N-1的全排列中每一個排列Pi進行如下操做的結果集合的並集:函數

在Pi的最後一位(N-1位)以後插入數字N;在Pi的N-2位以後插入數字N;... 在Pi的1位以後插入數字N, 在Pi的1位以前插入數字N設計

遍歷全排列

想到了問題化簡思路繼續想:筆試時主要是想生成全排列,當時想錯了,受男女兩類人影響把全排列數目想成了A(N, 2),已經感生成全排列太佔空間。如今再一想,全排列數目有 N! 個,很大的數目.而解決問題不須要生成全排列,只須要遍歷全排列就行了。而且,若能遍歷全排列,在此基礎上生成全排列也是垂手可得的事情。code

問題轉變爲遍歷全排列。遞歸

具體解決過程

(1)要遍歷全排列,要節省空間考慮只有一個數組A。
(2)獲得1到n-1的第一個排列項以後,把A[n]設n,而後依次與A[1]、A[2]...A[n-1]交換獲得1到n的全排列中的一項。每個1到n-1的排列項能夠獲得n個1到n的排列項。
(3)爲防止混亂,每次交換A[n]與A[i]後,都要交換回去。而後在進行下一次的A[n]與A[i+1]交換。
(4)設計遍歷函數nextN,每調用一次講數組A設置爲以前沒有設置過的排列項,並返回true,表示獲取一個排列項成功。遍歷完成後(全部可能都設置過以後),函數返回false,表示沒有其餘排列項了,遍歷完成。
(5.1)nextN函數在執行(2)時,須要一個變量i,記錄當前交換到第幾個了。
(5.2)nextN函數在執行(2)生成1到n的一個排列項時,須要得到一個1到n-1的排列項。採用遞歸調用nextN實現傳入參數n表示獲得1到n排列項的下一個,傳入參數n-1表示獲得1到n-1排列項的下一個。
(5.3)nextN函數的遞歸有n+1層,最外層傳入參數n, 最底層傳入參數0直接返回false;
(5.4)在(5.1)中不但最外層的nextN須要變量i記錄交換到第幾個,除了最底層的每一層遞歸函數都要有一個變量i進行記錄,各層的變量i相互獨立。因爲調用的是同一個函數,不能採用內部的static變量,考慮在函數外定義一個iN[n]數字專門記錄,iN[n]爲最外層i。class

最後的函數實現

寫完以後感受代碼好短?。基礎

bool nextN(int n, int iN[], int a[]){
    if(n == 0){
        return false;
    }
    int &i = iN[n];
    
    if(i > n){ 
        if(nextN(n-1, iN, a)){ 
            i = 1;
        }else{
            return false;  
        }
    }
    
    if(i != 1){
        a[i-1] = a[n];
    }
    a[n] = a[i];
    a[i] = n;
    
    i++;
    return true;
}

iN[0]和a[0]沒有使用。
調用前爲iN數組和a數組分別分配長度 > n+1的空間,並將iN的第iN[1]到iN[n]初始化爲 > n的值。變量

int main(int argc, const char * argv[]) {
    int a[6 + 1] ;
    int iN[6 + 1]= {0, 100, 100, 100, 100, 100, 100};
    while(nextN(3, iN, a)){
        std::cout << a[1] << ", " << a[2] << ", " << a[3] << ", "
                << a[4] << ", " << a[5] << ", " << a[6] << std::endl;
    }
    return 0;
}

兩個問題

(1)思考問題化簡時,因爲更直觀,想到的是在1到n-1的一個排列中各個位置插入n,獲取n個1到n的排列。程序實現時,因爲交換筆插上須要移動的數據更少,速度更快,想到和使用的是A[n],與前面各A[i]的交換。

兩種處理方式是不一樣,獲得的n個排列也不一樣。

但交換方式最終獲得的結果是正確的,交換方式和插入方式獲得的排列數的數目是相同的。兩個不一樣的1到n-1的排列數,各自利用交換方式獲得任意兩個1到n的排列數,這兩個排列數必定不相同。所以用交換方式獲得的全部排列數必定沒有重複。所以將全部可能的排列數都會遍歷一遍,獲得正確結果。

(2)將插入方式改爲交換方式可以獲得正確結果,那具體解決過程當中第(3)步的恢復現場能夠省略嗎?實驗顯示,不能夠。

相關文章
相關標籤/搜索