原文:
完美洗牌問題:給定乙個陣列a1,a2,a3,...an,b1,b2,b3..bn,把它最終設定為b1,a1,b2,a2,...bn,an這樣的。
分析:首先,有的問題要求把它換成a1,b1,a2,b2,...an,bn。其實也差不多。我們可以:迴圈n次交換a1,b1,a2,b2, 把陣列變為b1,b2...bn,a1,a2...an,時間複雜度o(n),再用完美洗牌問題的演算法。或者 先用完美洗牌演算法,再迴圈n次交換每組相鄰的兩個元素,也是o(n)。所以,我們只研究第一行給出的問題。為方便起見,我們考慮的是乙個下標從1開始的陣列,下標範圍是[1..2n]。 我們看一下每個元素最終去了什麼地方。
前n個元素 a1 -> a2 a2->a4.... 第i個元素去了 第(2 * i)的位置,後n個元素a(n + 1)->a1, a(n + 2)->a3... 第i個元素去了第 ((2 * (i - n-1) ) + 1) = (2 * i - (2 * n + 1)) = (2 * i) % (2 * n + 1) 個位置。統一一下,任意的第i個元素,我們最終換到了 (2 * i) % (2 * n + 1)的位置,這個取模很神奇,不會產生0。所有的位置編號還是從1到n。
一、完美洗牌演算法1
**如下:
// 時間o(n),空間o(n) 陣列下標從1開始二、 完美洗牌演算法2---分治的力量a1, a2,a3,a4,b1,b2,b3,b4void pefect_shuffle1(int *a,int n)
for (i = 1; i <= n2; ++i)
}
我們先要把前半段的後2個元素(a3,a4)與後半段的前2個元素(b1,b2)交換,得到a1,a2,b1,b2,a3,a4,b3,b4。
於是,我們分別求解子問題a (a1,a2,b1,b2)和子問題b (a3,a4,b3,b4)就可以了。
如果n = 5,是偶數怎麼辦?我們原始的陣列是a1,a2,a3,a4,a5,b1,b2,b3,b4,b5,我們先把a5拎出來,後面所有元素前移,再把a5放到最後,變為a1,a2,a3,a4,b1,b2,b3,b4,b5,a5。可見這時最後兩個元素b5,a5已經是我們要的結果了,所以我們只要考慮n=4就可以了。
那麼複雜度怎麼算? 每次,我們交換中間的n個元素,需要o(n)的時間,n是奇數的話,我們還需要o(n)的時間先把後兩個元素調整好,但這步影響總體時間複雜度。所以,無論如何都是o(n)的時間複雜度。
於是我們有 t(n) = t(n / 2) + o(n) 這個就是跟歸併排序一樣的複雜度式子,最終複雜度解出來t(n) = o(nlogn)。空間的話,我們就在陣列內部折騰的,所以是o(1)。(當然沒有考慮遞迴的棧的空間)
**:
//時間o(nlogn) 空間o(1) 陣列下標從1開始三、完美洗牌演算法這個演算法源自一篇文章,文章很數學,可以只記結論就好了…… 這個演算法的具體實現還是依賴於演算法1,和演算法2的。 首先,對於每乙個元素,它最終都會到達乙個位置,我們如果記錄每個元素應到的位置會形成圈。 為什麼會形成圈? 比如原來位置為a的元素最終到達b,而b又要達到c……,因為每個新位置和原位置都有乙個元素,所以一條鏈 a->b->c->d……這樣下去的話,必然有乙個元素會指向a,(因為中間那些位置b,c,d……都已經被其它元素指向了)。 這就是圈的成因。void perfect_shuffle2(int *a,int n)
int n2 = n * 2, n3 = n / 2;
if (n % 2 == 1)
a[n2] = t;
--n;
} //到此n是偶數
for (i = n3 + 1; i <= n; ++i)
// [1.. n /2]
perfect_shuffle2(a, n3);
perfect_shuffle2(a + n, n3);
}
比如 6個元素 原始是(1,2,3,4,5,6), 最終是(4,1,5,2,6,3),我們用a->b表示原來下標為a的元素,新下標為b了。
//陣列下標從1開始,from是圈的頭部,mod是要取模的數 mod 應該為 2 * n + 1,時間複雜度o(圈長)那麼如何找到每個圈的頭部呢?引用一篇**,名字叫:void cycle_leader(int *a,int
from, int mod)
a[from] = last;
}
a ****** in-place algorithm for in-shuffle.
peiyush jain, microsoft corporation.
利用數論知識,包括原根、甚至群論什麼的,**給出了乙個出色結論(*)
:對於2 * n = (3^k - 1),這種長度的陣列,恰好只有k個圈,並且每個圈的頭部是1,3,9,...3^(k - 1)
。這樣我們就解決了這種特殊的n作為長度的問題。那麼,對於任意的n怎麼辦?我們利用演算法2的思路,把它拆成兩部分,前一部分是滿足結論(*)。後一部分再單獨算。
為了把陣列分成適當的兩部分,我們同樣需要交換一些元素,但這時交換的元素個數不相等,不能簡單地迴圈交換,我們需要更強大的工具——迴圈移。假設滿足結論(*)的需要的長度是2 * m = (3^k - 1), 我們需要把n分解成m和n - m兩部分,按下標來說,是這樣:
原先的陣列(1..m) (m + 1.. n) (n + 1..n + m)(n + m + 1..2 * n),我們要達到的陣列 (1..m)(n + 1.. n + m)(m + 1..n)(n + m + 1..2 * n)。可見,中間那兩段長度為(n - m)和m的段需要交換位置,這個相當於把(m + 1..n + m)的段迴圈右移m次,而迴圈右移是有o(長度)的演算法的, 主要思想是把前(n - m)個元素和後m個元素都翻轉一下,再把整個段翻轉一下。
迴圈移位的**:
//翻轉字串時間複雜度o(to - from)再用a和b舉例一下,設n = 7這樣m = 4, k = 2void reverse(int *a,int
from,int to)
}//迴圈右移num位 時間複雜度o(n)
void right_rotate(int *a,int num,int n)
原先的陣列是a1,a2,a3,a4,(a5,a6,a7),(b1,b2,b3,b4),b5,b6,b7。
結論(*)是說m = 4的部分可以直接搞定,也就是說我們把中間加括號的那兩段(a5,a6,a7) (b1,b2,b3,b4)交換位置,也就是把(a5,a6,a7,b1,b2,b3,b4)整體迴圈右移4位就可以得到:(a1,a2,a3,a4,b1,b2,b3,b4)(a5,a6,a7,b5,b6,b7)
於是前m = 4個由演算法cycle_leading演算法直接搞定,n的長度減小了4。
所以這也是一種分治演算法。演算法流程:
輸入陣列 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。
時間複雜度分析:
1 因為迴圈不斷乘3的,所以時間複雜度o(logn)
2 迴圈移位o(n)
3 每個圈,每個元素只走了一次,一共2*m個元素,所以複雜度omega(m), 而m < n,所以 也在o(n)內。
4 t(n - m)
因此 複雜度為 t(n) = t(n - m) + o(n) m = omega(n) 解得:總複雜度t(n) = o(n)。
演算法**:
//時間o(n),空間o(1)以上所有**已經測試void perfect_shuffle3(int *a,int n)
//step 4
a += m * 2;
n -= m;
}// n = 1
t = a[1];
a[1] = a[2];
a[2] = t;
}
完美洗牌演算法
沒把空間複雜度o 1 的搬過來 問題描述 有乙個長度為2n的陣列,希望排序後變成,請考慮有沒有時間複雜度為o n 而空間複雜度為o 1 的解法。string a string temp new string len for int i 1 i len i temp 2 i len a i 經過上面處...
完美洗牌演算法
完美洗牌就是將一副牌平均分成兩份 26張 來交錯洗牌,如此迴圈反覆一定次數後,又變回洗牌前的順序了。即 a 1,a 2.a n,b 1,b 2.b n的序列變為b 1,a 1,b 2,a 2.b n,a n 我知道這 很shi,只是拋磚,向各位高人請教更好的演算法,謝謝 完美洗牌就是將一副牌平均分成...
完美洗牌 洗牌
完美洗牌問題,給定乙個陣列a1,a2,a3,an,b1,b2,b3.bn,把它最終設定為b1,a1,b2,a2,bn,an這樣的。o n 的演算法,o n 的空間。對於前n個數,對映為f i 2 i 1,0 i n 2 比如0 1,1 3 對於後n個數,對映為f i 2 i n 2 n 2 i n ...