日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

全排列算法的全面解析

發布時間:2025/3/20 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 全排列算法的全面解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述

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


版權說明

著作權歸作者所有。
商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
本文作者:Q-WHai
發表日期: 2016年3月27日
本文鏈接:https://qwhai.blog.csdn.net/article/details/50986990
來源:CSDN
更多內容:分類 >> 算法與數學


描述

對于一個給定的序列 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) {int length = array.length;fullArray(array, 0, length - 1);}private static void fullArray(int[] array, int cursor, int end) {if (cursor == end) {System.out.println(Arrays.toString(array));} else {for (int i = cursor; i <= end; i++) {ArrayUtils.swap(array, cursor, i);fullArray(array, cursor + 1, end);}}}

運行結果

[1, 2, 3] [1, 3, 2] [3, 1, 2] [3, 2, 1] [1, 2, 3] [1, 3, 2]

這個答案就有一些讓人匪夷所思了,為什么會有幾組是重復的?為什么第一位里面沒有 2?
理論上,上面的代碼沒有問題,因為當我們循環遍歷序列中每一位時,都有繼續進行后面序列的遞歸操作。core()方法當然沒什么問題,問題是出在fullArray()方法上了。很容易鎖定在了那個for循環里。我們來仔細推敲一下循環體里的代碼,當我們對序列進行交換之后,就將交換后的序列除去第一個元素放入到下一次遞歸中去了,遞歸完成了再進行下一次循環。這是某一次循環程序所做的工作,這里有一個問題,那就是在進入到下一次循環時,序列是被改變了??墒?#xff0c;如果我們要假定第一位的所有可能性的話,那么,就必須是在建立在這些序列的初始狀態一致的情況下(感興趣的你可以想想這是為什么)。
好了,這樣一來問題找到了,我們需要保證序列進入下一次循環時狀態的一致性。而保證的方式就是對序列進行還原操作。我們修改fullArray()如下:

private static void fullArray(int[] array, int cursor, int end) {if (cursor == end) {System.out.println(Arrays.toString(array));} else {for (int i = cursor; i <= end; i++) {ArrayUtils.swap(array, cursor, i);fullArray(array, cursor + 1, end);ArrayUtils.swap(array, cursor, i); // 用于對之前交換過的數據進行還原}}}

修改后的運行結果

[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] [1, 2, 2] [2, 1, 2] [2, 2, 1] [2, 2, 1] [2, 1, 2]

這里出現了好多的重復。重復的原因當然是因為我們列舉了所有位置上的可能性,而沒有太多地關注其真實的數值。
現在,我們這樣來思考一下,如果有一個序列T = {a1, a2, a3, …, ai, … , aj, … , an}。其中,a[i] = a[j]。那么是不是就可以說,在a[i]上,只要進行一次交換就可以了,a[j]可以直接忽略不計了。好了,基于這樣一個思路,我們對程序進行一些改進。我們每一次交換遞歸之前對元素進行檢查,如果這個元素在后面還存在數值相同的元素,那么我們就可以跳過進行下一次循環遞歸(當然你也可以反著來檢查某個元素之前是不是相同的元素)。
基于這個思路,不難寫出改進的代碼。如下:

private static void core(int[] array) {int length = array.length;fullArray(array, 0, length - 1);}private static boolean swapAccepted(int[] array, int start, int end) {for (int i = start; i < end; i++) {if (array[i] == array[end]) {return false;}}return true;}private static void fullArray(int[] array, int cursor, int end) {if (cursor == end) {System.out.println(Arrays.toString(array));} else {for (int i = cursor; i <= end; i++) {if (!swapAccepted(array, cursor, i)) {continue;}ArrayUtils.swap(array, cursor, i);fullArray(array, cursor + 1, end);ArrayUtils.swap(array, cursor, i); // 用于對之前交換過的數據進行還原}}}

基于非遞歸的實現

思路分析

由于非遞歸的方法是基于對元素大小關系進行比較而實現的,所以這里暫時不考慮存在相同數據的情況。
在沒有相同元素的情況下,任何不同順序的序列都不可能相同。不同的序列就一定會有大有小。也就是說,我們只要對序列按照一定的大小關系,找到某一個序列的下一個序列。那從最小的一個序列找起,直到找到最大的序列為止,那么就算找到了所有的元素了。
好了,現在整體思路是清晰了。可是,要怎么找到這里說的下一個序列呢?這個下一個序列有什么性質呢?
T[i]下一個序列T[i+1]是在所有序列中比T[i]大,且相鄰的序列。關于怎么找到這個元素,我們還是從一個例子來入手吧。
現在假設序列T[i] = {6, 4, 2, 8, 3, 1},那么我們可以通過如下兩步找到它的下一個序列。

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

  • 替換點和被替換點的尋找。替換點是從整個序列最后一個位置開始,找到一個連續的上升的兩個元素。前一個元素的index就是替換點。再從替換點開始,向后搜尋找到一個只比替換點元素大的被替換點。(如果這里你不是很理解,可以結合圖形多思考思考。)
  • 替換點后面子序列的反轉。在上一步中,可以看到替換之后的子序列({8, 2, 1})是一個遞減的序列,而替換點又從小元素換成 了大元素,那么與之前序列緊相鄰的序列必定是{8, 2, 1}的反序列,即{1, 2, 8}。
    這樣,思路已經完全梳理完了,現在就是對其的實現了。只是為了防止給定的序列不是最小的,那就需要對其進行按從小到大進行排序。

邏輯實現

public class DemoFullArray2 {public static void main(String[] args) {int[] array = {2, 3, 1, 4};core(array);}private static void core(int[] array) {// 先排序SortUtils sortUtils = new SortUtils(new QKSort()); sortUtils.sort(array);System.out.println(Arrays.toString(array)); // 最初始的序列do {nextArray(array);System.out.println(Arrays.toString(array));} while (!isLast(array));}private static int[] nextArray(int[] array) {int length = array.length;// 尋找替換點int cursor = 0;for (int i = length - 1; i >= 1; i--) {// 找到第一個遞增的元素對if (array[i - 1] < array[i]) {cursor = i - 1; // 找到替換點break;}}// 尋找在替換點后面的次小元素int biggerCursor = cursor + 1;for (int i = cursor + 1; i < length; i++) {if (array[cursor] < array[i] && array[i] < array[biggerCursor]) {biggerCursor = i;}}// 交換ArrayUtils.swap(array, cursor, biggerCursor);// 對替換點之后的序列進行反轉reverse(array, cursor);return array;}private static void reverse(int[] array, int cursor) {int end = array.length - 1;for (int i = cursor + 1; i <= end; i++, end--) {ArrayUtils.swap(array, i, end);}}private static boolean isLast(int[] array) {int length = array.length;for (int i = 1; i < length; i++) {if (array[i - 1] < array[i]) {return false;}}return true;} }

Ref

  • http://blog.csdn.net/morewindows/article/details/7370155

征集

如果你也需要使用ProcessOn這款在線繪圖工具,可以使用如下邀請鏈接進行注冊:
https://www.processon.com/i/56205c2ee4b0f6ed10838a6d

總結

以上是生活随笔為你收集整理的全排列算法的全面解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。