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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

全排列算法的全面解析

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

概述

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


版權(quán)說明

著作權(quán)歸作者所有。
商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
本文作者:Q-WHai
發(fā)表日期: 2016年3月27日
本文鏈接:https://qwhai.blog.csdn.net/article/details/50986990
來源:CSDN
更多內(nèi)容:分類 >> 算法與數(shù)學(xué)


描述

對(duì)于一個(gè)給定的序列 a = [a1, a2, a3, … , an],請(qǐng)?jiān)O(shè)計(jì)一個(gè)算法,用于輸出這個(gè)序列的全部排列方式。
例如:a = [1, 2, 3]
輸出

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

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


基于遞歸的實(shí)現(xiàn)

思路分析

我們知道全排列的含義就是一個(gè)序列所有的排序可能性,那么我們現(xiàn)在做這樣的一個(gè)假設(shè),假設(shè)給定的一些序列中第一位都不相同,那么就可以認(rèn)定說這些序列一定不是同一個(gè)序列,這是一個(gè)很顯然的問題。有了上面的這一條結(jié)論,我們就可以同理得到如果在第一位相同,可是第二位不同,那么在這些序列中也一定都不是同一個(gè)序列,這是由上一條結(jié)論可以獲得的。
那么,這個(gè)問題可以這樣來看。我們獲得了在第一個(gè)位置上的所有情況之后,抽去序列T中的第一個(gè)位置,那么對(duì)于剩下的序列可以看成是一個(gè)全新的序列T1,序列T1可以認(rèn)為是與之前的序列毫無關(guān)聯(lián)了。同樣的,我們可以對(duì)這個(gè)T1進(jìn)行與T相同的操作,直到T中只一個(gè)元素為止。這樣我們就獲得了所有的可能性。所以很顯然,這是一個(gè)遞歸算法。
例如下面這幅圖,就是第1個(gè)元素與其后面的所有其他元素進(jìn)行交換的示意圖。

如果我們從中抽出第i個(gè)元素,將剩下的其余元素進(jìn)行上圖交換操作,將是如下示意圖。

所有元素均無相同的情況

基于上面的分析,我們知道這個(gè)可以采用遞歸式實(shí)現(xiàn),實(shí)現(xiàn)代碼如下:

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);}}}

運(yùn)行結(jié)果

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

這個(gè)答案就有一些讓人匪夷所思了,為什么會(huì)有幾組是重復(fù)的?為什么第一位里面沒有 2?
理論上,上面的代碼沒有問題,因?yàn)楫?dāng)我們循環(huán)遍歷序列中每一位時(shí),都有繼續(xù)進(jìn)行后面序列的遞歸操作。core()方法當(dāng)然沒什么問題,問題是出在fullArray()方法上了。很容易鎖定在了那個(gè)for循環(huán)里。我們來仔細(xì)推敲一下循環(huán)體里的代碼,當(dāng)我們對(duì)序列進(jìn)行交換之后,就將交換后的序列除去第一個(gè)元素放入到下一次遞歸中去了,遞歸完成了再進(jìn)行下一次循環(huán)。這是某一次循環(huán)程序所做的工作,這里有一個(gè)問題,那就是在進(jìn)入到下一次循環(huán)時(shí),序列是被改變了。可是,如果我們要假定第一位的所有可能性的話,那么,就必須是在建立在這些序列的初始狀態(tài)一致的情況下(感興趣的你可以想想這是為什么)。
好了,這樣一來問題找到了,我們需要保證序列進(jìn)入下一次循環(huán)時(shí)狀態(tài)的一致性。而保證的方式就是對(duì)序列進(jìn)行還原操作。我們修改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); // 用于對(duì)之前交換過的數(shù)據(jù)進(jìn)行還原}}}

修改后的運(yùn)行結(jié)果

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

存在相同元素的情況

上面的程序乍一看沒有任何問題了。可是,如果我們對(duì)序列進(jìn)行一下修改 array = {1, 2, 2}.我們看看運(yùn)行的結(jié)果會(huì)怎么樣。

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

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

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); // 用于對(duì)之前交換過的數(shù)據(jù)進(jìn)行還原}}}

基于非遞歸的實(shí)現(xiàn)

思路分析

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

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

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

邏輯實(shí)現(xiàn)

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;// 尋找替換點(diǎn)int cursor = 0;for (int i = length - 1; i >= 1; i--) {// 找到第一個(gè)遞增的元素對(duì)if (array[i - 1] < array[i]) {cursor = i - 1; // 找到替換點(diǎn)break;}}// 尋找在替換點(diǎn)后面的次小元素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);// 對(duì)替換點(diǎn)之后的序列進(jìn)行反轉(zhuǎn)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這款在線繪圖工具,可以使用如下邀請(qǐng)鏈接進(jìn)行注冊(cè):
https://www.processon.com/i/56205c2ee4b0f6ed10838a6d

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。