[轉]完美洗牌(Perfect Shuffle)問題

[轉]原博文地址:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/02.09.mdgit

完美洗牌算法

題目詳情

有個長度爲2n的數組{a1,a2,a3,...,an,b1,b2,b3,...,bn},但願排序後{a1,b1,a2,b2,....,an,bn},請考慮有無時間複雜度o(n),空間複雜度0(1)的解法。github

題目來源:此題是去年2013年UC的校招筆試題,看似簡單,按照題目所要排序後的字符串蠻力變化便可,但若要完美的達到題目所要求的時空複雜度,則須要咱們花費不小的精力。OK,請看下文詳解,一步步優化。算法

分析與解法

解法1、蠻力變換

題目要咱們怎麼變換,我們就怎麼變換。此題@陳利人也分析過,在此,引用他的思路進行說明。爲了便於分析,咱們取n=4,那麼題目要求咱們把數組

a1,a2,a3,a4,b1,b2,b3,b4函數

變成優化

a1,b1,a2,b2,a3,b3,a4,b4spa

1.一、步步前移

仔細觀察變換先後兩個序列的特色,咱們可作以下一系列操做:翻譯

第①步、肯定b1的位置,即讓b1跟它前面的a2,a3,a4交換:3d

a1,b1,a2,a3,a4,b2,b3,b4code

第②步、接着肯定b2的位置,即讓b2跟它前面的a3,a4交換:

a1,b1,a2,b2,a3,a4,b3,b4

第③步、b3跟它前面的a4交換位置:

a1,b1,a2,b2,a3,b3,a4,b4

b4已在最後的位置,不須要再交換。如此,通過上述3個步驟後,獲得咱們最後想要的序列。但此方法的時間複雜度爲O(N^2),咱們得繼續尋找其它方法,看看有無辦法能達到題目所預期的O(N)的時間複雜度。

1.二、中間交換

固然,除了如上面所述的讓b1,b2,b3,b4步步前移跟它們各自前面的元素進行交換外,咱們還能夠每次讓序列中最中間的元素進行交換達到目的。仍是用上面的例子,針對a1,a2,a3,a4,b1,b2,b3,b4

第①步:交換最中間的兩個元素a4,b1,序列變成(待交換的元素用粗體表示):

a1,a2,a3,b1,a4,b2,b3,b4

第②步,讓最中間的兩對元素各自交換:

a1,a2,b1,a3,b2,a4,b3,b4

第③步,交換最中間的三對元素,序列變成:

a1,b1,a2,b2,a3,b3,a4,b4

一樣,此法同解法1.一、步步前移同樣,時間複雜度依然爲O(N^2),咱們得下點力氣了。

解法2、完美洗牌算法

玩過撲克牌的朋友都知道,在一局完了以後洗牌,洗牌人會習慣性的把整副牌大體分爲兩半,兩手各拿一半對着對着交叉洗牌,以下圖所示:

若是這副牌用a1 a2 a3 a4 b1 b2 b3 b4表示(爲簡化問題,假設這副牌只有8張牌),而後一分爲二以後,左手上的牌多是a1 a2 a3 a4,右手上的牌是b1 b2 b3 b4,那麼在如上圖那樣的洗牌以後,獲得的牌就多是b1 a1 b2 a2 b3 a3 b4 a4。

技術來源於生活,2004年,microsoft的Peiyush Jain在他發表一篇名爲:「A Simple In-Place Algorithm for In-Shuffle」的論文中提出了完美洗牌算法。

這個算法解決一個什麼問題呢?跟本題有什麼聯繫呢?

Yeah,顧名思義,完美洗牌算法解決的就是一個完美洗牌問題。什麼是完美洗牌問題呢?即給定一個數組a1,a2,a3,...an,b1,b2,b3..bn,最終把它置換成b1,a1,b2,a2,...bn,an。讀者能夠看到,這個完美洗牌問題本質上與本題徹底一致,只要在完美洗牌問題的基礎上對它最後的序列swap兩兩相鄰元素便可。

即:

a1,a2,a3,...an,b1,b2,b3..bn

經過完美洗牌問題,獲得:

b1,a1,b2,a2,b3,a3...  bn,an

再讓上面相鄰的元素兩兩swap,便可達到本題的要求:

a1,b1,a2,b2,a3,b3....,an,bn

也就是說,若是咱們能經過完美洗牌算法(時間複雜度O(N),空間複雜度O(1))解決了完美洗牌問題,也就間接解決了本題。

雖然網上已有很多文章對上篇論文或翻譯或作解釋說明,但對於初學者來講,理解難度實在太大,再者,若直接翻譯原文,根本沒法看出這個算法怎麼一步步得來的,故下文將從完美洗牌算法的最基本的原型開始提及,以讓讀者能對此算法一目瞭然。

2.一、位置置換pefect_shuffle1算法

爲方便討論,咱們設定數組的下標從1開始,下標範圍是[1..2n]。 仍是經過以前n=4的例子,來看下每一個元素最終去了什麼地方。

起始序列:a1 a2 a3 a4 b1 b2 b3 b4 數組下標:1 2 3 4 5 6 7 8 最終序列:b1 a1 b2 a2 b3 a3 b4 a4

從上面的例子咱們能看到,前n個元素中,

第1個元素a1到了原第2個元素a2的位置,即1->2;

第2個元素a2到了原第4個元素a4的位置,即2->4;

第3個元素a3到了原第6個元素b2的位置,即3->6;

第4個元素a4到了原第8個元素b4的位置,即4->8;

那麼推廣到通常狀況便是:前n個元素中,第i個元素去了 第(2 * i)的位置。

上面是針對前n個元素,那麼針對後n個元素,能夠看出:

第5個元素b1到了原第1個元素a1的位置,即5->1;

第6個元素b2到了原第3個元素a3的位置,即6->3;

第7個元素b3到了原第5個元素b1的位置,即7->5;

第8個元素b4到了原第7個元素b3的位置,即8->7;

推廣到通常狀況是,後n個元素,第i個元素去了第 (2 * (i - n) ) - 1 = 2 * i - (2 * n + 1) = (2 * i) % (2 * n + 1) 個位置。

再綜合到任意狀況,任意的第i個元素,咱們最終換到了 (2 * i) % (2 * n + 1)的位置。爲什麼呢?由於:

當0 < i < n時, 原式= (2i) % (2 * n + 1) = 2i;

當i > n時,原式(2 * i) % (2 * n + 1)保持不變。

所以,若是題目容許咱們再用一個數組的話,咱們直接把每一個元素放到該放得位置就行了。也就產生了最簡單的方法pefect_shuffle1,參考代碼以下:

// 時間O(n),空間O(n) 數組下標從1開始
void PefectShuffle1(int *a, int n) { int n2 = n * 2, i, b[N]; for (i = 1; i <= n2; ++i) { b[(i * 2) % (n2 + 1)] = a[i]; } for (i = 1; i <= n2; ++i) { a[i] = b[i]; } }

但很明顯,它的時間複雜度雖然是O(n),但其空間複雜度倒是O(n),仍不符合本題所期待的時間O(n),空間O(1)。咱們繼續尋找更優的解法。

與此同時,我也提醒下讀者,根據上面變換的節奏,咱們能夠看出有兩個圈,

一個是1 -> 2 -> 4 -> 8 -> 7 -> 5 -> 1;

一個是3 -> 6 -> 3。

下文2.2.一、走圈算法cycle_leader將再次提到這兩個圈。

2.二、完美洗牌算法perfect_shuffle2

2.2.一、走圈算法cycle_leader

由於以前perfect_shuffle1算法未達到時間複雜度O(N)而且空間複雜度O(1)的要求,因此咱們必須得再找一種新的方法,以期能完美的解決本節開頭提出的完美洗牌問題。

讓咱們先來回顧一下2.1節位置置換perfect_shuffle1算法,還記得我以前提醒讀者的關於當n=4時,經過位置置換讓每個元素到了最後的位置時,所造成的兩個圈麼?我引用下2.1節的相關內容:

當n=4的狀況:

起始序列:a1 a2 a3 a4 b1 b2 b3 b4 數組下標:1 2 3 4 5 6 7 8 最終序列:b1 a1 b2 a2 b3 a3 b4 a4

即經過置換,咱們獲得以下結論:

「於此同時,我也提醒下讀者,根據上面變換的節奏,咱們能夠看出有兩個圈,

一個是1 -> 2 -> 4 -> 8 -> 7 -> 5 -> 1;

一個是3 -> 6 -> 3。」

這兩個圈能夠表示爲(1,2,4,8,7,5)和(3,6),且perfect_shuffle1算法也已經告訴了咱們,無論你n是奇數仍是偶數,每一個位置的元素都將變爲第(2*i) % (2n+1)個元素:

所以咱們只要知道圈裏最小位置編號的元素即圈的頭部,順着圈走一遍就能夠達到目的,且由於圈與圈是不相交的,因此這樣下來,咱們恰好走了O(N)步。

仍是舉n=4的例子,且假定咱們已經知道第一個圈和第二個圈的前提下,要讓1 2 3 4 5 6 7 8變換成5 1 6 2 7 3 8 4:

第一個圈:1 -> 2 -> 4 -> 8 -> 7 -> 5 -> 1 第二個圈:3 -> 6 -> 3:

原始數組:1 2 3 4 5 6 7 8 數組下標:1 2 3 4 5 6 7 8

走第一圈:5 1 3 2 7 6 8 4 走第二圈:5 1 6 2 7 3 8 4

上面沿着圈走的算法咱們給它取名爲cycle_leader,這部分代碼以下:

//數組下標從1開始,from是圈的頭部,mod是要取模的數 mod 應該爲 2 * n + 1,時間複雜度O(圈長)
void CycleLeader(int *a, int from, int mod) { int t,i; for (i = from * 2 % mod; i != from; i = i * 2 % mod) { t = a[i]; a[i] = a[from]; a[from] = t; } }
2.2.二、神級結論:若2*n=(3^k - 1),則可肯定圈的個數及各自頭部的起始位置

下面我要引用此論文「A Simple In-Place Algorithm for In-Shuffle」的一個結論了,即 對於2*n = (3^k-1)這種長度的數組,剛好只有k個圈,且每一個圈頭部的起始位置分別是1,3,9,...3^(k-1)。

論文原文部分爲:

也就是說,利用上述這個結論,咱們能夠解決這種特殊長度2*n = (3^k-1)的數組問題,那麼若給定的長度n是任意的咋辦呢?此時,咱們能夠採起分而治之算法的思想,把整個數組一分爲二,即拆分紅兩個部分:

讓一部分的長度知足神級結論:若2*m = (3^k-1),則剛好k個圈,且每一個圈頭部的起始位置分別是1,3,9,...3^(k-1)。其中m < n,m往神級結論所需的值上套;

剩下的n-m部分單獨計算;

當把n分解成m和n-m兩部分後,原始數組對應的下標以下(爲了方便描述,咱們依然只須要看數組下標就夠了):

原始數組下標:1..m m+1.. n, n+1 .. n+m, n+m+1,..2*n

且爲了能讓前部分的序列知足神級結論2*m = (3^k-1),咱們能夠把中間那兩段長度爲n-m和m的段交換位置,即至關於把m+1..n,n+1..n+m的段循環右移m次(爲何要這麼作?由於如此操做後,數組的前部分的長度爲2m,而根據神級結論:當2m=3^k-1時,可知這長度2m的部分剛好有k個圈)。

而若是讀者看過本系列第一章、左旋轉字符串的話,就應該意識到循環位移是有O(N)的算法的,其思想便是把前n-m個元素(m+1.. n)和後m個元素(n+1 .. n+m)先各自翻轉一下,再將整個段(m+1.. n, n+1 .. n+m)翻轉下。

這個翻轉的代碼以下:

//翻轉字符串時間複雜度O(to - from)
void reverse(int *a, int from, int to) { int t; for (; from < to; ++from, --to) { t = a[from]; a[from] = a[to]; a[to] = t; } } //循環右移num位 時間複雜度O(n) void RightRotate(int *a, int num, int n) { reverse(a, 1, n - num); reverse(a, n - num + 1, n); reverse(a, 1, n); }

翻轉後,獲得的目標數組的下標爲:

目標數組下標:1..m n+1..n+m    m+1 .. n       n+m+1,..2*n

OK,理論講清楚了,再舉個例子便會更加一目瞭然。當給定n=7時,若要知足神級結論2*n=3^k-1,k只能取2,繼而推得n‘=m=4。

原始數組:a1 a2 a3 a4       a5 a6 a7     b1 b2 b3 b4   b5 b6 b7

既然m=4,即讓上述數組中有下劃線的兩個部分交換,獲得:

目標數組:a1 a2 a3 a4    b1 b2 b3 b4      a5 a6 a7     b5 b6 b7

繼而目標數組中的前半部分a1 a2 a3 a4 b1 b2 b3 b4部分能夠用2.2.一、走圈算法cycle_leader搞定,於此咱們最終求解的n長度變成了n’=3,即n的長度減少了4,單獨再解決後半部分a5 a6 a7 b5 b6 b7便可。

2.2.三、完美洗牌算法perfect_shuffle3

從上文的分析過程當中也就得出了咱們的完美洗牌算法,其算法流程爲:

輸入數組 A[1..2 * n]

step 1 找到 2 * m = 3^k - 1 使得 3^k <= 2 * n < 3^(k +1)

step 2 把a[m + 1..n + m]那部分循環移m位

step 3 對每一個i = 0,1,2..k - 1,3^i是個圈的頭部,作cycle_leader算法,數組長度爲m,因此對2 * m + 1取模。

step 4 對數組的後面部分A[2 * m + 1.. 2 * n]繼續使用本算法, 這至關於n減少了m。

上述算法流程對應的論文原文爲:

以上各個步驟對應的時間複雜度分析以下:

由於循環不斷乘3的,因此時間複雜度O(logn)

循環移位O(n)

每一個圈,每一個元素只走了一次,一共2*m個元素,因此複雜度omega(m), 而m < n,因此 也在O(n)內。 T(n - m)

所以總的時間複雜度爲 T(n) = T(n - m) + O(n) ,m = omega(n) ,解得:T(n) = O(n)。

此完美洗牌算法實現的參考代碼以下:

//copyright@caopengcs 8/24/2013
//時間O(n),空間O(1) void PerfectShuffle2(int *a, int n) { int n2, m, i, k, t; for (; n > 1;) { // step 1 n2 = n * 2; for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3) ; m /= 2; // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1) // step 2 right_rotate(a + m, m, n); // step 3 for (i = 0, t = 1; i < k; ++i, t *= 3) { cycle_leader(a , t, m * 2 + 1); } //step 4 a += m * 2; n -= m; } // n = 1 t = a[1]; a[1] = a[2]; a[2] = t; }
2.2.四、perfect_shuffle2算法解決其變形問題

啊哈!以上代碼即解決了完美洗牌問題,那麼針對本章要解決的其變形問題呢?是的,如本章開頭所說,在完美洗牌問題的基礎上對它最後的序列swap兩兩相鄰元素便可,代碼以下:

//copyright@caopengcs 8/24/2013
//時間複雜度O(n),空間複雜度O(1),數組下標從1開始,調用perfect_shuffle3 void shuffle(int *a, int n) { int i, t, n2 = n * 2; PerfectShuffle2(a, n); for (i = 2; i <= n2; i += 2) { t = a[i - 1]; a[i - 1] = a[i]; a[i] = t; } }

上述的這個「在完美洗牌問題的基礎上對它最後的序列swap兩兩相鄰元素」的操做(固然,你也可讓原數組第一個和最後一個不變,中間的2 * (n - 1)項用原始的標準完美洗牌算法作),只是在完美洗牌問題時間複雜度O(N)空間複雜度O(1)的基礎上再增長O(N)的時間複雜度,故總的時間複雜度O(N)不變,且理所固然的保持了空間複雜度O(1)。至此,我們的問題獲得了圓滿解決!

問題擴展

神級結論是如何來的?

咱們的問題獲得瞭解決,但本章還沒有完,即決定完美洗牌算法的神級結論:若2*n=(3^k - 1),則剛好只有k個圈,且每一個圈頭部的起始位置分別是1,3,9,...3^(k-1),是如何來的呢?

要證實這個結論的關鍵就是:這全部的圈合併起來必須包含從1到M之間的全部正數,一個都不能少。這個證實有點麻煩,由於證實過程當中會涉及到羣論等數論知識,但再遠的路一步步走也能到達。

首先,讓我們明確如下相關的概念,定理,及定義(搞清楚了這些東西,我們便證實了一大半):

概念1 mod表示對一個數取餘數,好比3 mod 5 =3,5 mod 3 =2;

定義1 歐拉函數ϕ(m) 表示爲不超過m(即小於等於m)的數中,與m互素的正整數個數

定義2 若ϕ(m)=Ordm(a) 則稱a爲m的原根,其中Ordm(a)定義爲:a ^d ( mod m),其中d=0,1,2,3…,但取讓等式成立的最小的那個d。

結合上述定義一、定義2可知,2是3的原根,由於2^0 mod 3 = 1, 2^1 mod 3 = 2, 2^2 mod 3 = 1, 2^3 mod 3 = 2,{a^0 mod m,a^1 mod m,a^2}獲得集合S={1,2},包含了全部和3互質的數,也即d=ϕ(2)=2,知足原根定義。

而2不是7的原根,這是由於2^0 mod 7 = 1, 2^1 mod 7 = 2, 2^2 mod 7 = 4, 2^3 mod 7 = 1,2^4 mod 7 = 2,2^5 mod 7 = 4,2^6 mod 7 = 1,從而集合S={1,2,4}中始終只有一、二、4三種結果,而沒包含所有與7互質的數(三、六、5便不包括),,即d=3,但ϕ(7)=6,從而d != ϕ(7),不知足原根定義。

再者,若是說一個數a,是另一個數m的原根,表明集合S = {a^0 mod m, a^1 mod m, a^2 mod m…… },獲得的集合包含了全部小於m而且與m互質的數,不然a便不是m的原根。並且集合S = {a^0 mod m, a^1 mod m, a^2 mod m…… }中可能會存在重複的餘數,但當a與m互質的時候,獲得的{a^0 mod m, a^1 mod m, a^2 mod m}集合中,保證了第一個數是a^0 mod m,故第一次發現重複的數時,這個重複的數必定是1,也就是說,出現餘數循環必定是從開頭開始循環的。

定義3 對模指數,a對模m的原根定義爲 ,st:中最小的正整數d

再好比,2是9的原根,由於,爲了讓除以9的餘數恆等於1,可知最小的正整數d=6,而ϕ(m)=6,知足原根的定義。

定理1 同餘定理:兩個整數a,b,若它們除以正整數m所得的餘數相等,則稱a,b對於模m同餘,記做,讀作a與b關於模m同餘。

定理2 當p爲奇素數且a是的原根時⇒ a也是的原根

定理3 費馬小定理:若是a和m互質,那麼a^ϕ(m) mod m = 1

定理4 若(a,m)=1 且a爲m的原根,那麼a是(Z/mZ)*的生成元。

取a = 2, m = 3。

咱們知道2是3的原根,2是9的原根,咱們定義S(k)表示上述的集合S,而且取x = 3^k(x表示爲集合S中的數)。

因此:

S(1) = {1, 2}
  S(2) = {1, 2, 4, 8, 7, 5}

咱們沒改變圈元素的順序,由前面的結論S(k)剛好是一個圈裏的元素,且認爲從1開始循環的,也就是說從1開始的圈包含了全部與3^k互質的數。

那與3^k不互質的數怎麼辦?若是0 < i < 3^k與 3^k不互質,那麼i 與3^k的最大公約數必定是3^t的形式(只包含約數3),而且 t < k。即gcd(i , 3^k) = 3^t,等式兩邊除以個3 ^ t,即得gcd( i/(3^t),3^(k - t) ) = 1, i/(3^t) 都與3^(k - t) 互質了,而且i / (3^t) < 3^(k - t), 根據S(k)的定義,可見i/(3^t) 在集合S(k - t)中。

同理,任意S(k - t)中的數x,都知足gcd(x , 3^k) = 1,因而gcd(3^k , x* 3^t) = 3 ^ t, 而且x3^t < 3^k。可見S(k - t)中的數x3^t 與 i造成了一一對應的關係。

也就是說S(k - t)裏每一個數x* 3^t造成的新集合包含了全部與3^k的最大公約數爲3^t的數,它也是一個圈,原先圈的頭部是1,這個圈的頭部是3^t。

因而,對全部的小於 3^k的數,根據它和3^k的最大公約數,咱們都把它分配到了一個圈裏去了,且k個圈包含了全部的小於3^k的數。

下面,舉個例子,如caopengcs所說,當咱們取「a = 2, m = 3時,

咱們知道2是3的原根,2是9的原根,咱們定義S(k)表示上述的集合S,而且x= 3^k。

因此S(1) = {1, 2}

S(2) = {1, 2, 4, 8, 7, 5}

好比k = 3。 咱們有:

S(3) = {1, 2 ,4 , 8, 16, 5, 10, 20, 13, 26, 25, 23, 19, 11, 22, 17, 7, 14} 包含了小於27且與27互質的全部數,圈的首部爲1,這是原根定義決定的。

那麼與27最大公約數爲3的數,咱們用S(2)中的數乘以3獲得。 S(2) * 3 = {3, 6, 12, 24, 21, 15}, 圈中元素的順序沒變化,圈的首部是3。

與27最大公約數爲9的數,咱們用S(1)中的數乘以9獲得。 S(1) * 9 = {9, 18}, 圈中得元素的順序沒變化,圈的首部是9。

由於每一個小於27的數和27的最大公約數只有1, 3, 9這3種狀況,又因爲前面所證的一一對應的關係,因此S(2) * 3包含了全部小於27且與27的最大公約數爲3的數,S(1) * 9 包含了全部小於27且和27的最大公約數爲9的數。」

換言之,若定義爲整數,假設/N定義爲整數Z除以N後所有餘數的集合,包括{0...N-1}等N個數,而(/N)*則定義爲這Z/N中{0...N-1}這N個餘數內與N互質的數集合。

則當n=13時,2n+1=27,即得/N ={0,1,2,3,.....,26},(/N)*至關於就是{0,1,2,3,.....,26}中所有與27互素的數的集合;

而2^k(mod 27)能夠把(/27)*取遍,故可得這些數分別在如下3個圈內:

取頭爲1,(/27)*={1,2,4,8,16,5,10,20,13,26,25,23,19,11,22,17,7,14},也就是說,與27互素且小於27的正整數集合爲{1,2,4,8,16,5,10,20,13,26,25,23,19,11,22,17,7,14},所以ϕ(m) = ϕ(27)=18, 從而知足的最小d = 18,故得出2爲27的原根;

取頭爲3,就能夠獲得{3,6,12,24,21,15},這就是以3爲頭的環,這個圈的特色是全部的數都是3的倍數,且都不是9的倍數。爲何呢?由於2^k和27互素。

具體點則是:若是3×2^k除27的餘數可以被9整除,則有一個n使得32^k=9n(mod 27),即32^k-9n可以被27整除,從而3*2^k-9n=27m,其中n,m爲整數,這樣一來,式子約掉一個3,咱們便能獲得2^k=9m+3n,也就是說,2^k是3的倍數,這與2^k與27互素是矛盾的,因此,3×2^k除27的餘數不可能被9整除。

此外,2^k除以27的餘數能夠是3的倍數之外的全部數,因此,2^k除以27的餘數能夠爲1,2,4,5,7,8,當餘數爲1時,即存在一個k使得2^k-1=27m,m爲整數。

式子兩邊同時乘以3獲得:32^k-3=81m是27的倍數,從而32^k除以27的餘數爲3;

同理,當餘數爲2時,2^k - 2 = 27m,=> 32^k- 6 =81m,從而32^k除以27的餘數爲6;

當餘數爲4時,2^k - 4 = 37m,=> 32^k - 12 =81m,從而32^k除以27的餘數爲12;

同理,能夠取到15,21,24。從而也就印證了上面的結論:取頭爲3,就能夠獲得{3,6,12,24,21,15}。 取9爲頭,這就很簡單了,這個圈就是{9,18}

你會發現,小於27的全部天然數,要麼在第一個圈裏面,也就是那些和27互素的數;要麼在第二個圈裏面,也就是那些是3的倍數,但不是9的倍數的數;要麼在第三個圈裏面,也就是是9倍數的數,而之因此可以這麼作,就是由於2是27的本原根。證實完畢。

最後,我們也再驗證下上述過程:

由於,故:

i = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

因爲n=13,2n+1 = 27,據此公式可知,上面第 i 位置的數將分別變成下述位置的:

i = 2 4 6 8 10 12 14 16 18 20 22 24 26 1 3 5 7 9 11 13 15 17 19 21 23 25 0

根據i 和 i‘ 先後位置的變更,咱們將獲得3個圈:

1->2->4->8->16->5->10->20->13->26->25->23->19->11->22->17->7->14->1;
   3->6->12->24->21->15->3
   9->18->9

沒錯,這3個圈的數字與我們以前獲得的3個圈一致吻合,驗證完畢。

觸類旁通

至此,本章開頭提出的問題解決了,完美洗牌算法的證實也證完了,是否能夠止步了呢?OH,NO!讀者有無思考過下述問題:

一、既然完美洗牌問題是給定輸入:a1,a2,a3,……aN,b1,b2,b3,……bN,要求輸出:b1,a1,b2,a2,……bN,aN;那麼有無考慮過它的逆問題:即給定b1,a1,b2,a2,……bN,aN,,要求輸出a1,a2,a3,……aN,b1,b2,b3,……bN ?

二、完美洗牌問題是兩手洗牌,假設有三隻手同時洗牌呢?那麼問題將變成:輸入是a1,a2,……aN, b1,b2,……bN, c1,c2,……cN,要求輸出是c1,b1,a1,c2,b2,a2,……cN,bN,aN,這個時候,怎麼處理?

相關文章
相關標籤/搜索