第三十五章:完美洗牌算法

完美洗牌算法

     题目详情:有个长度为2n的数组{a1,a2,a3,...,an,b1,b2,b3,...,bn},希望排序后{a1,b1,a2,b2,....,an,bn},请考虑有无时间复杂度o(n),空间复杂度0(1)的解法。


#include <iostream>     
#include <algorithm>
#include <functional>
using namespace std;    
/*
	完美洗牌算法
	题目详情:有个长度为2n的数组{a1,a2,a3,...,an,b1,b2,b3,...,bn},
	希望排序后{a1,b1,a2,b2,....,an,bn},
	请考虑有无时间复杂度o(n),空间复杂度0(1)的解法。
*/
#define N 1000

// 时间O(n),空间O(n) 数组下标从1开始  
//任意的第i个元素,最终换到了 (2 * i) % (2 * n + 1)的位置
void pefect_shuffle1(int *a,int n) {  
	int n2 = n * 2, i, b[N];  
    for (i = 1; i <= n2; ++i) {  
        b[(i * 2) % (n2 + 1)] = a[i];  
    }  
    for (i = 1; i <= n2; ++i) {  
        a[i] = b[i];  
    }  
}  
//时间O(nlogn) 空间O(1) 数组下标从1开始    
void perfect_shuffle2(int *a,int n) {    
	int t,i;    
    if (n == 1) {    
        t = a[1];    
        a[1] = a[2];    
        a[2] = t;    
        return;    
    }    
    int n2 = n * 2, n3 = n / 2;    
    if (n % 2 == 1) {  //奇数的处理    
        t = a[n];    
        for (i = n + 1; i <= n2; ++i) {    
            a[i - 1] = a[i];    
        }    
        a[n2] = t;    
        --n;    
    }    
    //到此n是偶数    
        
    for (i = n3 + 1; i <= n; ++i) {    
        t = a[i];    
        a[i] = a[i + n3];    
        a[i + n3] = t;    
    }    
        
    // [1.. n /2]    
    perfect_shuffle2(a, n3);    
    perfect_shuffle2(a + n, n3);    
}    
/*
 
	第一个圈:1 -> 2 -> 4 -> 8 -> 7 -> 5 -> 1
	第二个圈:3 -> 6 -> 3:

	原始数组:1 2 3 4 5 6 7 8
	数组小标:1 2 3 4 5 6 7 8

	走第一圈:5 1 (3) 2 7 (6) 8 4
	走第二圈:5 1 6 2 7 3 8 4 
*/
/*
神级结论:若2*n=(3^k - 1),则可确定圈的个数及各自头部的起始位置
对于2*n = (3^k-1)这种长度的数组,恰好只有k个圈,且每个圈头部的起始位置分别是1,3,9,...3^(k-1)。
*/
//数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 2 * n + 1,时间复杂度O(圈长) 
void cycle_leader(int *a,int from, int mod) {  
	int last = a[from],t,i;   
    for (i = from * 2 % mod;i != from; i = i * 2 % mod) {  
        t = a[i];  
        a[i] = last;  
        last = t;     
    }  
    a[from] = last;  
}  
//翻转字符串时间复杂度O(to - from)  
void reverse(int *a,int from,int to) {  
int t;  
    for (; from < to; ++from, --to) {  
        t = a[from];  
        a[from] = a[to];  
        a[to] = t;  
    }  
      
}  
//循环右移num位 时间复杂度O(n)  
void right_rotate(int *a,int num,int n) {  
    reverse(a, 1, n - num);  
    reverse(a, n - num + 1,n);  
    reverse(a, 1, n);  
}  
/*
完美洗牌算法,其算法流程为:
	输入数组 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 - 1,3^i是个圈的头部,做cycle_leader算法,数组长度为m,所以对2 * m + 1取模。
	step 4 对数组的后面部分A[2 * m + 1.. 2 * n]继续使用本算法, 这相当于n减小了m。
*/
//时间O(n),空间O(1)  
void perfect_shuffle3(int *a,int n) {  
	int n2, m, i, k,t;  
    for (;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) {  
            cycle_leader(a , t, m * 2 + 1);  
              
        }  
          
        //step 4  
        a += m * 2;  
        n -= m;  
    }  
    // n = 1  
    t = a[1];  
    a[1] = a[2];  
    a[2] = t;  
}  
int main()
{
	int arr[]={0,1,2,3,4,5,6,7,8};
	perfect_shuffle3(arr,4);
	for_each(arr,arr+9,[](int i){cout<<i<<endl;});
    return 0;
}   





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值