全排列演算法分析(原創方法/一般方法/字典序法)

來源:互聯網
上載者:User

標籤:blog   http   java   strong   os   資料   

全排列演算法即對給定的一個序列,輸出其所有不同的(n!種)排列,例如:

給定序列{1, 2, 3}有{1, 2, 3}、{1, 3, 2}、{2, 1, 3}、{2, 3, 1}、{3, 1, 2}、{3, 2, 1}這6種排列

好像很容易就能寫出來,對於更長的序列也只是時間問題,最終肯定能夠用筆一一列出來

但是要用程式實現的話,可能讓人有點無從下手(乍看好像很簡單),下面給出三種不同的解全排列的方法:

-------

一.原創方法

所謂的原創方法就是不考慮演算法的效率及其他因素,完全為瞭解決問題而自己去構造一種可行的方法(不一定好,但能解決問題)

首先,讓我們想想自己是如何寫出上面的6個序列的

1.確定第一位上的元素,有三種可能:1, 2, 3

2.確定第二位上的元素(以1為例),有兩種可能:2, 3

3.確定第三位上的元素(以2為例),只有一種可能:3。這時我們就得到了一個序列:1, 2, 3

接下來,讓我們理一理思路,想想用程式如何?

對於一個長度為n的序列來說,電腦要做的就是

1.確定第(i)位上的元素,有(n + 1 - i)種可能,依次選擇一種可能作為第i位的元素,儲存當前已經確定的序列(長度為i)

2.確定第(i+1)位上的元素,有(n + 1 - i - i)種可能,依次選擇一種可能作為第i + 1位的元素,儲存當前已經確定的序列(長度為i + 1)

……

n.確定第(n)位上的元素,只有一種可能,將唯一的可能元素作為第n位上的元素,輸出已經確定的序列

顯然,這是一種遞迴的方法

Java代碼如下:

public class FullPermutation {static int count = 0;/** * 遞迴實現全排列 * <br />程式思路是:依次確定第一位到最後一位,與人的一般思維方式一致 */public static void main(String[] args) {String str = "13234";str = check(str);//去除重複元素fullPermutate(0, "", str);System.out.print(count);}/** * @param index 本次調用確定第index位 * @param path 已經確定順序的串 * @param string 待全排列的串 */static void fullPermutate(int index, String path, String string){String restStr = strSub(string, path);if(index == string.length()){System.out.println(path + restStr);count++;//return;}else{for(int i = 0;i < string.length() - index;i++)fullPermutate(index + 1, path + restStr.charAt(i), string);}}/** * @param full 完整的串 * @param part 部分子串 * @return rest 返回full與part的差集 */static String strSub(String full, String part){String rest = "";for(int i = 0;i < full.length();i++){String c = full.charAt(i) + "";if(!part.contains(c))rest += c;}return rest;}/** * @param str 待檢查的串 * @return 返回不含重複元素的串 */static String check(String str){for(int i = 0;i < str.length() - 1;i++){String firstPart = str.substring(0, i + 1);String restPart = str.substring(i + 1);str = firstPart + restPart.replace(str.charAt(i) + "", "");}return str;}}

P.S.至於上面的代碼中為什麼要去除參數中的重複元素,是為了增強程式的魯棒性,經典的排列問題是針對不含重複元素的序列而言的,含重複元素的序列我們將在後面展開討論

二.一般演算法(最常見的,也是最經典的全排列演算法)

核心思想是交換,具體來說,對於一個長度為n的串,要得到其所有排列,我們可以這樣做:

1.把當前位上的元素依次與其後的所有元素進行交換

2.對下一位做相同處理,直到當前位是最後一位為止,輸出序列

[需要注意的一點:我們的思想是“交換”,也就是直接對原資料進行修改,那麼在交換之後一定還要再換回來,否則我們的原資料就發生變化了,肯定會出錯]

如果覺得上面的解釋還是很難懂的話,那麼記住這句話:核心思想就是讓你後面的所有人都和你交換一遍(而你是一個指標,從前向後按位移動...)

C代碼如下:(摘自http://www.cnblogs.com/nokiaguy/archive/2008/05/11/1191914.html)

#include <stdio.h>  int n = 0;  void swap(int *a, int *b) {         int m;         m = *a;         *a = *b;         *b = m; }  void perm(int list[], int k, int m) {         int i;         if(k > m)         {                  for(i = 0; i <= m; i++)                         printf("%d ", list[i]);                 printf("\n");                 n++;         }         else         {                 for(i = k; i <= m; i++)                 {                         swap(&list[k], &list[i]);                         perm(list, k + 1, m);                         swap(&list[k], &list[i]);                 }         } } int main() {         int list[] = {1, 2, 3, 4, 5};         perm(list, 0, 4);         printf("total:%d\n", n);         return 0; }

原文也給出了一點解釋,但如果還是不能理解的話,不妨輸出一下運行軌跡,有助於理解,或者用筆畫一畫,多看幾遍就明白了,直接看代碼的話確實不好理解

三.字典序法

其實在本文開頭給出的例子中就用了字典序,人寫全排列或者其它類似的東西的時候會不自覺的用到字典序,這樣做是為了防止漏掉序列

既然如此,用字典序當然也能實現全排列,對於給定序列{3, 1, 2},我們理一理思路,想想具體步驟:

1.對給定序列做升序排序,得到最小字典序{1, 2, 3}

2.對有序序列求下一個字典序,得到{1, 3, 2}

3.如果當前序列沒有下一個字典序(或者說當前序列是最大字典序,如{3, 2, 1}),則結束

顯然字典序法的核心是:求下一個字典序,要充分理解這裡的“下一個”,有兩層意思:

1.該序列在字典中是排在當前序列後面的

2.該序列是字典中最靠近當前序列的

字典序有嚴格的數學定義,按照定義就能求出一個序列的下一個字典序,具體做法不在此展開敘述(下面的代碼中有細緻的解釋)

Java代碼如下:

public class DictionaryOrder {/** * 按字典序輸出全排列 * <br />按照字典序可以得到已知序列的下一個序列,可以用於不需要得到所有全排列的場合(例如資料加密) */public static void main(String[] args) {int arr[] = new int[]{4, 3, 1, 2};/*boolean exist = nextPermutation(arr);if(exist){for(int value : arr)System.out.print(value);System.out.println();}elseSystem.out.println("當前序列已經是最大字典序列");*////*//對給定序列排序(升序)sort(arr);for(int value : arr)System.out.print(value);System.out.println();//求全排列並輸出int count = 1;//第一個已經在上面輸出了while(nextPermutation(arr)){for(int value : arr)System.out.print(value);System.out.println();count++;}System.out.println("共 " + count + " 個");//*/}/** * @param arr 當前序列 * @return 字典序中的下一個序列,沒找到則返回false */public static boolean nextPermutation(int[] arr){int pos1 = 0, pos2 = 0;//1.從右向左找出滿足arr[i] < arr[i + 1]的i//(就是找出相鄰位中滿足前者小於後者關係的前者的位置)boolean find = false;for(int i = arr.length - 2;i >= 0;i--)if(arr[i] < arr[i + 1]){pos1 = i;find = true;break;}if(!find)//若沒找到,說明當前序列已經是最大字典序了return false;//2.從pos1向後找出最小的滿足arr[i] >= arr[pos1]的i//(就是找出pos1後面不小於arr[pos1]的最小值的位置)int min = arr[pos1];for(int i = pos1 + 1;i < arr.length;i++){if(arr[i] >= arr[pos1]){min = arr[i];pos2 = i;}}//3.交換pos1與pos2位置上的值int temp = arr[pos1];arr[pos1] = arr[pos2];arr[pos2] = temp;//4.對pos1後面的所有值做逆序處理(轉置)int i = pos1 + 1;int j = arr.length - 1;for(;i < j;i++, j--){temp = arr[i];arr[i] = arr[j];arr[j] = temp;}return true;}/** * 對給定數組做升序排序(冒泡法) * @param arr 待排序數組 */public static void sort(int[] arr){for(int i = 0;i < arr.length - 2;i++)for(int j = 0;j < arr.length - i - 1;j++)if(arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}

-------

演算法細節就到這裡,下面討論幾個無關緊要問題:

1.給定序列中存在重複元素

經典的全排列問題不討論這個問題,但實際應用中可能會遇到,我們有兩個選擇:

i.對原資料(給定序列)進行處理,對於重複元素只保留一個,或者對重複元素做替換,例如對{1, 1, 2, 3, 2},我們建立一張替換表,a = 1, b = 2,原資料處理結果為{1, a, 2, 3, b},至此我們就消除了重複元素

ii.對演算法做修改,檢測重複元素並做相應處理,需要結合具體資料特徵做處理

2.演算法效率問題

如果複雜問題中需要用到全排列,那麼不得不考慮演算法效率問題了,上面給出的演算法中,前兩種時間複雜度相同,都是n層遞迴,每層n-i + 1個迴圈

第三種演算法的時間複雜度主要集中在了排序上,如果給定序列已經有序,那麼此時第三種演算法無疑是最佳的

另外,還有一種新穎的全排列演算法,有興趣的話也可以試一試,原文連結:

http://supershll.blog.163.com/blog/static/37070436201171005758332/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.