完美洗牌算法
Posted Jarvis_Xu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了完美洗牌算法相关的知识,希望对你有一定的参考价值。
完美洗牌算法
题目详情:有个长度为2n的数组{a1,a2,a3,...,an,b1,b2,b3,...,bn},希望排序后{a1,b1,a2,b2,....,an,bn},请考虑有无时间复杂度o(n),空间复杂度0(1)的解法。
http://blog.csdn.net/starcuan/article/details/19079913
#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; }
以上是关于完美洗牌算法的主要内容,如果未能解决你的问题,请参考以下文章