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

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


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

相关文章
|
5月前
|
设计模式 算法 程序员
探索编程之美:从问题到解决方案的转化艺术
【2月更文挑战第29天】 在编程的世界里,每一行代码都是对问题理解的延伸,每一个函数都是解决方案思考的结晶。本文将通过个人的技术感悟,深入探讨如何将复杂的编程问题转化为优雅的解决方案。我们将一起走进编程的艺术殿堂,体验从混沌到秩序的创造过程。
|
2月前
|
机器学习/深度学习 存储 人工智能
矩阵乘法运算:在这看似枯燥的数字组合中,究竟蕴含着怎样令人称奇的奥秘?
【8月更文挑战第19天】矩阵乘法不仅是数学概念,还在工程、图像处理及AI等领域发挥核心作用。例如,通过矩阵乘法可精确实现图像变换;在神经网络中,它帮助模型学习和优化以识别图像和理解语言。两个矩阵A(m×n)与B(n×p)相乘得C(m×p),其中C[i,j]为A的第i行与B的第j列元素乘积之和。尽管面临维度匹配等挑战,矩阵乘法仍在持续推动技术创新。下次享受智能服务时,不妨想想背后的矩阵乘法吧。
59 3
|
3月前
|
存储 算法 索引
深度挖掘:Python并查集背后的秘密,让你的代码逻辑清晰如水晶!
【7月更文挑战第17天】并查集,一种高效处理集合合并与查询的数据结构,常用于图论、社交网络分析等。Python中的实现利用数组存储元素的父节点,通过路径压缩和按秩合并优化查找和合并操作。简单代码示例展示了查找和合并方法,以及应用在检测无向图环路。并查集以其优雅的解决方案在算法世界中闪耀,提升代码的清晰度和效率。
46 5
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
大模型是如何理解人类语言的?
大模型是如何理解人类语言的?
52 0
|
5月前
|
存储 安全 关系型数据库
技术人必修课:利用金字塔原理高效思考与表达
作者写这篇文章的目的就是希望能够帮助更多同学了解金字塔原理并合理应用,不只是写作,更是要着眼于思考和表达。本文将围绕认识金字塔结构、表达的逻辑、思考的逻辑、解决问题的框架、演示的逻辑这几个方面带领大家深入学习金字塔原理。
|
5月前
|
机器学习/深度学习 算法 人机交互
编码之禅:技术洞见与内在平衡
【2月更文挑战第15天】 在技术的世界中,我们常常追求更快、更高效、更智能。然而,在这无限追求的过程中,我们是否忽略了技术本身的精神层面?本文将探讨技术发展背后的哲学思考,以及如何在快节奏的编程生活中寻找内在的平衡点。通过分享个人的编程感悟和实践,旨在启发读者对技术的深入理解和生活的和谐统一。
|
项目管理 开发者
实际工作中结构化思维在表达中的应用
技术PM如何在实际工作中正确精准表达出自己想要输出的信息,掌握底层逻辑---结构化思维就显得尤为重要。而培养结构化思维的有效工具即是---金字塔原理。
42435 31
|
算法 C++
【C/C++】阿克曼函数以及其数学的有限边界思维
## 在递归函数论和涉及集合的并的某些算法的复杂性研究中,有一个起重要作用的递归函数——阿克曼(Ackermann)函数,该函数是由希尔伯特的学生,德国著名数学家威尔海姆·阿克曼于1928年发现的。这是一个图灵机可计算的,但不是原始递归的函数。下面,我们介绍这个经典的递归函数,并给出其相应的计算过程。
449 0
【C/C++】阿克曼函数以及其数学的有限边界思维
|
人工智能 算法 安全
8种提升程序猿编程能力的方法+编程思维四个核心:分解、抽象、模式识别和算法
对于程序员来说,提高自己的编程能力,算是给自己定的职业发展目标之一,不过定一个成为编程大神的目标很容易,具体做起来可能就不是一件简单的事了。首先,既然决定“我要变得更好”,得先知道“更好”是什么样子的。另外,不能“想变得更好”,却没有任何具体可行的措施。
891 2
8种提升程序猿编程能力的方法+编程思维四个核心:分解、抽象、模式识别和算法
|
存储 安全
每日积累,补充类似的作用的函数啦,前两个简单看看,最后一个可以记忆一下。
每日积累,补充类似的作用的函数啦,前两个简单看看,最后一个可以记忆一下。
每日积累,补充类似的作用的函数啦,前两个简单看看,最后一个可以记忆一下。