C++ 实现计算24点

原理

暴力枚举所有的情况,运算符号4个,加减乘除 + - * / ,整数数字4个。所需要枚举的次数:

  1. 数字顺序:4个数的全排列,4! = 24
  2. 运算符号:4个数需要3个符号,每个可选4种,43 = 64
  3. 加括号方式:((AB)C)D(AB)(CD)(A(BC))DA((BC)D)A(B(CD)),共5种。
  4. 枚举次数 24*64*5

实现细节

  • 全排列枚举由库函数 next_permutation来完成枚举
  • 运算符号由一个整数(范围0—63)表示,00、01、10、11分别表示 +-*/,低2位代表运算符1,中2位代表运算符2,高2位代表运算符3。例如 37 = 100101,表示 - - *
  • 加括号方式手动分别写出来。
  • 由于运算过程中含有除法,所以当运算符为除法 且 除数为 0 时,需额外处理一下。
  • 验证是否有解和打印解分开,更灵活。

代码

#include <ranges>
#include <string>
#include <iostream>
#include <algorithm>
#include <format>
#include <array>
//运算符 + - * / , 数量 num_n = 4
constexpr double eps = 1e-8;
using array4 = std::array<int, 4>;
// 验证是否有解
bool has_answer(const array4& nums, int op, int method){
    bool div_err = false;
    auto calc = [&div_err](double x, int oper, double y) -> double {
        if(oper == 3 and std::abs(y) < eps){
            div_err = true;
            return 0;
        }
        return oper==0 ? x+y : oper==1 ? x-y : oper==2 ? x*y : x/y;
    };
    auto [a,b,c,d] = nums;
    int op1 = op&3, op2 = op>>2&3, op3 = op>>4&3;
    double res = 0;
    if(method == 1)      // ((A+B)+C)+D
        res = calc(calc(calc(a, op1, b), op2, c), op3, d);
    else if(method == 2) // (A+B)+(C+D)
        res = calc(calc(a, op1, b), op2, calc(c, op3, d));
    else if(method == 3) // (A+(B+C))+D
        res = calc(calc(a, op1, calc(b, op2, c)), op3, d);
    else if(method == 4) // A+((B+C)+D)
        res = calc(a, op1, calc(calc(b, op2, c), op3, d));
    else if(method == 5) // A+(B+(C+D))
        res = calc(a, op1, calc(b, op2, calc(c, op3, d)));
    return not div_err and std::abs(res - 24) < eps;
}
// 转换为 答案字符串 
std::string answer_to_string(const array4& nums, int op, int method){
    const char* ops = "+-*/";
    const char* res_fmt[] = {
        "There are result format string", 
        "(({}{}{}){}{}){}{} = 24", // ((A+B)+C)+D
        "({}{}{}){}({}{}{}) = 24", // (A+B)+(C+D)
        "({}{}({}{}{})){}{} = 24", // (A+(B+C))+D
        "{}{}(({}{}{}){}{}) = 24", // A+((B+C)+D)
        "{}{}({}{}({}{}{})) = 24"  // A+(B+(C+D))
    };
    return std::vformat(res_fmt[method], std::make_format_args(nums[0], ops[op&3], nums[1], ops[op>>2&3], nums[2], ops[op>>4&3], nums[3]));
}
int main() {
    array4 nums;
    while(true) {
        for(int& val : nums){
            std::cin >> val;
        }
        std::ranges::sort(nums);
        bool no_solution = true;
        do {			
            for(int method = 1; method <= 5; method++) {
                for(int oper = 0; oper < 64; oper++) {
                    if(has_answer(nums, oper, method)) {
                        no_solution = false;
                        std::cout << answer_to_string(nums, oper, method) << std::endl;
                        //若是打印所有可能答案,注释下方 goto 代码, 注意:重复解未去重
                        goto outer_loop; // break multiloop
                    }
                }
            }
        } while(std::ranges::next_permutation(nums).found);
        outer_loop:
        if(no_solution){
            std::cout << "No solution" << std::endl;
        }   
    }
}

测试

每次输入4个数,空格分开。
测试
(date: 2019.2.17【旧】已删除)
(date:2024.8.25【新】)
five years later…
(date:2024.1.15)

递归版的

原理就是遍历每一组的两两组合,组合成一个新数,这样总数量减一,就能按数量递归下去了。

// 24dian.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <vector>
#include <string>
using Numbers = std::vector<double>;
using Answers = std::vector<std::string>;
//存储步骤解
Answers answers;

//转为字符串 a + b = c
std::string to_concat(double a, char oper, double b, double c) {
    char buffer[100];
    std::snprintf(buffer, 100, "%g %c %g = %g", a, oper, b, c);
    return std::string(buffer);
}

//递归求解
bool my_solve24(const Numbers& nums, Answers* ans = nullptr) {
    int n = nums.size();
    //只剩一个数时,直接判断是否等于24
    if (n == 1) {
        return std::abs(nums[0] - 24) < 1e-6;
    }
    //第一次,清空答案
    if (ans == nullptr) {
        ans = &answers;
        ans->clear();
    }   
    //尝试两两组合
    for (int i = 0;i < n;i++) {
        for (int j = i + 1;j < n;j++) {
            //拷贝一个不含 nums[i] 和 nums[j] 的新数组
            Numbers newArr;
            for (int k = 0; k < n; k++) {
                if (k == i || k == j)
                    continue;
                newArr.push_back(nums[k]);
            }
            double a = nums[i], b = nums[j];
            //a+b
            newArr.push_back(a + b);
            if (my_solve24(newArr, ans)) {
                ans->push_back(to_concat(a, '+', b, a + b));
                return true;
            }
            newArr.pop_back();
            //a-b
            newArr.push_back(a - b);
            if (my_solve24(newArr, ans)) {
                ans->push_back(to_concat(a, '-', b, a - b));
                return true;
            }
            newArr.pop_back();

            //b-a
            newArr.push_back(b - a);
            if (my_solve24(newArr, ans)) {
                ans->push_back(to_concat(b, '-', a, b - a));
                return true;
            }
            newArr.pop_back();

            //a*b
            newArr.push_back(a * b);
            if (my_solve24(newArr, ans)) {
                ans->push_back(to_concat(a, '*', b, a * b));
                return true;
            }
            newArr.pop_back();

            //a/b
            if (b != 0) {
                newArr.push_back(a / b);
                if (my_solve24(newArr, ans)) {
                    ans->push_back(to_concat(a, '/', b, a / b));
                    return true;
                }
                newArr.pop_back();
            }

            //b/a
            if (a != 0) {
                newArr.push_back(b / a);
                if (my_solve24(newArr, ans)) {
                    ans->push_back(to_concat(b, '/', a, b / a));
                    return true;
                }
                newArr.pop_back();
            }

            


        }
    }
    return false;
}


int main() {
    const int n = 4;
    while (true) {
        Numbers nums;
        std::cout << "请输入" << n << "个整数,用空格分隔:" << std::endl;
        for (int i = 0; i < n; i++) {
            double num;
            std::cin >> num;
            nums.push_back(num);
        }
        if (my_solve24(nums)) {
            //输出答案
            for (auto it = answers.rbegin(); it != answers.rend(); it++) {
                std::cout << *it << std::endl;
            }
        }
        else {
            std::cout << "无解。" << std::endl;
        }
    }
    return 0;
}

测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值