zoukankan      html  css  js  c++  java
  • 8算法策略之枚举法

    蛮力法

    蛮力法是基于计算机运算速度快这一特性,在解决问题时采取的一种“懒惰”的策略。这种策略不经过(或者说是经过很少的)思考,把问题的所有情况或所有过程交给计算机去一一尝试,从中找出问题的解。蛮力策略的应用很广,具体表现形式各异,数据结构课程中学习的:选择排序、冒泡排序、插入排序、顺序查找、朴素的字符串匹配等,都是蛮力策略具体应用。比较常用还有枚举法、盲目搜索算法等。

    枚举法

    枚举( enumerate)法(穷举法)是蛮力策略的一种表现形式,也是一种使用非常普遍的思维方法。它是根据问题中的条件将可能的情况一一列举出来,逐一尝试从中找出满足问题条件的解。但有时一一列举出的情况数目很大,如果超过了我们所能忍受的范围,则需要进一步考虑,排除一些明显不合理的情况,尽可能减少问题可能解的列举数目。

    用枚举法解决问题,通常可以从两个方面进行算法设计:

        1)找出枚举范围:分析问题所涉及的各种情况。

         2)找出约束条件:分析问题的解需要满足的条件,并用逻辑表达式表示。

    【例1】百钱百鸡问题。中国古代数学家张丘建在他的《算经》中提出了著名的“百钱百鸡问题”:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,翁、母、雏各几何?

    算法设计1:

    通过对问题的理解,读者可能会想到列出两个三元一次方程,去解这个不定解方程,就能找出问题的解。这确实是一种办法,但这里我们要用“懒惰”的枚举策略进行算法设计: 

    设x,y,z分别为公鸡、母鸡、小鸡的数量。

       尝试范围:由题意给定共100钱要买百鸡,若全买公鸡最多买100/5=20只,显然x的取值范围1~20之间;同理,y的取值范围在1~33之间,z的取值范围在1~100之间。

       约束条件: x+y+z=100 且 5*x+3*y+z/3=100

    算法1如下:

    main( )
    { int x,y,z;
     for(x=1;x<=20;x=x+1)
        for(y=1;y<=34;y=y+1)
          for(z=1;z<=100;z=z+1)
              if(100=x+y+z and 100=5*x+3*y+z/3)
                 {print("the cock number is",x);
            print("the hen number is", y);
              print("the chick number is“,z);}
    }
    

    算法分析:以上算法需要枚举尝试20*34*100=68000次。算法的效率显然太低

    算法设计2:

    在公鸡(x)、母鸡(y)的数量确定后,小鸡 的数量 z就固定为100-x-y,无需再进行枚举了

     此时约束条件只有一个:  5*x+3*y+z/3=100
      算法2如下:

    main(   )
    {  int x,y,z;
        for(x=1;x<=20;x=x+1)               
            for(y=1;y<=33;y=y+1)            
            { z=100-x-y;             
             if(z mod 3=0 and 5*x+3*y+z/3=100)                                  
                 {print(the cock number is",x);
            print(the hen number is", y);
    print(the chick number is "z);}
            }
    }
    

    算法分析:以上算法只需要枚举尝试20*33=660次。实现时约束条件又限定Z能被3整除时,才会判断“5*x+3*y+z/3=100”。这样省去了z不整除3时的算术计算和条件判断,进一步提高了算法的效率。

    【例2】解数字迷:        A  B  C  A  B

                                     ×               A   

                                     D  D  D  D  D  D

    算法设计1:按乘法枚举

    1)枚举范围为:

     A:3——9(A=1,2时积不会得到六位数),B:0——9,

     C:0——9 六位数表示为A*10000+B*1000+C*100+A*10+B,   共尝试700次。

    2)约束条件为:

      每次尝试,先求六位数与A的积,再测试积的各位是否相同,若相同则找到了问题的解。测试积的各位是否相同比较简单的方法是,从低位开始,每次都取数据的个位,然后整除10,使高位的数字不断变成个位,并逐一比较。

    算法1如下:

    main( )
    { long  A,B,C,D,E,E1,F,G1,G2,i;
      for(A=3;  A<=9;  A++)
      for(B=0;  B<=9;  B++)
      for(C=0;  C<=9;  C++)
     { F=A*10000+B*1000+C*100+A*10+B;
        E=F*A; E1=E;  G1=E1 mod 10;
        for(i=1;  i<=5;  i++)
          { G2=G1; E1=E1/10;     G1= E1 mod 10;
             if(G1<>G2 )   break;     }
           if(i=6)    print( F,”*”,A,”=”,E);
          }
      }
    

    算法说明1:算法中对枚举出的每一个五位数与A相乘,结果存储在变量E中。然后,测试得到的六位数E是否各个位的数字都相同。鉴于要输出结果,所以不要改变计算结果,而另设变量E1,用于测试运算。

    算法分析1:以上算法的尝试范围是A:3——9,B:0——9, C:0——9 。共尝试800次,每次,不是一个好的算法。

    算法设计2:将算式变形为除法:DDDDDD/A=ABCAB。此时只需枚举A:3——9 D:1——9,共尝试7*9=63次。每次尝试,测试商的万位、十位与除数是否相同,千位与个位是否相同,都相同时为解。

    算法2如下:

    main()
    {long A,B,C,D,E,F;
     for(A=3;A<=9;A++)
        for(D=1;D<=9;D++) 
         { E = D*100000+D*10000+D*1000+D*100+D*10+D;
          if(E mod A=0) F=EA;
           if(F10000=A and (F mod 100)10=A)
           if(F1000==F mod 10)
           print( F,”*”,A,”=”,E);
          }
     }
    

      

    【例3】狱吏问题

        某国王对囚犯进行大赦,让一狱吏n次通过一排锁着的n间牢房,每通过一次,按所定规则转动n间牢房中的某些门锁, 每转动一次, 原来锁着的被打开, 原来打开的被锁上;通过n次后,门锁开着的,牢房中的犯人放出,否则犯人不得获释。

        转动门锁的规则是这样的,第一次通过牢房,要转动每一把门锁,即把全部锁打开;第二次通过牢房时,从第二间开始转动,每隔一间转动一次;第k次通过牢房,从第k间开始转动,每隔k-1 间转动一次;问通过n次后,那些牢房的锁仍然是打开的?

    算法设计1:

    1)用n个空间的一维数组a[n],每个元素记录一个锁的状态,1为被锁上,0为被打开。

    2)用数学运算方便模拟开关锁的技巧,对i号锁的一次开关锁可以转化为算术运算:a[i]=1-a[i]。

    3)第一次转动的是1,2,3,……,n号牢房;

       第二次转动的是2,4,6,……号牢房;

       第三次转动的是3,6,9,……号牢房;

       ……第i次转动的是i,2i,3i,4i,……号牢房,是起点为i,公差为i的等差数列。

      4)不做其它的优化,用蛮力法通过循环模拟狱吏的开关锁过程,最后当第i号牢房对应的数组元素a[i]为0时,该牢房的囚犯得到大赦。

    算法1如下:

    main1( )
    {int *a,i,j,n;
     input(n);
     a=calloc(n+1,sizeof(int));
     for (i=1; i<=n;i++)
        a[i]=1;
     for (i=1; i<=n;i++)
      for (j=i; j<=n;j=j+i)
         a[i]=1-a[i];
     for (i=1; i<=n;i++)
     if (a[i]=0)     print(i,”is  free.”);
    }
    

    算法分析:以一次开关锁计算,算法的时间复杂度为n(1+1/2+1/3+……+1/n)=O(nlogn)。

    问题分析:转动门锁的规则可以有另一种理解,第一次转动的是编号为1的倍数的牢房;第二次转动的是编号为2的倍数的牢房;第三次转动的是编号为3的倍数的牢房;……则狱吏问题是一个关于因子个数的问题。令d(n)为自然数n的因子个数,这里不计重复的因子,如4的因子为1,2,4共三个因子,而非1,2,2,4。则d(n)有的为奇数,有的为偶数,见下表:

     数学模型1:上表中的d(n)有的为奇数,有的为偶数,由于牢房的门开始是关着的,这样编号为i的牢房,所含1——i之间的不重复因子个数为奇数时,牢房最后是打开的,反之,牢房最后是关闭的。

    算法设计2: 

    1)算法应该是求出每个牢房编号的不重复的因子个数,当它为奇数时,这里边的囚犯得到大赦。

    2)一个数的因子是没有规律的,只能从1——n枚举尝试。算法2如下:

    main2( )
    {int s,i,j,n;
     input(n);
     for (i=1; i<=n;i++)
       { s=1;
        for (j=2; j<=i;j=j++)
              if (i mod  j=0) s=s+1;
        if (s mod 2 =1) print(i,”is  free.”); }
     }
    

    算法分析:

       狱吏开关锁的主要操作是a[i]=1- a[i];共执行了n*(1+1/2+1/3+……+1/n)次,时间近似为复杂度为O(n log n)。使用了n个空间的一维数组。算法2没有使用辅助空间,但由于求一个编号的因子个数也很复杂,其主要操作是判断i mod  j是否为0,共执行了n*(1+2+3+……+n)次,时间复杂度为O(n2)。

    数学模型2:仔细观察表4-3,发现当且仅当n为完全平方数时,d(n)为奇数;这是因为n的因子是成对出现的,也即当n=a*b且a≠b时,必有两个因子a,b; 只有n为完全平方数,也即当n=a2时, 才会出现d(n)为奇数的情形。

    算法设计3:这时只需要找出小于n的平方数即可。

    main3( )
    {int s,i,j,n;
     input(n);
    for (i=1;i<=n;i++)
    if(i*i<=n) print(i*i,”is  free.”);
       else      break;
     }
    

      由此算法我们应当注意:在对运行效率要求较高的大规模的数据处理问题时,必须多动脑筋找出效率较高的数学模型及其对应的算法。

  • 相关阅读:
    用正则表达式简单加密(C#为例)
    新浪微博error:redirect_uri_mismatch的解决方法 [
    UITableView延伸:点击cell关闭键盘,加载不同cell,监听里面的textfeild内容改变
    iossharesdk微信登录出错
    关于IOS项目QQ空间授权提示安装最新版本的QQ的解决方法!
    如何解决 错误code signing is required for product type 'xxxxx' in SDK 'iOS 8.2'
    UITableView加载几种不同的cell
    iOS学习小结(一)
    开源中国+soucetree
    获取本机ip地址
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/10381487.html
Copyright © 2011-2022 走看看