• 完美洗牌算饭


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

    其实完美洗牌算法,应该给我说明白题,最少举个例子吧,当时确实大意了,也没问清楚就直接不会了,其实题意是有个长度为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
  • 相关阅读:
    叶问14
    叶问13
    叶问12
    叶问11
    叶问10
    叶问9
    Java三种循环之间的区别
    利用Java对象数组制作简易学生管理系统
    什么叫java方法重载?
    Java编译器的常量优化
  • 原文地址:https://www.cnblogs.com/zmlctt/p/4001088.html
Copyright © 2020-2023  润新知