perfect shuffle 演算法
今天又發現一個關於完美洗牌的演算法。這個比較簡單一些,由 microsoft的Peiyush Jain提出。
原論文: A Simple In-Place Algorithm for In-Shuffle.
Peiyush Jain, Microsoft Corporation.
July 2004
問題描述:
所謂完美洗牌演算法即是把輸入為:
a_1,a_2........a_n,b_1,b_2.........b_n的序列變為
b_1,a_1,b_2,a_2.......b_n,a_n 這是in perfect shufle。相對應的還有out perfect shuffle。兩者區別在於首尾元素位置變或不變。
perfect shuffle演算法要求在O(n),時間內,O(1)空間內完成。
perfect shuffle實質是一個置換。置換為:
i -> 2*i mod (2*n+1)
由於置換可以分解為一系列不相交的輪換之積。故如果能找出所有輪換的一個代表元則可很容易解決問題。
如
n=3時 輸入 1 2 3 A B C b => A 1 B 2 C 3所對應的輪換為(1,2,4)(3,6,5)
選代表元為1和3以及一個臨時變數T:
2->T,1->2
1 2 3 A B C ----------->
4->1,T->4
_ 1 3 A B C ----------->
6->T,3->6
A 1 3 2 B C ----------->
5->3,T->5
A 1 _ 2 B 3 ----------->
A 1 B 2 C 3 置換完成
因此問題就轉換為求置換的輪換分解中的代表元問題了。
文中巧妙的利用特定條件下每個不相交的輪換可有3的不同冪次產生。
我們分析長度2*n=3^k-1的置換的輪換分解。
考慮某一包含3^s( 1 =< s < k )的輪換。不妨記3^s為a_1,3^k記為m。
則輪換裡的數分別為:
a_2 = 2* a_1 mod m
a_3 = 2* a_2 mod m;
a_4 = 2* a_3 mod m;
.
.
.
a_n = 2* a_n-1 mod m
a_1 = 2* a_n mod m
則 a_1 ≡2^n * a_1 mod m; ( 最後一項中的a_n用倒數第二行乘2替代,以此類推........)
因此每個3^s開始的一個輪換滿足 : 3^s ≡3^s * 2^n mod 3^k ,且長度為n
現假設兩個不同的3^s開始的輪換存在相交的元素,記為:p
p≡3^i*2^n mod 3^s
p≡3^j*2^m mod 3^s (i,j<s)
若n,m都為0,則顯然i=j; 假設 i>j
否則應有:3^s |(3^i*2^n -3^j*2^m) ===>3^s |{ 3^i*( 2^n-3^(j-i)*2^m ) }
因為: gcd(3^s , ( 2^n -3^(j-i)*2^m) )=1 注:2^n - 3^(j-i)*2^m 只含2的冪次因子.因為由初等的數論知識可知道
am+bn即m,n的線性組合只能表示gcd(m,n)的倍數.
因此上面等式不能成立.
因此每個以不同的3^i開始的輪換不會相交.
上面證明了每個3^i開始的輪換不相交,還需要計算每個3^i起始的輪換覆蓋了所有的元素,這可以採用計數的方法證明.
因為每個3^i開始的輪換的長度滿足:
3^i≡3^i *2^N mod3^s 即2^N≡1mod3^(s-i)
所以N=φ(3^(s-i))=φ(3^s)/3^i { gcd (2,3)=1, N是滿足等式最小的數}
對i從0到3-1求和就得所有輪換的元素個數為φ(3^s) . 3^s為素數,因此φ(3^s)=3^s-1,即覆蓋了所有元素.
因此很容易得出各輪換的代表元就為3^0,3^1,3^2......3^i(i<k,i+1>k).
對於2*n不等於3^k-1的情況,可以巧妙的利用這個結論完成ferfect shuffle。
對於2*n不等於3^k-1時,先找一個最接近2*n且比2*n小的2*m=3^k-1。進行如下變換。
把序列中m+1到n+m的子序列迴圈右移m位。
A_1,A_2,A_3.......A_m-1,A_m,A_m+1......A_n,A_n+1,A_n+2,.......A_n+m,A_n+m+1.....A_2*n ->
A_1,A_2,A_3.......A_m-1,A_n+1,A_n+2.....A_n+m,A_m,A_m+1......A_n+m+1................A_2*n
然後對前2*m子序列進行上面的perfect shuffle。然後對剩下的部分進行同樣處理。
例如對於長為14的序列進行perfect shuffle置換:
輸入序列為:
1 2 3 4 5 6 7 A B C D E F G
14=2*7≠3^k.與14最接近的3^k-1是8=3^2 - 1.因此先對4+1到7+4的子序列迴圈右移4位得:
1 2 3 4 A B C D 5 6 7 E F G
對前8位進行perfect shuffle移位後得:
A 1 B 2 C 3 D 4 5 6 7 E F G
剩下的子序列為
5 6 7 E F G
長度為6 最接近的2*m1=3^k1-1是 m=1
因此對 1+1 到3+1進行迴圈右移1位得
5 E 6 7 F G
進行2*m的perfect shuffle後得整個序列為:
A 1 B 2 C 3 D 4 E 5 6 7 F G
剩下的未處理的子序列為:
6 7 F G
同樣的迴圈移位後為:
6 F 7 G
進行m=1的perfect shuffle得整個序列為:
A 1 B 2 C 3 D 4 E 5 F 6 7 G
剩下未處理的子序列為
7 G
長為2的輪換即交換,最後得整個序列為:
A 1 B 2 C 3 D 4 E 5 F 6 G 7
完成perfect shuffle。
移位是線性時間,3^k - 1的perfect shuffle置換也是線性時間,最後的遞迴是對剩下的子序列進行同樣的操作,因此整個過程線上性時間內完成。而且需要的輔助空間為常數-個額外臨時變數。
實現代碼:
#include "stdio.h"
//輪換
void Cycle(int Data[],int Lenth,int Start)
{
int Cur_index,Temp1,Temp2;
Cur_index=(Start*2)%(Lenth+1);
Temp1=Data[Cur_index-1];
Data[Cur_index-1]=Data[Start-1];
while(Cur_index!=Start)
{
Temp2=Data[(Cur_index*2)%(Lenth+1)-1];
Data[(Cur_index*2)%(Lenth+1)-1]=Temp1;
Temp1=Temp2;
Cur_index=(Cur_index*2)%(Lenth+1);
}
}
//數組迴圈移位 參考編程珠璣
void Reverse(int Data[],int Len)
{
int i,Temp;
for(i=0;i<Len/2;i++)
{
Temp=Data[i];
Data[i]=Data[Len-i-1];
Data[Len-i-1]=Temp;
}
}
void ShiftN(int Data[],int Len,int N)
{
Reverse(Data,Len-N);
Reverse(&Data[Len-N],N);
Reverse(Data,Len);
}
//滿足Lenth=3^k-1的perfect shfulle的實現
void Perfect1(int Data[],int Lenth)
{
int i=1;
if(Lenth==2)
{
i=Data[Lenth-1];
Data[Lenth-1]=Data[Lenth-2];
Data[Lenth-2]=i;
return;
}
while(i<Lenth)
{
Cycle(Data,Lenth,i);
i=i*3;
}
}
//尋找最接近N的3^k
int LookUp(int N)
{
int i=3;
while(i<=N+1) i*=3;
if(i>3) i=i/3;
return i;
}
void perfect(int Data[],int Lenth)
{
int i,startPos=0;
while(startPos<Lenth)
{
i=LookUp(Lenth-startPos);
ShiftN(&Data[startPos+(i-1)/2],(Lenth-startPos)/2,(i-1)/2);
Perfect1(&Data[startPos],i-1);
startPos+=(i-1);
}
}
#define N 100
void main()
{
int data[N]={0};
int i=0;
int n;
printf("please input the number of data you wanna to test(should less than 100):/n");
scanf("%d",&n);
if(n&1)
{
printf("sorry,the number should be even ");
return;
}
for(i=0;i<n;i++)
data[i]=i+1;
perfect(data,n);
for(i=0;i<n;i++)
printf("%d ",data[i]);
}