找回密码
 欢迎注册
查看: 25966|回复: 10

[提问] 嵌套循环的简化

[复制链接]
发表于 2012-9-19 23:50:35 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?欢迎注册

×
比如,在求水仙花数时可以用如下的方法,按照这种方法求3-9位的水仙花数时,就需要很多循环(貌似是42个for),简化类似的循环我知道可以用递归,据说还可以用矩阵或者别的方法,但不知道该如何实现(说明:这里不是想探讨水仙花数的算法,同样是穷举我知道有相对比较简洁些的如分拆各位数字等):
  1. #include <stdio.h>
  2. #include <time.h>
  3. int power(int a,int b)
  4. {
  5. int t=1,i;
  6. for (i=0;i<b;i++)
  7. t*=a;
  8. return t;
  9. }
  10. int main(int argc, char *argv[])
  11. {
  12. double t0=clock();
  13. int a,b,c,d,e,f,g;
  14. for (a=1;a<=9;a++)
  15. for (b=0;b<=9;b++)
  16. for (c=0;c<=9;c++)
  17. for (d=0;d<=9;d++)
  18. for (e=0;e<=9;e++)
  19. for (f=0;f<=9;f++)
  20. for (g=0;g<=9;g++)
  21. {
  22. int num=1000000*a+100000*b+10000*c+1000*d+100*e+10*f+g;
  23. if (num==power(a,7)+power(b,7)+power(c,7)+power(d,7)+power(e,7)+power(f,7)+power(g,7))
  24. printf(" %d ",num);
  25. }
  26. printf("\nElapsed time :%f s",(clock()-t0)/CLOCKS_PER_SEC);
  27. return 0;
  28. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-20 09:16:58 | 显示全部楼层
这个问题我自己也常碰到,比如我用在构造高维幻方时。 下面介绍一下我自己创造的一个方法(也许早已是通法), 也就是借用我们常用的计数法则:
  1. // 定义一个n元数组A[n],
  2. // 下标从小到大(0 to n-1)对应于嵌套for循环的由外到内的变量,
  3. // 它们的值则对应于for循环中的循环变量值。
  4. int i;
  5. for ( i=0; i<n; ++i )
  6. {
  7. A[i] <-- A[i]_first; // 初始值
  8. }
  9. for ( ; ; )
  10. {
  11. // Call your function ...
  12. i = n;
  13. while ( --i >= 0 && A[i] == A[i]_last /* 终止值 */ )
  14. {
  15. A[i] <-- A[i]_first; // 复位
  16. }
  17. if ( i < 0 )
  18. break;
  19. A[i] <--next(A[i]); // 也许不是简单的递增,反正是 next 到下一个有效值。
  20. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-20 12:41:42 | 显示全部楼层
这也是我自己创造的方法,不知道别人是否也做过类似的事情。 这是一个混合进制问题,其核心是进位处理。 比如:有个4位混合进制数, 最低位数字的范围是 0 to 5 次低位数字的范围是 0 to 8 第三低位数字的范围是 0 to 5 最高位数字的范围是 0 to 3 如果我们想要列举所有的数,最容易想到的方法是1个四重循环,代码如下 int i,j,k,m; for (i=0;i<=3;i++) { for (j=0;j<=5;j++) { for (k=0;k<=8;k++) { for (m=0;m<=5;m++) { printf("%d%d%d%d\n",i,j,k,m); } } } } 上面的问题有很大的局限,如果我们想要列举一个20位混合进制数,我们不得不重新编写一个具有20重循环的程序,当然,一个20重循环的程序既不优雅也不便于扩充和维护。我们能否找一个通用的方法来解决这个问题的,答案是肯定的。 以上面的例子为例。 对于每一位数字,我们定义一个结构体,如下。它包含3个字段min,max,curr. 在这个"位"上,其数字的值的范围为min到max,curr表示当前的数字,等价于上面程序的i,j,k.m。 typedef struct _dig_st { int min; int max; int curr; }DIG_ST; 一个4位数需要这样的四个结构体变量,故我们可以定义具有4个元素的结构体数组,为了便于扩充计,这里可定义更大的数组,比如32个元素。然会对每个元素的min,max字段作初始化。 代码如下: 定义一个数组 DIG_ST permutation[32]; 根据你的需要,定义这个混合进制数的每一位数的最小值和最大值 void init() { //最低位数字的范围是 0 to 5 permutation[0].min=0; permutation[0].max=5; //次低位数字的范围是 0 to 8 permutation[1].min=0; permutation[1].max=8; //第三低位数字的范围是 0 to 5 permutation[2].min=0; permutation[2].max=5; //最高位数字的范围是 0 to 3 permutation[3].min=0; permutation[3].max=3; } 取得第一个排列前面的那个排列,执行nextPerm函数后,便得到第一个排列 void preFirstPerm(DIG_ST arr[], int len) { int i; arr[0].curr=arr[0].min-1; for (i=1;i=len) return FALSE; arr[ i ].curr++; for (i--;i>=0;i--) arr[ i ].curr=arr[ i ].min; return TRUE; } 使用新的代码实现第一个例子,等价于原来的四重循环。很容易扩展为一个20重循环的替代品。 楼下给出完整的可编译通过的代码。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-20 12:45:04 | 显示全部楼层
  1. // By liangbch@263.net, 2012-9-20
  2. #include "stdio.h"
  3. #include "stdlib.h"
  4. #define TRUE 1
  5. #define FALSE 0
  6. typedef struct _dig_st
  7. {
  8. int min;
  9. int max;
  10. int curr;
  11. }DIG_ST;
  12. /*
  13. 一个4位数需要这样的四个结构体变量,故我们可以定义具有4个元素的结构体数组,为了便于扩充计,这里可定义更大的数组,比如32个元素。然会对每个元素的min,max字段作初始化。
  14. 代码如下:
  15. */
  16. //定义一个数组
  17. DIG_ST permutation[32];
  18. //根据你的需要,定义这个混合进制数的每一位数的最小值和最大值
  19. void init()
  20. {
  21. //最低位数字的范围是 0 to 5
  22. permutation[0].min=0; permutation[0].max=5;
  23. //次低位数字的范围是 0 to 8
  24. permutation[1].min=0; permutation[1].max=8;
  25. //第三低位数字的范围是 0 to 5
  26. permutation[2].min=0; permutation[2].max=5;
  27. //最高位数字的范围是 0 to 3
  28. permutation[3].min=0; permutation[3].max=3;
  29. }
  30. //取得第一个排列前面的那个排列,执行nextPerm函数后,便得到第一个排列
  31. void preFirstPerm(DIG_ST arr[], int len)
  32. {
  33. int i;
  34. arr[0].curr=arr[0].min-1;
  35. for (i=1;i<len;i++)
  36. {
  37. arr[i].curr=arr[i].min;
  38. }
  39. }
  40. //返回 TURE: 表示得到下一个排列
  41. //返回 FALSE: 表示所有的排列都枚举过了
  42. int nextPerm(DIG_ST arr[],int len)
  43. {
  44. int i=0;
  45. while (i<len)
  46. {
  47. if ( arr[i].curr < arr[i].max)
  48. break;
  49. else
  50. i++;
  51. }
  52. if (i>=len)
  53. return FALSE;
  54. arr[i].curr++;
  55. for (i--;i>=0;i--)
  56. arr[i].curr=arr[i].min;
  57. return TRUE;
  58. }
  59. void old_flow()
  60. {
  61. int i,j,k,m;
  62. for (i=0;i<=3;i++)
  63. {
  64. for (j=0;j<=5;j++)
  65. {
  66. for (k=0;k<=8;k++)
  67. {
  68. for (m=0;m<=5;m++)
  69. {
  70. printf("%d%d%d%d\n",i,j,k,m);
  71. }
  72. }
  73. }
  74. }
  75. }
  76. void new_flow()
  77. {
  78. init();
  79. preFirstPerm(permutation,4);
  80. while ( 1 )
  81. {
  82. int bRet;
  83. bRet=nextPerm(permutation,4);
  84. if ( !bRet)
  85. break;
  86. printf("%d%d%d%d\n",
  87. permutation[3].curr,
  88. permutation[2].curr,
  89. permutation[1].curr,
  90. permutation[0].curr);
  91. }
  92. }
  93. int main()
  94. {
  95. old_flow();
  96. //new_flow();
  97. return 0;
  98. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-21 10:13:14 | 显示全部楼层
如果就事论事,上面的过程对应的只是一个数学计数。原程序本想得到一个7个数的所有全排列,但这本质就是枚举0至999999的数的简单问题而已(我曾碰到类似问题,冥思苦想后发现只是映射为一类简单的数学算式)。即源程序可改为: int main(int argc, char *argv[]) { double t0=clock(); int a,b,c,d,e,f,g,i; for (i=0;i<=9999999;i++) { int num=i; //a=i\10^7; 取出每位数 printf(" %d ",num); } printf("\nElapsed time :%f s",(clock()-t0)/CLOCKS_PER_SEC); return 0; }
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-21 10:43:02 | 显示全部楼层
的确,就1楼的代码而言,其实质是遍历所有9位数,这个问题很简单,改成一个一重循环很容易,将1个9位数拆成9个数字就可以了。 2#实现的是针对一个更加通用的问题,实现混合进制数的遍历,这个4位混合进制的每一位数字的范围都是不一样的,用你的方法恐怕就没那么容易了。 在实际应用上,这类题目一般用于搜索,其搜索空间往往非常大,如果对每一个可能的值(比如几个数字的组合)都进行验证,时间代价是不可接受的。 而如果使用2楼的方法,改写nextPerm函数,在执行nextPerm时,一些不符合要求的值可直接跳过,这样就可以在合理的时间内完成搜索。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-21 10:45:33 | 显示全部楼层
把内存里一个整数按十进制取出每个数字,代价很大, 因为需要用到除法指令, 或几次乘法及减法指令。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-21 10:57:13 | 显示全部楼层
而如果使用2楼的方法,改写nextPerm函数,在执行nextPerm时,一些不符合要求的值可直接跳过,这样就可以在合理的时间内完成搜索。
例如如下题目: 现有1角,2角,5角,1元,2元,5元的纸币,数量足够多。 问,现需要给顾客10元钱,有多少种给法。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2012-9-21 13:06:51 | 显示全部楼层
本质上就是对 循环所需的全排列总个数 做一个线性的编号。 将多维循环变成一维的循环
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2012-9-21 15:17:00 | 显示全部楼层
8# liangbch 递归写法
  1. count = 0
  2. coin=[50,20,10,5,2,1]
  3. total=[100,0,0,0,0,0]
  4. def func(i):
  5. global count
  6. for x in range(total[i-1]/coin[i-1]+1):
  7. total[i]=total[i-1]-x*coin[i-1]
  8. if i == 5:
  9. count += 1
  10. else:
  11. func(i+1)
  12. func(1)
  13. print count
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-11-24 08:06 , Processed in 0.026371 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表