全排列演算法的全面解析

2021-08-21 13:17:35 字數 4520 閱讀 7211

對陣列進行全排列是乙個比較常見問題,如果是乙個比較喜歡考演算法的公司(貌似一些大公司都比較喜歡考演算法),那麼估計就會考察應聘者這個全排列的問題了(就算不讓你編寫完整**,也會讓你描述大致的思路)。這個問題也難也難,說易也易,下面我就來對這個問題進行乙個比較全面的解析吧。如有遺漏,還望指正。

對於乙個給定的序列 a = [a1, a2, a3, … , an],請設計乙個演算法,用於輸出這個序列的全部排列方式。

例如:a = [1, 2, 3]

輸出

[1

, 2, 3][1

, 3, 2][2

, 1, 3][2

, 3, 1][3

, 2, 1][3

, 1, 2

]

如果要按從小到大輸出呢?演算法又要怎麼寫?

我們知道全排列的含義就是乙個序列所有的排序可能性,那麼我們現在做這樣的乙個假設,假設給定的一些序列中第一位都不相同,那麼就可以認定說這些序列一定不是同乙個序列,這是乙個很顯然的問題。有了上面的這一條結論,我們就可以同理得到如果在第一位相同,可是第二位不同,那麼在這些序列中也一定都不是同乙個序列,這是由上一條結論可以獲得的。

那麼,這個問題可以這樣來看。我們獲得了在第乙個位置上的所有情況之後,抽去序列t中的第乙個位置,那麼對於剩下的序列可以看成是乙個全新的序列t1,序列t1可以認為是與之前的序列毫無關聯了。同樣的,我們可以對這個t1進行與t相同的操作,直到t中只乙個元素為止。這樣我們就獲得了所有的可能性。所以很顯然,這是乙個遞迴演算法。

例如下面這幅圖,就是第1個元素與其後面的所有其他元素進行交換的示意圖。

如果我們從中抽出第i個元素,將剩下的其餘元素進行上圖交換操作,將是如下示意圖。

基於上面的分析,我們知道這個可以採用遞迴式實現,實現**如下:

private

static

void

core

(int

array)

private

static

void

fullarray

(int

array, int

cursor, int

end) else}}

執行結果

[1

, 2, 3][1

, 3, 2][3

, 1, 2][3

, 2, 1][1

, 2, 3][1

, 3, 2

]

這個答案就有一些讓人匪夷所思了,為什麼會有幾組是重複的?為什麼第一位裡面沒有 2?

理論上,上面的**沒有問題,因為當我們迴圈遍歷序列中每一位時,都有繼續進行後面序列的遞迴操作。core()方法當然沒什麼問題,問題是出在fullarray()方法上了。很容易鎖定在了那個for迴圈裡。我們來仔細推敲一下迴圈體裡的**,當我們對序列進行交換之後,就將交換後的序列除去第乙個元素放入到下一次遞迴中去了,遞迴完成了再進行下一次迴圈。這是某一次迴圈程式所做的工作,這裡有乙個問題,那就是在進入到下一次迴圈時,序列是被改變了。可是,如果我們要假定第一位的所有可能性的話,那麼,就必須是在建立在這些序列的初始狀態一致的情況下(感興趣的你可以想想這是為什麼)。

好了,這樣一來問題找到了,我們需要保證序列進入下一次迴圈時狀態的一致性。而保證的方式就是對序列進行還原操作。我們修改fullarray()如下:

private

static

void

fullarray

(int

array, int

cursor, int

end) else}}

修改後的執行結果

[1

, 2, 3][1

, 3, 2][2

, 1, 3][2

, 3, 1][3

, 2, 1][3

, 1, 2

]

上面的程式乍一看沒有任何問題了。可是,如果我們對序列進行一下修改 array = .我們看看執行的結果會怎麼樣。

[1

, 2, 2][1

, 2, 2][2

, 1, 2][2

, 2, 1][2

, 2, 1][2

, 1, 2

]

這裡出現了好多的重複。重複的原因當然是因為我們列舉了所有位置上的可能性,而沒有太多地關注其真實的數值。

現在,我們這樣來思考一下,如果有乙個序列t = 。其中,a[i] = a[j]。那麼是不是就可以說,在a[i]上,只要進行一次交換就可以了,a[j]可以直接忽略不計了。好了,基於這樣乙個思路,我們對程式進行一些改進。我們每一次交換遞迴之前對元素進行檢查,如果這個元素在後面還存在數值相同的元素,那麼我們就可以跳過進行下一次迴圈遞迴(當然你也可以反著來檢查某個元素之前是不是相同的元素)。

基於這個思路,不難寫出改進的**。如下:

private

static

void

core

(int

array)

private

static

boolean

swapaccepted

(int

array, int

start, int

end)

}return

true

; }

private

static

void

fullarray

(int

array, int

cursor, int

end) else

arrayutils.swap(array, cursor, i);

fullarray(array, cursor + 1

, end);

arrayutils.swap(array, cursor, i); // 用於對之前交換過的資料進行還原}}

}

由於非遞迴的方法是基於對元素大小關係進行比較而實現的,所以這裡暫時不考慮存在相同資料的情況。

在沒有相同元素的情況下,任何不同順序的序列都不可能相同。不同的序列就一定會有大有小。也就是說,我們只要對序列按照一定的大小關係,找到某乙個序列的下乙個序列。那從最小的乙個序列找起,直到找到最大的序列為止,那麼就算找到了所有的元素了。

好了,現在整體思路是清晰了。可是,要怎麼找到這裡說的下乙個序列呢?這個下乙個序列有什麼性質呢?

t[i]下乙個序列t[i+1]是在所有序列中比t[i]大,且相鄰的序列。關於怎麼找到這個元素,我們還是從乙個例子來入手吧。

現在假設序列t[i] =,那麼我們可以通過如下兩步找到它的下乙個序列。

看完上面的兩個步驟,不知道大家有沒有理解。如果不理解,那麼不理解的點可能就在於替換點和被替點的尋找,以及之後為什麼又要進行反轉上。我們乙個乙個地解決問題吧。

public

class

demofullarray2

; core(array);

}private

static

void

core

(int

array) while

(!islast(array));

}private

static

intnextarray

(int

array)

}// 尋找在替換點後面的次小元素

intbiggercursor = cursor + 1

; for

(int

i = cursor + 1

; i < length; i++)

}// 交換

arrayutils.swap(array, cursor, biggercursor);

// 對替換點之後的序列進行反轉

reverse(array, cursor);

return

array;

}private

static

void

reverse

(int

array, int

cursor)

}private

static

boolean

islast

(int

array)

}return

true

; }

}

LeetCode演算法題46 全排列解析

給定乙個沒有重複數字的序列,返回其所有可能的全排列。示例 輸入 1,2,3 輸出 1,2,3 1,3,2 2,1,3 2,3,1 3,1,2 3,2,1 這個題突然讓我又對遞迴產生了新的認識,遞迴可以是不確定層數的巢狀迴圈。所以這個題還是用遞迴來解,思路還是深度優先搜尋,其實想還是很好想的,和之前那...

演算法 全排列

從n個不同元素中任取m m n 個元素,按照一定的順序排列起來,叫做從n個不同元素中取出m個元素的乙個排列。當m n時所有的排列情況叫全排列。用演算法分別實現全排列,其中n個元素儲存在乙個長度為n的陣列中。實現全排列之前,先看一下對進行全排列的一種方法 從圖中可以看出,我們首先從n個元素中取出乙個元...

全排列演算法

1.遞迴全排列 分別將每個位置交換到最前面位,之後全排列剩下的位。遞迴全排列 1 2 3 4 5 1,for迴圈將每個位置的資料交換到第一位 swap 1,1 5 2,按相同的方式全排列剩餘的位 2.字典序全排列演算法 對給定的字符集中的字元規定了乙個先後關係,在此基礎上規定兩個全排列的先後是從左到...