全排列產生演算法(三)

來源:互聯網
上載者:User

基於階乘數的全排列產生演算法,是另一種通過序列順序,輸出全排列的演算法。所謂階乘數,實際上和我們常用的2進位,8進位,10進位,16進位一樣,是一種數值的表示形式,所不同的是,上面這幾種進位數,相鄰位之間的進位是固定值,以10進位為例,第n位與第n+1位之間的進位是10,而階乘數,相鄰兩位之間的進位是變值,第n位與第n+1位之間的進位是(n+1)!。對於10進位數,每一位的取值範圍也是固定的0~9,而階乘數每一位的取值範圍為0~n。可以證明,任何一個數量,都可以由一個階乘數唯一表示。下面以23為例,說明其在各種進位中的表現形式

  2進位 8進位 10進位 16進位 階乘數
23 10111 27 23 17 3210

其中10進位23所代表的數量的計算方法為

D(23) = 2×10^1 + 3×10^0 = 2×10 + 3×1 = 23

階乘數3210所代表的數量的計算方法為

F(3210) = 3×3! + 2×2! + 1×1! + 0×0! = 3×6 + 2×2 + 1×1 + 1×0 = 23

對於階乘數而言,由於階乘的增長速度非常快,所以其可以表示的數值的範圍隨著位元的增長十分迅速,對於n位的階乘數而言,其表示的範圍從0~(n+1)!-1,總共(n+1)!個數。階乘數有很多性質這裡我們只介紹其和全排列相關的一些性質。

首先是加法操作,與普通十進位數的加法基本一樣,所不同的是對於第n位F[n](最低位從第0位開始),如果F[n]+1>n,那麼我們需要將F[n]置0,同時令F[n+1]+1,如果對於第n+1位,也導致進位,則向高位依次執行進位操作。這裡我們看一下F(3210)+1,對於第0位,有F[0]+1=0+1=1>0,所以F[0]=0(實際上階乘數的第0位一直是0),F[1]+1=1+1=2>1,F[1]=0,……,依次執行,各位都發生進位,最終結果F(3210)+1=F(10000)。

其次,對於n位的階乘數,每一個階乘數的各位的數值,正好對應了一個n排列各位的逆序關係。這裡以abcd為例。例如F(2110),其對應的排列的意思是,對於排列的第一個元素,其後有兩個元素比他小;第二個元素,後面有一個元素比他小;第三個元素,後面有一個元素比他小。最終根據F(2110)構建的排列為cbda。4位的階乘數,與4排列的對應關係如下表所示。

0000 abcd 1000 bacd 2000 cabd 3000 dabc
0010 abdc 1010 badc 2010 cadb 3010 dacb
0100 acbd 1100 bcad 2100 cbad 3100 dbac
0110 acdb 1110 bcda 2110 cbda 3110 dbca
0200 adbc 1200 bdac 2200 cdab 3200 dcab
0210 adcb 1210 bdca 2210 cdba 3210 dcba

由此,我們就可以利用階乘數與排列的對應關係構建集合的全排列,演算法如下。

  • 對於n個元素的全排列,首先產生n位的階乘數F[0...n-1],並令F[0...n-1]=0。
  • 每次對F[0...n-1]執行+1操作,所得結果,根據其與排列的逆序對應關係,產生排列。
  • 直到到達F[0...n-1]所能表示的最大數量n!-1為止,全部n!個排列產生完畢。

這裡有一個問題需要解決,就是如何根據階乘數,及其與排列逆序的對應關係產生對應的排列,這裡給出一個方法,

  • 以字典序最小的排列a[0...n-1]作為起始,令i從0到n-2。
  • 如果F[i]=0,遞增i。
  • 否則令t=a[i+F[i]],同時將a[i...i+F[i]-1]區間的元素,向後移動一位,然後令a[i]=t,遞增i。

下面說明一下如何根據階乘數F(2110)和初始排列abcd,構建對應的排列。首先,我們發現F[0]=2,所以我們要將a[0+2]位置的元素c放在a[0]位置,之前,先用臨時變數t記錄a[2]的值,然後將a[0...0+2-1]區間內的元素向後移動一位,然後令a[0]=t,得到cabd,i值增加1;然後有F[1]=1,所以我們要將a[1+1]=a[2]=b放在a[1]位置,同時將a[1]向後移動一位,得到排列cbad;然後有F[2]=1,所以將a[2+1]=a[3]=d放在a[2]位置,同時a[2]向後移動一位。最終得到cbda,排列產生結束。整個演算法代碼如下

inline int FacNumNext(unsigned int* facnum, size_t array_size){unsigned int i = 0;while(i < array_size){if(facnum[i] + 1 <= i){facnum[i] += 1;return 0;}else{facnum[i] = 0;++i;}}return 1;}/* * 根據階乘數所指定的逆序數根據原始字串構建排列輸出 */inline void BuildPerm(const char* array, size_t array_size, const unsigned int* facnum, char* out){char t;unsigned int i, j;memcpy(out, array, array_size * sizeof(char));for(i = 0; i < array_size - 1; ++i){j = facnum[array_size - 1 - i];if(j != 0){t = out[i + j];memmove(out + i + 1, out + i, j * sizeof(char));out[i] = t;}}}/* * 基於階乘數(逆序數)的全排列產生演算法 */void FullArray(char* array, size_t array_size){unsigned int facnum[array_size];char out[array_size];for(unsigned int i = 0; i < array_size; ++i){facnum[i] = 0;}BuildPerm(array, array_size, facnum, out);for(unsigned int i = 0; i < array_size; ++i){cout << out[i] << ' ';}cout << '\n';while(!FacNumNext(facnum, array_size)){BuildPerm(array, array_size, facnum, out);for(unsigned int i = 0; i < array_size; ++i){cout << out[i] << ' ';}cout << '\n';}}

用該演算法產生1234全排列,順序如,該圖來自與Wiki百科。

從產生排列順序的角度講,概演算法相較於字典序和最小變更有明顯優勢,但是在實際應用中,由於根據階乘數所定義的逆序構建排列是一個O(n^2)時間複雜度的過程,所以演算法的整體執行效率遜色不少。但是通過階乘數建立逆序數與排列對應關係的思路,還是十分精彩的,值得借鑒。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.