zoukankan      html  css  js  c++  java
  • 《啊哈算法》——枚举

      枚举法,作为编程世界里一个非常基本的方法或者说技巧,它也可以叫穷举法、暴力法、遍历法,深入了解一些算法后,你会发现它在算法世界当中的用途非常的广泛。

      概括地说这种方法非常的简单,我们抽象点来说,对于一个问题的解x,这个解满足限制条件f(x),枚举法给出解决问题的方案是一一列举x所有可能的情况,然后判断是否满足限制条件f(x),如果满足,则x是问题的解;反之,不是。

      枚举法虽然简单,但是在解决实际问题中往往伴随着比较巧妙的优化与筛选,而且在枚举算啊中体现出的“找到x所有可能的情况”,在其他的一些算法(比如计算几何)也有着用武之地。因此所谓暴力法看似暴力无脑,其实里面还是有很多需要深思熟虑的地方。

      我们来看一个例子:对于()()()+()()() = ()()(),9个空填写1~9这9个数字各一次,请问会有多少种符合的情况?

      显然我们枚举每一个()可能的取值,即[1,9],需要设置9层循环,然后判断这9个数是否相等,随后验证等式成立,但是基于这个思路编程我们发现在判断9个数是否相同的时候太过麻烦了,有没有较为简便的编码方式呢?

      其实类似我们在第一章介绍的桶排序的方式,我们用a[i]表示第i个()的数字,然后我们用book[a[i]]来标记数字a[i]是否出现过,如果∑book[a[i]] = 9  ,则表明9个数字分别出现了一次,是满足限制条件的。

      简单的参考代码如下。

    #include<cstdio>
    int main()
    {
          int a[10] , i , total = 0 , book[10] , sum;
    
          for(a[1] = 1;a[1]<=9;a[1]++)
             for(a[2]=1;a[2]<=9;a[2]++)
               for(a[3]=1;a[3]<=9;a[3]++)
                 for(a[4]=1;a[4]<=9;a[4]++)
                   for(a[5]=1;a[5]<=9;a[5]++)
                     for(a[6]=1;a[6]<=9;a[6]++)
                       for(a[7]=1;a[7]<=9;a[7]++)
                         for(a[8]=1;a[8]<=9;a[8]++)
                           for(a[9]=1;a[9]<=1;a[9]++)
          {
                for(i = 1;i <= 9;i++)
                      book[i] = 0;
                for(i = 1;i <= 9;i++)
                       book[a[i]] = 1;
                sum = 0;
                for(i  =1 ;i <= 9;i++)
                      sum+= book[a[i]];
    
                if(sum==9 &&100*a[1] + 10*a[2] + a[3]
                          + 100*a[4] + 10*a[5] + a[6]
                        ==  100*a[7] + 10*a[8] + a[9])
                {
                      total++;
                }
          }
    
          printf("total = %d",total/2); //由加法交换律我们不难理解这里应该除2的,对于结果进行符合题意的筛选计算往往是枚举的关键
    }

      

      我们来看另外一个简单的用到枚举的例子——生成数的全排列。

      假设我们想要得到1234的所有排列情况,我们便可以设置四层嵌套循环,当四层循环的参数各不相同的时候,则表明当前可以得到一个排列情况,输出即可。

      简单的参考代码如下。

    for(a = 1;a<=4;a++)
          for(b=1;b<=4;b++)
               for(c=1;c<=4;c++)
                  for(d=1;d<=4;d++)
                       if(a!=b &&a!=c&&a!=d&&b!=c&&b!=d&&c!=d)
                            printf("%d%d%d%d
    ",a,b,c,d);

      但是我们会看到这种利用枚举的方法生成全排列的方法,在生成12345……n(n取很大的时候),这种方法就显得有些捉襟见肘了(尤其是判断n个数不相等的时候,而且在循环中出现了大量的数字重复情况浪费了时间),有没有更好的方法呢?在这里先埋下伏笔,在介绍搜索的章节中我们会进行详细的讨论。

      我们再给出一个用枚举算法解决的实例。

      炸弹人游戏:用二维矩阵储存一个图,:"G"表示有敌人,"#"表示不可破坏的砖头,"."表示空地和可破坏的砖头。炸弹的破坏力极大,可以杀死以安置点为原点的直角坐标系上的任意一个敌人(前提是图中没有被不可破坏的砖头阻隔),那么炸弹应该安置在哪个店,能够杀死最多的敌人呢?

      很显然,基于我们储存的图,我们从第一个点开始,想上、下、左、右四个方向走,记录能够杀死的敌人,然后枚举图中的所有点,维护一个最大值即可。

      简单的程式如下。

    #include<cstdio>
    using namespace std;
    int main()
    {
          char a[20][21];
          int i , j , sum , map = 0 , p , q , x , y , n, m;
          scanf("%d%d",&n,&m);
    
            for(i = 0;i<=n-1;i++)
                  scanf("%s",a[i]);
    
            for(i=0;i<=n-1;i++)
            {
                  for(j = 0;j<=m-1;j++)
                  {
                       if(a[i][j] == '.')
                       {
                             sum = 0;
    
                             x = i;y = j;
                             while(a[x][y] != '#')
                             {
                                     if(a[x][y] == 'G')
                                          sum++;
                                     x--;
                             }
    
    
                       x = i;y = j;
                       while(a[x][y]!='#')
                       {
                             if(a[x][y] == 'G')
                                  sum++;
                             x++;
                       }
    
                       x = i;y = j;
                       while(a[x][y] != '#')
                       {
                             if(a[x][y] == 'G')
                                  sum++;
                             y--;
                       }
    
                       x=i;y=j;
                       while(a[x][y] != '#')
                       {
                             if(a[x][y] == 'G')
                                  sum++;
    
                             y++;
                       }
    
                          if(sum > map)
                           {
                              map = sum;p = i  ,q = j;
                           }
                       }
                  }
             }
    
        printf("将炸弹放置在(%d,%d),最多可消灭%d个敌人
    ",p,q,map);
        return 0;
    }

      我们再来看一个用到枚举算法典型——火柴等式游戏。

      一直每个数字用火柴拼出的数目,游戏内容是给出m根(m<=24)火柴,要求拼出() + () = ()的等式,问:这样的等式有多少种?(A!=B时,A + B = C与B + A = C为两种拼法)。其中,”+“与”=“分别用两个火柴

      这道题其实体现了枚举的解决实际问题的一个特点——道理简单但是处处藏着优化。

      我们从题设条件出发,对于m<=24我们能够提取出怎样的有效信息呢?显然它决定了我们枚举的范围,我们至多有20根火柴来拼数字,而用火柴拼1是最节约的,20个火柴平摊给3个数,每个数字也就分7根火柴,我们凑一个8,能够拼出来的数字为1111,事实上考虑到加法1111这个数字还是太大。因此我们得知,每个数字不会超过1111,那么我们枚举每一位数字,然后计算其使用的火柴,判断其是否满足我们所有的火柴根数,便可得到等式了。

      这里还有很关键的一步优化,这里有必要枚举三个数字么?事实上,我们枚举两个数字,通过等式便可以得到第三个数字,并且不用再判断相等(枚举三个数字显然要判断等式是否成立),一举两得,节省了很多时间。

      由此我们不难发现,在用暴力枚举解决问题时,思维中一定要时刻绷着优化的弦。

      简单的参考代码如下。

    #include<cstdio>
    int fun(int x)
    {
         int num = 0;
         int f[10] ={6,2,5,5,4,5,6,3,7,6};
         while(x/10 != 0)
         {
              num += f[x%10];
              x = x/10;
         }
         num += f[x];
         return num;
    }
    
    int main()
    {
         int a  , b , c , m , i , sum = 0;
         scanf("%d",&m);
    
            for(a = 0;a <= 1111;a++)
            {
                  for(b = 0;b<= 1111;b++)
                  {
                         c = a + b;
                             if(fun(a) + fun(b) + fun(c) == m-4)
                             {
                                  printf("%d+%d=%d
    ",a,b,c);
                                  sum++;
                             }
                  }
            }
            printf("一共可以拼出%d个不同等式",sum);
    
            return 0;
    }
  • 相关阅读:
    github的使用
    QPalette的用法
    QTimer的用法
    QStatusBar的用法
    QWhatsThis的用法
    QString::​arg的用法
    qt中ui的 使用介绍
    安全协议IPSEC
    安全协议ssl
    对称加密和非对称加密
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5449641.html
Copyright © 2011-2022 走看看