Chinaunix首页 | 论坛 | 博客
  • 博客访问: 229345
  • 博文数量: 39
  • 博客积分: 1130
  • 博客等级: 少尉
  • 技术积分: 453
  • 用 户 组: 普通用户
  • 注册时间: 2010-02-26 15:54
文章分类

全部博文(39)

文章存档

2012年(1)

2011年(31)

2010年(7)

分类: C/C++

2011-06-25 07:40:04

 
6.准备写qiongju()函数

     因为要解决的问题对时间的要求以及计算对象是21位数,所以不难想象这个函数可能比较复杂。不
可能一蹴而就,所以在编写前首先要考虑测试。
     在测试用的main()内添加函数调用qiongju();作为测试驱动。
  1. #ifdef CESHI //测试
  2.    int main( void )
  3.    {
  4.       qiongju();
  5.       system("PAUSE");
  6.       return 0;
  7.    }

  8. #endif //CESHI
     把 0_问题.h 中的
  1. #define QIUJIE
中的 QIUJIE 改成CESHI ,编译运行通过 。这表明文件包含及qiongju()函数原型等方面均没什么问题,也就是说完成了对qiongju()函数测试的环境搭建。
     下面转到 2_穷举.c 源文件中,填充那个空着的qiongju()函数定义。
 
7.对穷举方案的思考
  
    最容易想到的穷举方案是
    方案1.
    for(W_ws=最小W位数;W_ws<=最大W位数;W_ws++)
      {
         //验算
      }
    这种方案首先需要解决W位数的表示问题,即设计存储这种数据的数据类型的问题。
    此外还需要考虑这种数据如何赋值、加1及比较大小等问题。
    最主要的,还需要解决解的搜索空间过大的问题。
    一下子同时解决这几个问题总是让人有些生畏(但也并非不能解决),因此暂不考虑。
第二种方案用循环嵌套的方式进行穷举
   方案2.
   for(第1位数=1;第1位数     for(第2位数=0;第2位数        ……
       for(第W位数=0;第W位数       {
            //验算
       }
   这种方案在一开始不必考虑W位数的表示问题,但解的搜索空间依然很大。
   解空间巨大的原因是穷举给出的是各位数的排列,这是一个相当巨大的数目 ((JINZHI-1)
*JINZHI^(W-1),对21位十进制数来说,这个值是9*10^20)。然而各种排列中有相当一部分,其各位数N次方的和是相同的(以3位数为例,110和101各位数的3次方之和都为2),不应该重复计算这些数各位数N次方的和。
   为了避免重复计算这些值,应该只验算各位数字的各种组合而不是对各位数字的排列进行验算。
   为此,可将穷举的循环结构改良为
   方案2-1.
   for(第1位数=JINZHI-1;第1位数>0;第1位数--)
      for(第2位数=第1位数;第2位数>=0;第2位数--)
         ……
        for(第W位数=第W-1位数;第W位数>=0;第W位数--)
        {
           //验算
        }         
    这个结构由于保证了各位数字均不大于前面的各位数字,所以得到的是W位数的各种组合。解的
搜索空间大大降低(对21位十进制数,只有14307149(30!/9!/21!-1)种可能)。
    此时验算的方法要稍微复杂一些。例如对于3位数情况,循环嵌套给出的数字组合可能为531,求出5、3、1这3个数字的3次方之和为153,由于153中各个数字出现个数与最初的数字组合531中各个数字出现的个数完全一致,因此即可判定153满足花朵数条件。 
   
这种方案比较形象直观,容易理解。 
    既然第二种方案在本质上无非是给出各位数字的各种组合,那么也许不如索性更直接一些。第3种方案虽然略有些抽象但却更加直接。
    方案3.
      for( 数字(JINZHI-1)的个数=0 ; 数字(JINZHI-1)的个数<=总位数 ; 数字(JINZHI-1)的
个数++)
         for( 数字(JINZHI-2)的个数=0 ;数字(JINZHI-2)的个数<=总位数-数字(JINZHI-1)的
个数 ; 数字(JINZHI-2)的个数++ )
            ……
           for( 数字1的个数=0 ; 数字1的个数<=总位数-前面各数字个数之和 ; 数字1的个数
++)
           {
                //数字0的个数= 总位数-前面各数字个数之和
                //<=>验算<=>记录结果
           }
    当JINZHI势。因此后面采用这种方案。 
      
8.测试方案可行性

  对3进制5位数情况测试方案是否可行。因为是3进制,所以是两层循环嵌套,穷举的数字为2,1,0
;因为是5位数,所以每种数字的个数可能为0~5个。
   
  1. /*2_穷举.c*/
  2. #include "2_穷举.h"
  3. extern void qiongju( void )
  4. {
  5.    //<=>验算<=>记录结果
  6.    #ifdef CESHI 
  7.    int n2; //n2:2的个数
  8.    for( n2 = 0 ; n2 <= 5 ;n2++ ){ //5位数
  9.       int n1;
  10.       printf("%d个%d ",n2 ,3 - 1 ) ; //3进制
  11.       for( n1 = 0 ; n1 <= 5-n2 ;n1 ++ ){ //n1:1的个数
  12.          int n0;
  13.          printf("%d个%d ",n1 ,3 - 2 );
  14.          {
  15.          n0 = 5 - n2 - n1 ; //n0:0的个数
  16.          printf("%d个%d",n0 ,3 - 3 );
  17.          putchar('\n');
  18.          }
  19.       }
  20.    }
  21.    #endif //CESHI
  22. }
  运行结果符合期待,表明方案可行。 
        
9.把嵌套改成递归         

   循环嵌套只能描述固定层数的嵌套结构,所以前面的方案只能写出某特定进制的情况(3进制2
层循环……10进制9层循环)。如果希望代码对不同进制的问题都成立,需要把循环的嵌套改成函数的递归,即用函数描述每一层循环及最内层的循环体部分的运算。递归调用很容易利用变量控制嵌套的层数。
  即使是只考虑十进制的情况,这样的修改也是必要的。因为循环嵌套的结构复杂、繁琐,不利于
修改(后面应该还有许多内容需要添加)和维护。况且,写9层循环多少有些让人不寒而栗,至少对我来说是这样,不痛下决心是没勇气写9层循环嵌套的。
  每一层循环(包括最内层的循环体)都需要两个参数确定:枚举的是哪个数字,这个数字的个数
最多有几个(上限)。以第一层循环为例
   for( n2 = 0 ; n2 <= 5 ;n2++ ){  //5位数
      int n1; 
      printf("%d个%d ",n2 ,3 - 1 ) ; //3进制 
       ……
   }
  需要5、3-1 这两个参数。第二层循环和最内层的循环体同样需要这两个参数。 
  对于函数的递归调用来说,获得这些参数的的途径可能有三种:函数调用的实参,外部变量,局
部static变量。最常用的是第一种,外部变量的办法通常是写不出手的,局部static变量的办法写起来要稍微困难一点。
  "2_穷举.c"的第二版为
  1. /*2_穷举.c*/
  2. #include "2_穷举.h"
  3. #include "0_问题.h" //用到了WEISHU , JINZHI
  4. static void xunhuan(const int ,const int ) ;

  5. //改循环嵌套为函数递归   
  6. extern void qiongju( void )
  7. {
  8.   xunhuan( WEISHU , JINZHI-1 ) ;
  9. }
  10.   
  11. static void xunhuan( const int gs_sx /*个数上限*/,
  12.                      const int sz /*关于哪个数字*/)
  13. {
  14.    if( sz > 0 ){
  15.       int i;
  16.       for( i = 0 ; i <= gs_sx ; i++ ){
  17.          #ifdef CESHI
  18.           printf("%d个%d " , i , sz );
  19.          #endif //CESHI  
  20.          xunhuan( gs_sx - i , sz - 1 );
  21.       }
  22.    }
  23.    else{
  24.       #ifdef CESHI
  25.        printf("%d个%d " , gs_sx , sz );
  26.        putchar('\n');
  27.       #endif //CESHI
  28.       //<=>验算<=>记录结果
  29.    }
  30. }
  测试:对10进制3位数情况测试,输出正确。
  到此,穷举部分基本完成。
 
10.去除递归调用的参数
 
  考虑到函数传递参数比较慢,所以改用另一种方式实现递归——使用static 局部变量传递参量。修改之后的代码不再需要xunhuan()这个函数
  1. /*2_穷举.c*/
  2. #include "2_穷举.h"
  3. #include "0_问题.h" //用到了QIUJIE、WEISHU

  4. extern void qiongju( void )
  5. {
  6.   static int sz = JINZHI , gs_sx ; /*关于哪个数字 ,该数字个数的上限*/
  7.   switch ( sz ) {
  8.      case JINZHI :
  9.          gs_sx = WEISHU ;
  10.          sz -- ;
  11.          qiongju();
  12.          sz = JINZHI ;
  13.          return ;
  14.      default :
  15.          {
  16.             int i ;
  17.             for( i = 0 ; i <= gs_sx ; i++ ){
  18.                #ifdef CESHI
  19.                  printf("%d个%d " , i , sz );
  20.                #endif //CESHI
  21.                sz -- ;
  22.                gs_sx -= i ;
  23.                qiongju ();
  24.                gs_sx += i ;
  25.                sz ++ ;
  26.             }
  27.             return ;
  28.          }
  29.      case 0 :
  30.          #ifdef CESHI
  31.           printf("%d个%d " , gs_sx , sz );
  32.           putchar('\n');
  33.          #endif //CESHI
  34.          //<=>验算<=>记录结果
  35.          return ;
  36.   }   
  37. }
阅读(1671) | 评论(0) | 转发(0) |
0

上一篇:练习

下一篇:编程的首要原则(s)是什么?

给主人留下些什么吧!~~