24 点游戏是一个很有意思的数字游戏,也是一道常见的算法面试题。题目是这样的:任给四个数(为了便于人们心算或口算,一般都是小于 10 的数),对四个数字用各种组合进行加、减、乘、除四则运算,看看结果是否能等于 24?对于面试题来说,这是一个典型的穷举类型算法问题。这个题目比较有意思的地方是它除了要对数字组合进行枚举,还要对四个运算符进行组合。我们要介绍的方法有点特殊,它没有简单地使用穷举遍历,而是采用穷举法和分治法相结合的方法来解决这个问题,这种方法比数字 + 运算符一起枚举的方法简单,容易理解,整个算法只有大约 40 行有效代码,其中主体部分有效代码只有不到 20 行,快来看看是怎么回事儿吧。

问题分析和建模

这个算法的难点主要两个

  1. 数字的运算符和穷举遍历
  2. 将四则运算表达式作为结果输出

引言部分提到过,我们这个方法会用到分治法,分治法的主要特点之一就是通过分解子问题的方式减小问题的规模,怎么划分子问题呢?子问题和原始问题必须是同构的,所谓同构就是问题必须是一样的,问题的模式不能变,能变的只是问题的规模。

对于这个问题来说,原始问题的规模是 4 个数字计算 24 点,那么分解子问题可以从两个方向考虑:

  • 一种是只考虑减少问题的规模,对于这个问题来说,减少规模不就是变成 3 个数字计算 24 点吗?然后再减少为两个数字计算 24 点,以此类推,直到问题能够直接求解为止;
  • 另一种是在减少问题规模的同时,调整结果的范围,同样,对这个问题来说,假如说我将问题规模从 4 个变成 3 个,被排除的数字是 3,那么子问题就应该变成“3 个数字计算 21 点”。进一步将问题规模减少成两个数字时,假如被排除的数字是 7,则子问题就变成“2 个数字计算 14 点”,以此类推,直到问题能直接解决为止。

我们的思路是每次从 4 个数字中任选两个,分别应用加、减、乘、除四种运算方法得到 4 个计算结果,每个计算结果与剩下的 2 个数字一起组成一个规模为 3 个数字的子问题,一共可以得到 4 个子问题,其变化过程如图所示(图中第一行的数字为 4 个数字的索引位置,第二行的数字分别是 4 个待计算数字,第三行是计算过程),使用第一个数字和第二个数字组合计算,得到了一组 4 个子问题,然后用第一个数字和第三个数字组合计算,得到了另一组 4 个子问题:

从 3 个数字中任选两个,然后应用加、减、乘、除四种运算方法得到 4 个计算结果,每个计算结果与剩下的 1 个数字组成一个规模为两个数字的子问题,又可以得到 4 个子问题。从 3 个数字中任选两个进行不重复的排列,可以得到 $P_{3}^{2} $= 6个组合结果,也就是说总共有 6 × 4 = 24 个规模为两个数字的子问题,下图展示了其中一组,也就是第一个数 3/7 和第二个数 3 的组合情况:

第三层组合计算将问题规模减少到1个数字

以上就是我们介绍的穷举法 + 分治法解决 24 点计算问题的算法分析过程。前面提到过,这个问题的难点有两个,上述分析过程解决了第一个,即数字和运算符的穷举遍历问题。**还有第二个问题,也就是将四则运算表达式作为结果输出的问题没有解决。**可能大家已经从几个图上看到了,图的第三行就是最后要输出的中缀表达式,这是怎么做到的呢?其实很简单,我们给每个数字都指定了一个“出身”,所谓的“出身”就是描述这个数字的来历,或者是计算过程。每个数字的“出身”记录了这个数字的计算过程,当数字被带入到子问题的时候,这个计算过程也跟着被带入到子问题,并随着子问题的求解过程一步一步带到最后。对于原问题来说,4 个数字的出身就是数字本身,当两个数字参与一次计算称为一个结果数字时,就将这两个数字的计算过程作为结果数字的“出身”。

好了,根据上面的分析,我们已经明确了问题和子问题的定义,就是“用 m 个数排列组合计算 24 点(1⩽m⩽4)(1\leqslant m \leqslant 4)(1⩽m⩽4)”。所以我们的子问题的参数就是 m 个数,考虑用数组来组织这 m 个数。每个数除了数字本身,还有一个出身,用以下数据结构来描述这个“数”:

typedef struct
{double num;std::string num_str;
}Number;

num_str 是这个数的“出身”,用字符串描述没问题,num是数字本身,但是数据类型用了 double,这也是实际计算过程的需要,毕竟从上图中也能看到,我们的计算方法是支持分数形式的,中间计算过程会出现浮点数,最终子问题定义就是 Calc24()函数的参数:

void Calc24(const std::vector<Number>& nums)
{//求解子问题
}//原始问题的定义
std::vector<Number> numbers = { { 3, "3" },{ 3, "3" },{ 7, "7" },{ 7, "7" } };
Calc24(numbers);

算法实现

递归作为一种算法的实现方式,与分治法是一对儿天然的好朋友

Calc24() 函数对子问题进行处理的时候,要对子问题规模是 1 个数的情况做处理,这实际上也是递归函数的退出(递归终止)条件。对于这个问题来说,当子问题的规模是 1 个数的时候,就要检查这个数是否是 24,如果是则输出一组结果,并退出递归处理;如果不是,说明这个穷举出来的结果是个无效结果,直接退出递归处理。这部分判断和处理的实现在第 4 行开始的 if (count == 1) 处理流程里,比较简单,就不多说了。对于子问题规模大于 1 的情况,就要选两个数进行计算,对于 Pn2P_{n}^{2}Pn2​问题,常用的代码实现模式就是两重循环。

void Calc24(std::vector<Number>& nums)
{std::size_t count = nums.size();if (count == 1) //当只有一个数时,说明计算完成,可以判断结果了{if (nums[0].num == 24){std::cout << nums[0].num_str << " = " << nums[0].num << std::endl;}return;}//两重循环,从 nums 中找两个数的组合for (std::size_t i = 0; i < count; i++){for (std::size_t j = 0; j < count; j++){if (i == j) //排除相同的情况continue;for (auto& op : acops) //对四种运算进行枚举{Number new_num;//运算可能失败,比如除数是 0 的情况,不再继续处理这个运算符,相当于剪枝效果if (op(nums[i], nums[j], new_num)){std::vector<Number> sub_nums;//定义子问题sub_nums.push_back(new_num);//除了被选出来的两个数,将剩下的数加入子问题for (std::size_t k = 0; k < count; k++) {if ((k != i) && (k != j)){sub_nums.push_back(nums[k]);}}Calc24(sub_nums); //解决子问题}}}}
}

现在说说 acops,它是一个计算函数的数组,定义了对两个操作数的加、减、乘、除四种运算。std::function<…>是个可调用对象包装器,这里包装的是一个这样的调用接口:

bool (const Number&, const Number&, Number&)

这个接口有两个 const Number& 类型的入参,一个 Number& 类型的出参和一个 bool类型的返回值,两个入参是参与计算的操作数,出参是计算的结果。四个操作符对应的可调用对象是用lamda 表达式定义的操作函数。这些操作函数的的作用很简单,就是计算两个操作数,当然,还有很重要的一点,就是拼装计算结果的“出身”。前面分析算法的时候提到过,一个数的“出身”很重要,即使数字本身算对了,如果“出身”拼装的不正确,输出的结果也不正确。“出身”拼装很简单,就是将参与计算的两个数的“出身”用操作符连接在一起,然后两端加上一对儿括号,就得到结果数字的“出身”了。这里面只有除法比较特殊一点,因为被除数不能为 0,所以加了个判断。当其返回 false 的时候,相当于做了一次剪枝操作。

std::function<bool (const Number&, const Number&, Number&)> acops[] =
{[](const Number& d1, const Number& d2, Number& dr) { dr.num = d1.num + d2.num; dr.num_str = '(' + d1.num_str + '+' + d2.num_str + ')';return true; },[](const Number& d1, const Number& d2, Number& dr) { dr.num = d1.num - d2.num;dr.num_str = '(' + d1.num_str + '-' + d2.num_str + ')';return true;},[](const Number& d1, const Number& d2, Number& dr) {dr.num = d1.num * d2.num;dr.num_str = '(' + d1.num_str + '*' + d2.num_str + ')';return true;},[](const Number& d1, const Number& d2, Number& dr) {if (d2.num == 0)return false;dr.num = d1.num / d2.num;dr.num_str = '(' + d1.num_str + '/' + d2.num_str + ')';return true;}
};

完整代码

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <utility>typedef struct
{double num;std::string num_str;
}Number;std::function<bool (const Number&, const Number&, Number&)> acops[] =
{[](const Number& d1, const Number& d2, Number& dr) { dr.num = d1.num + d2.num; dr.num_str = '(' + d1.num_str + '+' + d2.num_str + ')';return true; },[](const Number& d1, const Number& d2, Number& dr) { dr.num = d1.num - d2.num;dr.num_str = '(' + d1.num_str + '-' + d2.num_str + ')';return true;},[](const Number& d1, const Number& d2, Number& dr) {dr.num = d1.num * d2.num;dr.num_str = '(' + d1.num_str + '*' + d2.num_str + ')';return true;},[](const Number& d1, const Number& d2, Number& dr) {if (d2.num == 0)return false;dr.num = d1.num / d2.num;dr.num_str = '(' + d1.num_str + '/' + d2.num_str + ')';return true;}
};void Calc24(const std::vector<Number>& nums)
{std::size_t count = nums.size();if (count == 1) //当只有一个数时,说明计算完成,可以判断结果了{if (nums[0].num == 24){std::cout << nums[0].num_str << " = " << nums[0].num << std::endl;}return;}//两重循环,从numbers中找两个数的组合for (std::size_t i = 0; i < count; i++){for (std::size_t j = 0; j < count; j++){if (i == j) //排除相同的情况continue;for (auto& op : acops) //对四种运算进行枚举{Number new_num;//运算可能失败,比如除数是0的情况,不再继续处理这个运算符,相当于剪枝效果if (op(nums[i], nums[j], new_num)){std::vector<Number> sub_nums;//定义子问题sub_nums.push_back(new_num);//除了被选出来的两个数,将剩下的数加入子问题for (std::size_t k = 0; k < count; k++) {if ((k != i) && (k != j)){sub_nums.push_back(nums[k]);}}Calc24(sub_nums); //解决子问题}}}}
}int main()
{std::vector<Number> numbers = { { 3, "3" },{ 3, "3" },{ 7, "7" },{ 7, "7" } };//std::vector<Number> numbers = { { 1, "1" },{ 5, "5" },{ 5, "5" },{ 5, "5" } };//std::vector<Number> numbers = { { 1, "1" },{ 6, "6" },{ 8, "8" },{ 9, "9" } };//std::vector<Number> numbers = { { 2, "2" },{ 7, "7" },{ 6, "6" },{ 3, "3" } };Calc24(numbers);return 0;
}

24点计算器问题[C++实现]相关推荐

  1. javafx 制作 24点游戏 24点计算器 24点算法

    24点我也没想到太好的办法 主要思路如下(以四个数举例): 1.先生成3个符号,数量是4* 4* 4也就是64个不同的符号数组,类似这样[+,-,*] 2.将输入的四个数与64组不同符号对应运算保留可 ...

  2. 在线24点计算器工具

    24点计算器_在线24点计算器工具_教育学习_好工具网  http://www.nicetool.net/app/24game.html 24点是一种益智游戏,24点是把4个整数(一般是正整数)通过加 ...

  3. 计算机器点游戏,24游戏计算器

    24点计算器软件是一款游戏计算器,用给出的随机4个数字,运用算法将计算结果为24.它也可以自定义数字,也可以是随机的,你将结果输入方框内即可,它会有计时. 软件功能 发牌:随机挑选出4个数字 自定义牌 ...

  4. python习作——简易24点计算器

    最近了解了下python的itertools模块,感觉挺强大的.写了个简单的24点计算器做练习.思路很简单,对于一个24点的计算过程,一定可以通过调整顺序和加括号表现为4个操作数3个操作符这样的形式. ...

  5. 第3-5课:24 点计算器

    24 点游戏是一个很有意思的数字游戏,也是一道常见的算法面试题.题目是这样的:任给四个数(为了便于人们心算或口算,一般都是小于 10 的数),对四个数字用各种组合进行加.减.乘.除四则运算,看看结果是 ...

  6. 24点计算器c语言源代码,萌新求助!!24点游戏计算器

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 有没有大佬知道这个代码的运行过程,急!! #include #include #include #define PRECISION 1E-6 #defin ...

  7. 24点计算器Python脚本

    使用Python脚本枚举24点答案,去除冗余括号,效率没有Ruby版本的好,可能因精度的关系,和其它人些的答案数目不一致,记录之. # -*- coding:gbk -*-import itertoo ...

  8. Python 程序:24点计算器

    24点是一种非常简单的游戏,可以锻炼我们的计算能力. 今天博主给大家带来的就是一个关于24点计算的程序,并不是很难,希望能给python学习者提供一些帮助. 首先我编写了一个输入数字后自动返回有解或无 ...

  9. linux中24点游戏下载,怀旧24点官网版-怀旧24点游戏下载v2.0.0-Linux公社

    怀旧24点游戏,是一款可以开发思维的休闲益智类手机游戏.游戏中有着精美简约的画面,并且有多个不同的游戏模式和等级场次玩家可以自由进行选择.怀旧24点游戏内容丰富多彩,还支持多种语言系统.每日登录签到还 ...

最新文章

  1. linux查找用户前三进程_查看 Linux 系统中进程和用户的内存使用情况 | Linux 中国...
  2. Android—Binder+AIDL
  3. 表达式_关系式_关系表达式_比较表达式
  4. 手持iPhone 12测温测出80度?安兔兔致歉:测温枪年久失修!
  5. 技术要扎扎实实的做,业余功夫也要修炼
  6. 网关支付、银联代扣通道、快捷支付、银行卡支付分别是怎么样进行支付的?
  7. android 9.0 xposed,EdXposed管理器(安卓9.0专用)
  8. 结合模电分析电流源电路(简单晶体管电流源,Howland电流源,压控电流源)
  9. git clone 码云仓库项目报错fatal: Authentication failed for ‘https://gitee.com/...‘
  10. Ubuntu18.04创建快捷方式
  11. 如何修改织梦后台登陆界面
  12. YOLOV5改进||YOLOV5+GSConv+Slim Neck
  13. 计算机开机速度慢是什么原因,电脑开机慢是什么原因?怎么处理?
  14. e.Row.RowType == DataControlRowType.DataRow诠释(实例解释)转自孤舟济海,云卷云舒
  15. Qt5学习之位置函数
  16. 遥感影像内部“白点”去除技巧
  17. uni-app中设置不同平台显示不同的样式
  18. officemix安装 0x80091007 哈希数值不正确
  19. 基于逻辑回归(Logistic Regression)的糖尿病视网膜病变(Diabetic Retinopathy)检测
  20. 【苹果电脑装Windows7驱动大全、Macbook电脑win7驱动安装】

热门文章

  1. 解决Maven通过ojdbc连接Oracle
  2. Ble Mesh技术(一)之概览
  3. socket.io-client源码分析——建立socket连接
  4. 2023华为机考刷题指南:八周机考速通车
  5. 各大编程语言、软件,电子电路刷题学习网站链接及微信公众号
  6. Java 项目开发团队分配管理软件
  7. 漫威《黑豹2:瓦坎达万岁》经历坎坷,近期终于恢复制作
  8. Rabbitmq- 消费者ack机制与发布者消息确认
  9. [收藏]超实用压力测试工具-ab工具
  10. PCB中MARK点画法与注意事项