完美洗牌算法

Posted 为了更优秀的你,加油!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了完美洗牌算法相关的知识,希望对你有一定的参考价值。

本文是看完july博客完美洗牌之后的个人笔记。

题目:把a1,a2,a3,a4,...,an-1 an,b1,b2,b3,...,bn-1,bn变成a1,b1,a2,b1,...,an,bn.要求时间复杂度为O(n),空间复杂度为O(1).

1.位置置换算法:b是新开的一个数组,但是时间复杂度为O(n),空间复杂度为O(n).

void perfectShuttle(int a[],int n){
    int n2=n*2,b[N];
    for(int i=1;i<=n2;i++)
        b[(2*i)%(n2+1)]=a[i];
    for(int i=1;i<=n2;i++)
        a[i]=b[i];
}

2.不管你n是奇数还是偶数,每个位置的元素都将变为第(2*i) % (2n+1)个元素: 

数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 2 * n + 1,时间复杂度O(圈长) 

void CycleLeader(int a[],int from,int mod){
    for(int i=from*2%mod;i!=from;i=i*2%mod)
        swap(a[i], a[from]);
}

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

 

完美洗牌算法,其算法流程为:

输入数组 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 - 13^i是个圈的头部,做cycle_leader算法,数组长度为m,所以对2 * m + 1 取模

step 4 对数组的后面部分A[2 * m + 1.. 2 * n]继续使用本算法, 这相当于n减小了m

//翻转字符串时间复杂度O(to - from)
void reverse(int *a, int from, int to) {
    for (; from < to; ++from, --to)
        swap(a[from],a[to]);
}
//循环右移num位 时间复杂度O(n)
void RightRotate(int *a, int num, int n) {
    reverse(a, 1, n - num);
    reverse(a, n - num + 1, n);
    reverse(a, 1, n);
}
void PerfectShuffle2(int *a, int n)
{
    int n2, m, i, k, t;
    while( n > 1 )
    {
        // step 1
        n2 = n * 2;
        for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)
            ;
        m /= 2;
        // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)
        // step 2
        Right_Rotate(a + m, m, n);
        // step 3
        for (i = 0, t = 1; i < k; ++i, t *= 3) {
            CycleLeader(a , t, m * 2 + 1); }
        //step 4
        a += m * 2; n -= m;
    }
    // n = 1
    swap(a[1],a[2]);
}

 

以上是关于完美洗牌算法的主要内容,如果未能解决你的问题,请参考以下文章

完美洗牌算法

完美洗牌算法

算法系列:完美洗牌算法

完美洗牌算法

[转]完美洗牌(Perfect Shuffle)问题

游戏常用算法-洗牌算法