perfect shuffle 算法的一個線性複雜度實現

今天又發現一個關於完美洗牌的算法。這個比較簡單一些,由 microsoft的Peiyush Jain提出。 ­算法

原論文:      A Simple In-Place Algorithm for In-Shuffle. ­編程

                 Peiyush Jain, Microsoft Corporation. ­數組

                            July 2004 ­less

問題描述: ­spa

所謂完美洗牌算法便是把輸入爲: ­code

a_1,a_2........a_n,b_1,b_2.........b_n的序列變爲 ­orm

b_1,a_1,b_2,a_2.......b_n,a_n 這是in perfect shufle。相對應的還有out perfect shuffle。二者區別在於首尾元素位置變或不變。 ­ci

perfect shuffle算法要求在O(n),時間內,O(1)空間內完成。 ­input

perfect shuffle實質是一個置換。置換爲: ­it

                                       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  -----------> _  1  3   A   B   C 

                       4->1,T->4    

_  1  3   A   B   C  ----------->  A  1  3   2   B   C                     ­

                           6->T,3->6 ­

A  1  3   2   B   C  -----------> ­A  1  _   2   B   3

                          5->3,T->5 ­

A  1  _   2   B   3  -----------> ­A  1  B   2   C   3   置換完成

 ­

所以問題就轉換爲求置換的輪換分解中的表明元問題了。 ­

文中巧妙的利用特定條件下每一個不相交的輪換可有3的不一樣冪次生成­。

 

見論文:A Simple In-Place Algorithm for In-Shuffle. 

                 Peiyush Jain, Microsoft Corporation. 

一、問題描述: 

所謂完美洗牌算法便是把輸入爲: 

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。

perfect shuffle算法要求在O(n),時間內,O(1)空間內完成。 

 

perfect shuffle實質是一個置換。置換爲: 

                                       i -> 2*i mod (2*n+1) 

因爲置換能夠分解爲一系列不相交的輪換之積。故若是能找出全部輪換的一個表明元則可很容易解決問題。 

如 len=3時 輸入 1  2  3  A  B  C b   => A   1   B   2   C   3所對應的輪換爲(1,2,4)(3,6,5) 

選表明元爲1和3以及一個臨時變量T:                      

1  2  3   A   B   C  ----------->  2->T,1->2   

_  1  3   A   B   C  ----------->   4->1,T->4                

A  1  3   2   B   C  -----------> 6->T,3->6              

A  1  _   2   B   3  -----------> 5->3,T->5 

A  1  B   2   C   3   置換完成 

所以問題就轉換爲求置換的輪換分解中的表明元問題了。 

文中巧妙的利用特定條件下:

即(2的任意次冪)對(3的k次冪)取模,能獲得一個輪換環,而且環與環之間恰好互不相交 。 

咱們分析長度2*n=3^k-1的置換的輪換分解。 (若長度爲2*4=8,則8=3^2-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_len = 2* a_len-1 mod m 

  a_1 = 2* a_len   mod m 

則  a_1  ≡2^len * a_1 mod m;  ( 最後一項中的a_len用倒數第二行乘2替代,以此類推........) ,

依此,假設m=9,能夠肯定每一個輪換中,len的值

a_1=3^s=3^0=1,則1=2^len * 1 mod m,則 len=2;

a_1=3^s=3^1=3,則3=2^len * 3 mod m,則 len=6  

所以每一個3^s開始的一個輪換知足 :  

3^s ≡3^s * 2^len mod 3^k ,且輪換的長度爲len。

補充:若對1 2 3 4 5 6 7 8 9 10進行洗牌則首先交換成

1 2 3 4 6 7 8 9 5 10   (n=4,所以咱們只對須要的前8個先調整到前面)

下面舉了個例子:洗牌置換過程例證,它例舉了將1234ABCD置換成A1B2C3D4的過程,請將圖中的n理解爲len,k=2。

#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]);

}
相關文章
相關標籤/搜索