首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

关于完美洗牌问题的若干思考

  • 25-03-02 17:21
  • 2759
  • 11102
blog.csdn.net

前面学习了完美洗牌问题 

完美洗牌算法学习

又写了一个证明

完美洗牌问题的证明


进一步思考了其他的一些问题:

完美洗牌问题: 给定的输入a1, a2, a3, ……aN, b1,b2,……bN,输出b1,a1,b2,a2,b3,a3…… bN,aN

(1) 如果要求输出是a1,b1,a2,b2……aN,bN怎么办?

这个问题在学习的时候已经考虑过,只是觉得如果先把a部分和b部分交换掉,或者最后再交换相邻的一组两个位置的方法不够美观。

现在想想可以这样,原数组第一个和最后一个不变,中间的2 * (n - 1)项用原始的标准完美洗牌算法做就可以了。

(2) 完美洗牌问题的逆问题:

给定b1,a1,b2,a2,……bN,aN, 输出a1,a2,a3,……aN,b1,b2,b3,……bN

这相当于把偶数位上的数放到一起,奇数位上的数放到一起。

关键问题: 我们需要把cycle_leader算法改一下,沿着圈换回去。改造后的叫reverse_cycle_leader,代码如下:

  1. //逆变换,数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 2 * n + 1,时间复杂度O(圈长)
  2. void reverse_cycle_leader(int *a,int from, int mod) {
  3. int last = a[from],next, i;
  4. for (i = from;;i = next) {
  5. next = i * 2 % mod;
  6. if (next == from) {
  7. a[i] = last;
  8. break;
  9. }
  10. a[i] = a[next];
  11. }
  12. }

按照完美洗牌算法,我们同样把数分为m和(n - m)两部分。

假设我们把前面若干项已经置换成先a后b的形式了,现在把这m项也置换成先a后b的形式,我们需要把这m项中的a部分换到前面去,这里需要一个循环右移,还要知道以前处理了多长。总之,这个逆shuffle算法需要小心实现一下,代码如下:

  1. //逆shuffle 时间O(n),空间O(1)
  2. void reverse_perfect_shuffle3(int *a,int n) {
  3. int n2, m, i, k, t, done = 0;
  4. for (;n > 1;) {
  5. // step 1
  6. n2 = n * 2;
  7. for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)
  8. ;
  9. m /= 2;
  10. // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)
  11. for (i = 0, t = 1; i < k; ++i, t *= 3) {
  12. reverse_cycle_leader(a , t, m * 2 + 1);
  13. }
  14. if (done) {
  15. right_rotate(a - done, m, done + m); //移位
  16. }
  17. a += m * 2;
  18. n -= m;
  19. done += m;
  20. }
  21. // n = 1
  22. right_rotate(a - done, 1, done + 2);
  23. }

总体算法(含变换和逆变换、还有测试代码)如下,注意所有的下标均从1开始:

  1. #include
  2. #include
  3. #include
  4. using namespace std;
  5. //数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 2 * n + 1,时间复杂度O(圈长)
  6. void cycle_leader(int *a,int from, int mod) {
  7. int last = a[from],t,i;
  8. for (i = from * 2 % mod;i != from; i = i * 2 % mod) {
  9. t = a[i];
  10. a[i] = last;
  11. last = t;
  12. }
  13. a[from] = last;
  14. }
  15. //翻转字符串时间复杂度O(to - from)
  16. void reverse(int *a,int from,int to) {
  17. int t;
  18. for (; from < to; ++from, --to) {
  19. t = a[from];
  20. a[from] = a[to];
  21. a[to] = t;
  22. }
  23. }
  24. //循环右移num位 时间复杂度O(n)
  25. void right_rotate(int *a,int num,int n) {
  26. reverse(a, 1, n - num);
  27. reverse(a, n - num + 1,n);
  28. reverse(a, 1, n);
  29. }
  30. //时间O(n),空间O(1)
  31. void perfect_shuffle3(int *a,int n) {
  32. int n2, m, i, k,t;
  33. for (;n > 1;) {
  34. // step 1
  35. n2 = n * 2;
  36. for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)
  37. ;
  38. m /= 2;
  39. // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)
  40. // step 2
  41. right_rotate(a + m, m, n);
  42. // step 3
  43. for (i = 0, t = 1; i < k; ++i, t *= 3) {
  44. cycle_leader(a , t, m * 2 + 1);
  45. }
  46. //step 4
  47. a += m * 2;
  48. n -= m;
  49. }
  50. // n = 1
  51. t = a[1];
  52. a[1] = a[2];
  53. a[2] = t;
  54. }
  55. //逆变换,数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 2 * n + 1,时间复杂度O(圈长)
  56. void reverse_cycle_leader(int *a,int from, int mod) {
  57. int last = a[from],next, i;
  58. for (i = from;;i = next) {
  59. next = i * 2 % mod;
  60. if (next == from) {
  61. a[i] = last;
  62. break;
  63. }
  64. a[i] = a[next];
  65. }
  66. }
  67. //逆shuffle 时间O(n),空间O(1)
  68. void reverse_perfect_shuffle3(int *a,int n) {
  69. int n2, m, i, k, t, done = 0;
  70. for (;n > 1;) {
  71. // step 1
  72. n2 = n * 2;
  73. for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)
  74. ;
  75. m /= 2;
  76. // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)
  77. for (i = 0, t = 1; i < k; ++i, t *= 3) {
  78. reverse_cycle_leader(a , t, m * 2 + 1);
  79. }
  80. if (done) {
  81. right_rotate(a - done, m, done + m); //移位
  82. }
  83. a += m * 2;
  84. n -= m;
  85. done += m;
  86. }
  87. // n = 1
  88. right_rotate(a - done, 1, done + 2);
  89. }
  90. //测试代码
  91. int main() {
  92. const int N = 100000;
  93. int a[N * 2 + 1],i;
  94. for (i = 1; i <= 2 * N; ++i) {
  95. a[i] = i;
  96. }
  97. perfect_shuffle3(a, N);
  98. reverse_perfect_shuffle3(a, N);
  99. for (i = 1; i <= 2 * N; ++i) {
  100. printf("%d\n", a[i]);
  101. }
  102. for (i = 1; i <= 2 * N; ++i) {
  103. if (a[i] != i) {
  104. puts("NO");
  105. return 0;
  106. }
  107. }
  108. puts("YES");
  109. return 0;
  110. }


(3) 如果输入是a1,a2,……aN, b1,b2,……bN, c1,c2,……cN,要求输出是c1,b1,a1,c2,b2,a2,……cN,bN,aN怎么办?

这个问题也不是我凭空想像出来的,这是在careercup上看到过的面试题。

我研究了下这个问题,对于任意位置i = 1..3 * N 我们发现

原始1 <= i <= N 时,即a部分, 转移到的位置是 3 * i

原始N < i <= 2 * N 时 即b部分,转移到的位置是 3 * i - (3 * N + 1)

原始2 * N < i <= 3 * N时,即c部分转移到的位置是 3 * i - 2 * (3 * N + 1)

于是我们得到映射位置 i' = i mod (3 * N + 1)

之所以要把a,b,c的顺序反过来,因为有如上这么好的形式。

剩下的问题和学习完美洗牌算法差不多,我们试图对一个特定的长度解决掉。

仿照完美洗牌算法的思路,我验证了3是7的原根,是49的原根,于是3是7^k的原根。于是,我们可以把原来的圈按照截取出一个m,满足3 * m = 7 ^ k - 1,截取出一个m长度后,我们同样需要循环移位,使得(a1..am)(b1..bm)(c1..cm)在一起,这里要循移位两次。算法的步骤如下:

step 1 找到 3 * m = 7^k - 1 使得 7^k <= 3 * n < 7^(k +1)

step 2 把a[m + 1..n + m]那部分循环移m位,再把a[m * 2 + 1..2 * n + m]那部分循环右移m位,这样把数组分成了m和(n - m)两部分。

step 3 对每个i = 0,1,2..k - 1,7^i是个圈的头部,做cycle_leader算法,数组长度为m,所以对3 * m + 1取模。

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

代码:

  1. //翻转字符串时间复杂度O(to - from)
  2. void reverse(int *a,int from,int to) {
  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. //循环右移num位 时间复杂度O(n)
  11. void right_rotate(int *a,int num,int n) {
  12. reverse(a, 1, n - num);
  13. reverse(a, n - num + 1,n);
  14. reverse(a, 1, n);
  15. }
  16. //数组下标从1开始,from是圈的头部,mod是要取模的数 mod 应该为 3 * n + 1,时间复杂度O(圈长)
  17. void cycle_leader(int *a,int from, int mod) {
  18. int last = a[from],t,i;
  19. for (i = from * 3 % mod;i != from; i = i * 3 % mod) {
  20. t = a[i];
  21. a[i] = last;
  22. last = t;
  23. }
  24. a[from] = last;
  25. }
  26. //时间O(n),空间O(1)
  27. void perfect_shuffle3n(int *a,int n) {
  28. int n3, m, i, k,t;
  29. for (;n > 2;) {
  30. // step 1
  31. n3 = n * 3;
  32. for (k = 0, m = 1; n3 / m >= 7; ++k, m *= 7)
  33. ;
  34. m /= 3;
  35. // 3m = 7^k - 1 , 7^k <= 3n < 7^(k + 1)
  36. // step 2
  37. right_rotate(a + m, m, n);
  38. right_rotate(a + m * 2, m , n * 2 - m);
  39. // step 3
  40. for (i = 0, t = 1; i < k; ++i, t *= 7) {
  41. cycle_leader(a , t, m * 3 + 1);
  42. }
  43. //step 4
  44. a += m * 3;
  45. n -= m;
  46. //printf("n = %d m = %d\n",n, m);
  47. //getchar();
  48. }
  49. if (n == 2) {
  50. cycle_leader(a, 1, 7);
  51. }
  52. else if (n == 1) {
  53. t = a[1];
  54. a[1] = a[3];
  55. a[3] = t;
  56. }
  57. }



注:本文转载自blog.csdn.net的cpcs的文章"http://blog.csdn.net/caopengcs/article/details/10521603"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top