全排列算法的JS實現


問題描述:給定一個字符串,輸出該字符串所有排列的可能。如輸入“abc”,輸出“abc,acb,bca,bac,cab,cba”。

雖然原理很簡單,然而我還是折騰了好一會才實現這個算法……這里主要記錄的是解決問題中的思路。

我實現的是最普通的遞歸算法,也沒有除重,嗯非遞歸及除重的算法以后再補上吧。

 

實現過程

首先明確函數的輸入和輸出,輸入是一個字符串,輸出么對於JS而言用數組來表示最恰當了,所以函數的雛形應該是這樣的:

function permutate(str) {
    var result = [];

    return result;
}

 然后,確定是用遞歸的形式解決。遞歸的解法么,其實就是數學歸納法的尋找規律那一步。數學歸納法是什么樣來着:第一步,給出基礎值,比如輸入為1的時候輸出應該是成立的。第二步,假設對於輸入n成立,證明輸入n+1時也成立。

好了,所以先來完成第一步。對這個問題而言,基礎情況應該是輸入字符串為單個字符時的情況。這個時候輸出應該是什么呢。當然是輸入本身。但是,不要忘了輸出應該是數組形式,所以接下來的樣子:

function permutate(str) {
    var result = [];

    if(str.length > 1) {
} else if (str.length == 1) { return [str]; } return result; }

 中間用了else if 而沒有用else的原因是不清楚到最后是否要處理空字符串的情況,所以先留着個else。邏輯上應該位於第一個if里的return語句,放到了最后,比較清晰。

接着進行第二步,假設我們已經知道了n-1的輸出,要由這個輸出得出n的輸出。在這個問題里,n-1的輸入,對應着長度比當前輸入的字符串少1的輸入字符串。也就是說,如果我已經知道了“abc”的全排列輸出的集合,現在再給你一個“d”,要怎樣得出新的全排列呢?

很簡單,只要對於集合中每一個元素,把d插入到任意相鄰字母之間(或者頭部和尾部),就可以得到一個新的排列。例如對於元素“acb”,插入到第一個位置,即可得到“dacb”,插入其余位置,可得到“adcb”,“acdb”,“acbd”。容易證明這樣形成的新元素不會有重復。

在這里,對於每一個輸入的str,我們把它分為兩部分,第一部分為字符串的第一個字母,定義為left,第二部分為剩余的字符串,定義為rest,根據以上的假設,現在可以把 permutate(rest) 作為一個已知量看待。

function permutate(str) {
    var result = [];

    if(str.length > 1) {
        var left = str[0];
        var rest = str.slice(1, str.length);
        var preResult = permutate(rest);
        /*
          Do some operation
        */
    } else if (str.length == 1) {
        return [str];
    }

    return result;
}

接着對permutate(rest)里的每一個排列進行處理,將left插入到每一個位置中,每得到一個排列,便將它push到result里面去。

......
    for(var i=0; i<preResult.length; i++) {
        for(var j=0; j<preResult[i].length; j++) {
            var tmp = preResult[i],slice(0, j) + left + preResult[i].slice(j, preResult[i].length);
            result.push(tmp);
        }
    }
......

 有了字符串自帶的slice方法省了不少事。一開始想到插入字符串想到的是splice方法,然而這個方法會對原始字符串進行修改,要是用它的話會出很無奈的bug……

到這里就告一段落了,把上面的片段插入到前面的注釋的位置就是完整代碼了。

然后這個函數對於空字符串的輸入會輸出空字符串,所以前面else if的if也可以去掉。

 

另一個問題

說起全排列我還想到了另一個問題:

給定一個數字字符串,輸出組成這個字符串的每個數字的所有組合中,比當前數字大的下一個數字。

舉例:對於輸入“113”,它的所有不重復的排列組合為“113”,“131”,“311”,那么其中比當前數字大的下一個數字為“131”,所以輸出為“131”。

我想到的第一個解法,首先求出當前字符串的所有排列組合,然后對返回的結果排序,再求出當前數字所在下標的下一個。

程序的樣子差不多應該是這樣:

function permutate() {
	//......
	return result;
}

function main(str) {
	var all = permutate().sort();
	var targetIndex = all.indexOf(str)+1;
	return result[targetIndex];
}

 思路非常直接,然而這個算法的缺點也是很明顯的:復雜度太高了。對於輸入長度為 n 的字符串,光是排列算法就得計算 n!次。

所以這個想法可以pass。

實際上比較正確的算法是可以在O(n)的復雜度內求出結果的。不過在這里就不詳細說明了。各種解決途徑可以點擊以下鏈接查看:

鏈接 (注:注冊了codewars賬號並給出一種解法后方可查看對應的solution)

之所以提到這個問題,是因為雖然不推薦使用全排列的方法解決這個問題,但是我們可以通過這個問題的解法,反過來給出全排列的一種非遞歸式解法。

例如我們要給出字符串“aabccdde"的全排列,可以把對應字母替換為“11233445”,然后調用上面問題的解法,依次輸出每一個排列即可。因為本身這個算法的復雜度很低,所以不會影響到最終全排列算法的復雜度。

缺點么,如果有十個以上的不同字符,那就沒有辦法了……

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM