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

简介: 考虑到一共五个人,直接模拟推理有些太难,计算机最擅长的遍历此时就会派上用场,将每个人从第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,来解释


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

相关文章
|
SQL 消息中间件 Kafka
流数据湖平台Apache Paimon(二)集成 Flink 引擎
流数据湖平台Apache Paimon(二)集成 Flink 引擎
1875 0
|
9月前
|
传感器 JavaScript 调度
HarmonyOS Next 并发 taskpool 和 worker
HarmonyOS Next 提供了 TaskPool 和 Worker 两种并发能力,基于 Actor 并发模型实现。TaskPool 是 Worker 的封装,支持参数直接传递、返回数据接收、任务优先级设置及取消功能,适合大多数场景;Worker 则适用于超长任务或需手动管理线程生命周期的场景。两者通过消息通信完成跨线程数据交换,支持普通对象拷贝、ArrayBuffer 拷贝/转移、SharedArrayBuffer 共享及 Sendable 引用传递等方式。实际开发中,TaskPool 更简化任务调度,而 Worker 更灵活,可根据任务类型(耗时、长时、常驻)选择合适方案。
409 12
HarmonyOS Next 并发 taskpool 和 worker
|
9月前
|
人工智能 弹性计算 Ubuntu
从零开始即刻拥有 DeepSeek-R1 满血版并使用 Dify 部署 AI 应用
本文介绍了如何使用阿里云提供的DeepSeek-R1大模型解决方案,通过Chatbox和Dify平台调用百炼API,实现稳定且高效的模型应用。首先,文章详细描述了如何通过Chatbox配置API并开始对话,适合普通用户快速上手。接着,深入探讨了使用Dify部署AI应用的过程,包括选购云服务器、安装Dify、配置对接DeepSeek-R1模型及创建工作流,展示了更复杂场景下的应用潜力。最后,对比了Chatbox与Dify的输出效果,证明Dify能提供更详尽、精准的回复。总结指出,阿里云的解决方案不仅操作简便,还为专业用户提供了强大的功能支持,极大提升了用户体验和应用效率。
3011 20
从零开始即刻拥有 DeepSeek-R1 满血版并使用 Dify 部署 AI 应用
|
10月前
一文彻底搞定电容元件
电容元件是电路中储存电荷的基本组件,通常用“C”表示,单位为法拉(F),常见单位有微法(μF)、纳法(nF)和皮法(pF)。电容具有“通交流,隔直流”的特性,主要用于储能、滤波、耦合与隔直等。根据安装方式可分为固定电容、可变电容和微调电容。其主要参数包括电容值、额定电压和损耗因数。电容广泛应用于电源滤波、信号处理及脉冲电路等领域。
880 0
|
10月前
|
SQL JSON 数据可视化
基于 DIFY 的自动化数据分析实战
本文介绍如何使用DIFY搭建数据分析自动化流程,实现从输入需求到查询数据库、LLM分析再到可视化输出的全流程。基于经典的employees数据集和DIFY云端环境,通过LLM-SQL解析、SQL执行、LLM数据分析及ECharts可视化等模块,高效完成数据分析任务。此方案适用于人力资源分析、薪酬管理等数据密集型业务,显著提升效率并降低成本。
14002 16
|
搜索推荐 区块链 开发者
【python程序打包教程】PyInstaller一键打包Python程序为独立可执行exe文件
【python程序打包教程】PyInstaller一键打包Python程序为独立可执行exe文件
|
存储 弹性计算 编解码
阿里云五代、六代、七代、八代云服务器实例规格性能提升介绍
阿里云服务器有多种实例规格可选,这些实例规格主要以五代、六代、七代和最新第八代倚天云服务器为主,当下主售的是以七代和八代云服务器为主,那么我们在购买阿里云服务器时所看到的各种云服务器实例具体属于那一代云服务器呢?有的用户可能并不清楚哪些实例规格分别属于哪一代实例,下面小编为大家介绍下阿里云五代、六代、七代、八代云服务器实例规格分别有哪些以及每一代云服务器在性能方面具体有哪些提升,以供大家参考和了解。
阿里云五代、六代、七代、八代云服务器实例规格性能提升介绍
|
JavaScript API UED
drag / drop
"拖放"(Drag and Drop)是一种常见的计算机操作方式,指将一个对象从一处移动到另一处,通常通过鼠标或触摸屏手势来实现。这种操作方式在许多应用程序中被广泛采用,例如在文件管理器中移动文件,在图像编辑器中调整图片大小和位置等。 拖放操作的步骤一般如下:
197 6
|
Java
JVM 调优常用参数(JDK1.8.0_281+CentOS7)参数1.8其他版本JDK也适用
JVM 调优常用参数(JDK1.8.0_281+CentOS7)参数1.8其他版本JDK也适用
422 0