對陣列進行全排列是乙個比較常見問題,如果是乙個比較喜歡考演算法的公司(貌似一些大公司都比較喜歡考演算法),那麼估計就會考察應聘者這個全排列的問題了(就算不讓你編寫完整**,也會讓你描述大致的思路)。這個問題也難也難,說易也易,下面我就來對這個問題進行乙個比較全面的解析吧。如有遺漏,還望指正。
對於乙個給定的序列 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.字典序全排列演算法 對給定的字符集中的字元規定了乙個先後關係,在此基礎上規定兩個全排列的先後是從左到...