将现实问题转换为编程问题

简介: 考虑到一共五个人,直接模拟推理有些太难,计算机最擅长的遍历此时就会派上用场,将每个人从第1到第5来一遍,则一共会产生5^5种可能性,这个只需要一个5层循环即可搞定。但是这样会导致一些不期望出现的结果出现,因为并没有查重,所以会出现两个人抢名次的情况,也就是两个人或者更多的人名次相同的情况,例如两个第二,三个第三这样的,所以即使满足了条件,也要查看一下五个人的名次是否重复,这个交给一个函数来执行,只要五个人名次并列,那就返回0,否则返回1即可。有了这个思路,就能完成以下代码。

将现实问题转换为编程问题需要转换思维,不过孰能生巧,见多了就自然懂如何做了,所以动起手来是决没错的。


1.猜名次问题


53374cecc8ed47d6be6e8bb3e9e2c27d.png


每个选手都说了两句话,而只有一句话是对的


如果把 两句话都看成两个判断句, 如果为真则结果为1 如果为假则结果为0


所有两个判断式相加 等于1则表示只有一个判断句是真另外一个是假比如A选手说对一般表示:(b= =2)+(a= =3)==1

把所有的可能性穷举出来,a可能是第1第2第……5,b可能是第1第2第……5,c可能是……,嵌套就可以了。


思路:


考虑到一共五个人,直接模拟推理有些太难,计算机最擅长的遍历此时就会派上用场,将每个人从第1到第5来一遍,则一共会产生5^5种可能性,这个只需要一个5层循环即可搞定。但是这样会导致一些不期望出现的结果出现,因为并没有查重,所以会出现两个人抢名次的情况,也就是两个人或者更多的人名次相同的情况,例如两个第二,三个第三这样的,所以即使满足了条件,也要查看一下五个人的名次是否重复,这个交给一个函数来执行,只要五个人名次并列,那就返回0,否则返回1即可。有了这个思路,就能完成以下代码。


#include <stdio.h>
int checkData(int* p)
{
  int tmp[7] = { 0 }; //标记表,实际是哈希表的思路。一开始每个元素都是0
  //用来表示名次是否有人了,如果p[0](a选手)的名次是3,则tmp[3]=1,表示第3名已经有人了
  //如果p[1](b选手)的名次也是3,则进入if()语句tmp[3]==1,
  //直接返回0,这组成绩作废,再判断下组成绩。
  //直到没有重复的 才可以才能返回1
  int i;
  for (i = 0; i < 5; i++)
  {
    if (tmp[p[i]]==1) //如果这个位置的标记已经是1,则代表重复,直接返回0。
    {
      return 0;
    }
    tmp[p[i]] = 1; //如果不是,则给这个位置标记为1。
  }
  return 1; //全部标记完毕也没有出现重复的情况,代表OK。
}
int main()
{
  int p[5]; //0 1 2 3 4分别代表a b c d e
  for (p[0] = 1; p[0] <= 5; p[0]++)
  {
    for (p[1] = 1; p[1] <= 5; p[1]++)
    {
      for (p[2] = 1; p[2] <= 5; p[2]++)
      {
        for (p[3] = 1; p[3] <= 5; p[3]++)
        {
          for (p[4] = 1; p[4] <= 5; p[4]++) //五层循环遍历
          {
            //这里是五个人的描述,由于比较表达式只有0和1两个结果,如果要两个条件有且只有一个为真,则可以用比较表达式的值总和为1的方式直接判定。别忘了还要判定不能并列。
            if ((p[1] == 2) + (p[0] == 3) == 1 && //B第二,我第三
              (p[1] == 2) + (p[4] == 4) == 1 && //我第二,E第四
              (p[2] == 1) + (p[3] == 2) == 1 && //我第一,D第二
              (p[2] == 5) + (p[3] == 3) == 1 && //C最后,我第三
              (p[4] == 4) + (p[0] == 1) == 1 && //我第四,A第一
              checkData(p) //不能并列||,如果checkData返回0,则整体都为假,如果返回1则为真
              )
            {
              for (int i = 0; i < 5; i++)
              {
                printf("%d ", p[i]);
              }
              putchar('\n');
            }
          }
        }
      }
    }
  }
  return 0;
}


有的人可能不太懂为什么要查重复,因为5^5把全部的可能性列举出来,每个人都可能会相同,而题目给的也不是很严谨,所有会导致出现相同名次的情况比如把查重函数拿走就会出现这个现象:


da44ddfa49ab4f1bbc500d5e00fa636a.png


会出现抢名次的现象,所以我们需要进行查重。


查重后结果:


51fb54096f33450f97731d6190c666e1.png


改进一:


检查是否重复的过程,我们是用一个数组来做的,实际每个标签只有0和1两种可能,


没必要一定要用数组做,可以考虑用一个位来做(哈希中的位图)


代码如下:


int checkData(int *p)
{
  char tmp = 0;
  int i;
  for (i = 0; i < 5; i++)
  {
    tmp |= 1 << p[i]; 
        //tmp每次或上一位1,p[i]如果是1~5都有,则1<<1到1<<5都或上的结果将会是00111110,如果有并列,则一定会至少却其中一个1,结果就不会是00111110,所以可以判断tmp最终的结果是不是这个数字来判断有没有重复。
  }
  return tmp == 0x3E;
}


改进二:


循环代码又长又难看,可以考虑改成递归:


void diveRank(int * p, int n)
{
  if(n >= 5) //此时的n是用来控制循环层数的。
  {
    if ((p[1] == 2) + (p[0] == 3) == 1 && //B第二,我第三
      (p[1] == 2) + (p[4] == 4) == 1 && //我第二,E第四
      (p[2] == 1) + (p[3] == 2) == 1 && //我第一,D第二
      (p[2] == 5) + (p[3] == 3) == 1 && //C最后,我第三
      (p[4] == 4) + (p[0] == 1) == 1 && //我第四,A第一
      checkData(p)) //查重
    {
      for (int i = 0; i < 5; i++)
      {
        printf("%d ", p[i]);
      }
      putchar('\n');
    }
    return;
  }
  for(p[n] = 1; p[n] <= 5; p[n]++)
  {
    diveRank(p, n + 1); //通过递归模拟多层循环,每进一次递归相当于进了一层新的循环。
  }
}
int main()
{
  int p[5];
  diveRank(p, 0);
  return 0;
}


改进三:


以上的方法只是让代码简单了点,但还是需要5 ^ 5次比较,而如果本来就是做1 ~ 5的排列组合的话只需要5!次比较,能极大的减少遍历所需的次数(复杂度由O(n^n)降低为O(n!)),那是不是可以用一个递归完成对1~5的全排列呢?当然是可以的,所以我们可以进一步优化遍历的方式,将遍历用的递归程序改成这样:


#include <stdio.h>
void swapArgs(int * a, int * b) //交换函数
{
  int tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}
void diveRank(int * p, int n)
{
  if(n >= 5) //此时的n也是用来控制循环层数的。
  {
    if ((p[1] == 2) + (p[0] == 3) == 1 && //B第二,我第三
      (p[1] == 2) + (p[4] == 4) == 1 && //我第二,E第四
      (p[2] == 1) + (p[3] == 2) == 1 && //我第一,D第二
      (p[2] == 5) + (p[3] == 3) == 1 && //C最后,我第三
      (p[4] == 4) + (p[0] == 1) == 1)   //我第四,A第一
            //由于此时是执行的全排列,所以查重也省了。
    {
      for (int i = 0; i < 5; i++)
      {
        printf("%d ", p[i]);
      }
      putchar('\n');
    }
    return;
  }
    int i;
  for(i = n; i < 5; i++) //这个递归方式就完成了对1~5的全排列,方法是从后向前不停的执行交换。可以参考改进二和原代码,将这个递归程序写回成循环后,可以更好的理解。
  {
    swapArgs(p + i, p + n);
    diveRank(p, n + 1);
    swapArgs(p + i, p + n);
  }
}
int main()
{
  int p[5] = { 1, 2, 3, 4, 5 }; //当然由于是全排列,所以初值必须给好。
  diveRank(p, 0);
  return 0;
}


至此,遍历速度上达到了一个新的高度,这种遍历大大减少了遍历的次数,极大的提升了效率。


2.猜凶手问题


568c1b9e536d4f1a970d8c97433c9913.png


这个还是将简单的现实问题转换为编程问题,跟上面的题目一样,问题就是如何把这几个人说的话转换为编程


四个嫌疑犯,3个人说了真话,1个人说了假话,那把这四个人的话看成4个表达式(判断式),如果为真则结果为1,如果为

假结果为0,所以只要让4个表达式相加等于3就可以了。


思路:


这个题就是按照正常方式,假设凶手是a,b,c,d其中的一个,看是不是满足题目条件,如果满足就找出了凶手。


int main()
{
  char killer = 0;
  //分别假设凶手是a,b,c,d,看谁是凶手是符合三个人说真话,一个人
  //说假话
  for (killer = 'a'; killer <= 'd'; killer++)
  {
    if (('a' != killer) + (killer == 'c') + (killer == 'd') + (killer != 'd')==3)
    {
      printf("凶手是:%c", killer);
    }
  }
  return 0;
}

67531eddf3b74d6c99a870e8e5e56028.png


总结:


这种问题可能刚做不适应,不太好想,不过接触多了就知道怎么回事了,将所说的看成表达式,根据真为1,假为0,来解释


多刷题,就能接触更多的有趣的题类型。

相关文章
|
存储 jenkins 持续交付
Jenkins X 官方介绍
Jenkins X 简介 Jenkins X 为云上的开发人员自动化并加速了持续集成和持续交付,因此他们可以专注于构建出色的软件。
Qt 将字符串转成16进制显示
最近项目用到了需要将字符串转换成16进制显示。这玩意折腾了一上午。
997 0
|
8月前
|
传感器 JavaScript 调度
HarmonyOS Next 并发 taskpool 和 worker
HarmonyOS Next 提供了 TaskPool 和 Worker 两种并发能力,基于 Actor 并发模型实现。TaskPool 是 Worker 的封装,支持参数直接传递、返回数据接收、任务优先级设置及取消功能,适合大多数场景;Worker 则适用于超长任务或需手动管理线程生命周期的场景。两者通过消息通信完成跨线程数据交换,支持普通对象拷贝、ArrayBuffer 拷贝/转移、SharedArrayBuffer 共享及 Sendable 引用传递等方式。实际开发中,TaskPool 更简化任务调度,而 Worker 更灵活,可根据任务类型(耗时、长时、常驻)选择合适方案。
361 12
HarmonyOS Next 并发 taskpool 和 worker
|
9月前
一文彻底搞定电容元件
电容元件是电路中储存电荷的基本组件,通常用“C”表示,单位为法拉(F),常见单位有微法(μF)、纳法(nF)和皮法(pF)。电容具有“通交流,隔直流”的特性,主要用于储能、滤波、耦合与隔直等。根据安装方式可分为固定电容、可变电容和微调电容。其主要参数包括电容值、额定电压和损耗因数。电容广泛应用于电源滤波、信号处理及脉冲电路等领域。
832 0
|
域名解析 缓存 网络协议
关于错误ERR_NAME_NOT_RESOLVED
如果以上方法都未能解决问题,你可能需要联系你的网络管理员或互联网服务提供商以获取更多帮助,或者考虑尝试在不同的网络环境中访问网站。
6145 0
|
存储 机器学习/深度学习 缓存
阿里云PAIx达摩院GraphScope开源基于PyTorch的GPU加速分布式GNN框架
阿里云机器学习平台 PAI 团队和达摩院 GraphScope 团队联合推出了面向 PyTorch 的 GPU 加速分布式 GNN 框架 GraphLearn-for-PyTorch(GLT) 。
阿里云PAIx达摩院GraphScope开源基于PyTorch的GPU加速分布式GNN框架
|
存储 弹性计算 编解码
阿里云五代、六代、七代、八代云服务器实例规格性能提升介绍
阿里云服务器有多种实例规格可选,这些实例规格主要以五代、六代、七代和最新第八代倚天云服务器为主,当下主售的是以七代和八代云服务器为主,那么我们在购买阿里云服务器时所看到的各种云服务器实例具体属于那一代云服务器呢?有的用户可能并不清楚哪些实例规格分别属于哪一代实例,下面小编为大家介绍下阿里云五代、六代、七代、八代云服务器实例规格分别有哪些以及每一代云服务器在性能方面具体有哪些提升,以供大家参考和了解。
阿里云五代、六代、七代、八代云服务器实例规格性能提升介绍
|
监控 Ubuntu 安全
在 Ubuntu 服务器上如何启用自动登录?
在 Ubuntu 服务器上如何启用自动登录?
506 0
在 Ubuntu 服务器上如何启用自动登录?