被大腾讯问到了完美洗牌算法,瞬间就跪了,其实原来看过,只可惜都忘了啊,现在在补充进来吧。

其实完美洗牌算法,应该给我说明白题,最少举个例子吧,当时确实大意了,也没问清楚就直接不会了,其实题意是有个长度为2n的数组{a1,a2,a3,a4,..,an,b1,b2,b3,b4,...,bn},希望排序后{a1,b1,a2,b2,...,an,bn},最好要求时间复杂度为O(n),空间复杂度为O(1)。

解法一:蛮力交换方法

1.1 说白了就是b1和a2,a3,a4,...,an交换,然后是b2和a3,a4,a5,...,an交换,直到bn不交换。复杂度O(n2),肯定不行。

1.2中间交换

交换中间的元素方法,例如:

a1,a2,a3,a4,b1,b2,b3,b4交换a4,b1为:a1,a2,a3,b1,a4,b2,b3,b4

交换中间两个,交换a3,b1和a4,b2为a1,a2,b1,a3,b2,a4,b3,b4,

下一次交换中间三个,依次增加,最后即可但时间复杂度依然为O(n2)

解法二:完美洗牌算法O(n)

有人研究出了完美洗牌算法可以将a1,a2,a3,a4,b1,b2,b3,b4通过O(n)变成b1,a1,b2,a2,b3,a4,b4,a4,通过两两交换即可完成a1,b1,a2,b2,a3,b3,a4,b4.这里来说一下如何用O(n)完成。

2.1 位置置换算法,需要空间复杂度O(n),时间复杂度O(n)

原序: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;2->4,3->6,4->8,5->1,6->3,7->5,8->7上即第i个元素放到了第(2*i)%(2*n+1)位置上

代码:

1 void pefect_shuffle1(int *a,int n){
2 int n2=n*2,i,b[N];
3 for(int i=1;i<=n2;i++)
4 {
5    b[(i*2)%(n2+1)]=a[i];
6 }
7 for(int i=1;i<=n2;i++)
8 a[i]=b[i];
9 }

我们注意到:1->2->4->8->7->5->1;和3->6->3这就是完美洗牌的关键后面继续说。

2.2 分治处理O(nlogn)

可以将大问题化成两个子问题处理,例如:

n=4情况

a1,a2,a3,a4,b1,b2,b3,b4,交换前半段的后n/2和后半段的前n/2为a1,a2,b1,b2,a3,a4,b3,b4就变成两个子问题。

n=5情况

a1,a2,a3,a4,a5,b1,b2,b3,b4,b5,将中间的给放到最后,剩下的前移即:a1,a2,a3,a4,b1,b2,b3,b4,b5,a5,变成处理n=4情况

 1 #include <iostream>
 2 using namespace std;
 3 void perfect_shuffle2(int *a ,int n)
 4 {
 5     int t,i;
 6     if(n==1)
 7     {
 8         swap(a[1],a[2]);
 9         return;
10     }
11     int n2=n*2,n3=n/2;
12     if(n%2==1)
13     {
14         t=a[n];
15         for(i=n+1;i<=n2;i++)
16         {
17             a[i-1]=a[i];
18         }
19         a[n2]=t;
20         --n;
21     }
22     for(i=n3+1;i<=n;i++)
23     {
24         t=a[i];
25         a[i]=a[i+n3];
26         a[i+n3]=t;
27     }
28     perfect_shuffle2(a,n3);
29     perfect_shuffle2(a+n,n3);
30 }

时间复杂度O(nlogn),空间不算递归调用栈的话为O(1)

2.3 真正完美算法O(n),O(1)

利用走圈方法:

1->2->4->8->7->5->1;

3->6->3;

 1 void circle_leader(int *a ,int form ,int mod)  //mod=2*n+1
 2 {
 3     int last=a[from],t,i;
 4     for(i=from*2%mod;i!=from;i=i*2%mod)
 5     {
 6         t=a[i];
 7         a[i]=last;
 8         last=t;
 9     }
10     a[from]=last;
11 }

神级结论:若2*n=(3^k-1),则可以确定圈的个数及各自头部的起始位置

对于2*n=(3^k-1)这种长度的数组,恰好只有k个圈,且每个圈头部的起始位置分别为:1,3,9,...,3^(k-1)。

如果n满足神结论,则直接可以计算,如果不满足时,采用分治的思想。将n分成m满足结论,剩下的n-m继续这样做。

a1,a2,a3,..,am,a(m+1),a(m+2),a(m+3),...,an,b1,b2,b3,...,bm,b(m+1),b(m+2),b(m+3),...,b(n).

通过翻转方法:a1,a2,a3,..am,b1,b2,b3,...,bm,a(m+1),a(m+2),a(m+3),a(m+4),...an,b(m+1),b(m+2),b(m+3),...bn

reverse代码:

 1 void reverse(int *a,int from ,int to)
 2 {
 3     int t;
 4     for(;from<to;++from,--to){
 5         t=a[from];
 6         a[from]=a[to];
 7         a[to]=t;
 8     }
 9 }
10 void right_rotate(int *a ,int num,int n)        //n为总数,num为右面的个数
11 {
12     reverse(a,1,n-num);
13     reverse(a,n-num+1,n);
14     reverse(a,1,n);
15 }

确定m方法:3^k<=2*m<3^(k+1)。

最终真正完美洗牌算法代码实现:

 1 void circle_leader(int *a ,int from ,int mod)  //mod=2*n+1  from为头
 2 {
 3     int last=a[from],t,i;
 4     for(i=from*2%mod;i!=from;i=i*2%mod)
 5     {
 6         t=a[i];
 7         a[i]=last;
 8         last=t;
 9     }
10     a[from]=last;
11 }
12 
13 void reverse(int *a,int from ,int to)
14 {
15     int t;
16     for(;from<to;++from,--to){
17         t=a[from];
18         a[from]=a[to];
19         a[to]=t;
20     }
21 }
22 void right_rotate(int *a ,int num,int n)        //n为总数,num为右面的个数
23 {
24     reverse(a,1,n-num);
25     reverse(a,n-num+1,n);
26     reverse(a,1,n);
27 }
28 void perfect_shuffle3(int * a ,int n)
29 {
30     int n2,m,i,k,t;
31     while(n>1)
32     {
33         n2=n*2;
34         for(k=0,m=1;n2/m>=3;k++,m*=3)
35         ;
36         m/=2;    //m即为分出的可以用神结论的,正常应该是n1*2=3^k-1(即为m)   
37                  //,但因为2的倍数和三的倍数,所以不用减一就可以。
38         right_rotate(a+m,m,n);
39         for(i=0,t=1;i<k;++i,t*=3)
40         {
41             cycle_leader(a,t,m*2+1);
42         }
43         a+=m*2; //a指针前进
44         n-=m;
45     }
46     //剩只有a1,b1情况
47     t=a[1];
48     a[1]=a[2];
49     a[2]=t;
50 }
51 //全部完事了,但是注意这里是b1,a1,b2,a2,b3,a3,b4,a4,....,bn,an

 

 posted on 2014-09-29 22:42  zmlctt  阅读(889)  评论(0编辑  收藏  举报