C语言递归实现24点游戏计算器:算法原理与VS2019完整教程

落日邮差
落日邮差
发布时间:2026-10-15 | 3人浏览过
原创

利用C语言中的递归思想与回溯策略,可以系统地枚举四个数字间的所有四则运算组合及括号排列方式,从而高效求解24点问题。

一、算法核心逻辑与数据模型

24点游戏的本质是在给定的四个整数中,寻找通过加、减、乘、除以及括号嵌套能够得到目标值(通常是24)的数学表达式。其难点在于运算顺序的多变性(如 (a + b) * (c - d) 与 a + (b * c) / d)。

算法采用递归(Recursive)与分治的思想:如果牌堆里只剩一张牌,判断该值是否等于24;否则,从当前牌堆中任取两张牌,将其进行四种基本运算(加、减、乘、除),并将结果放回牌堆,然后对剩下的牌堆(张数减一)继续递归计算。

为了同时输出具体的算式过程,程序需要维护每个中间状态的值及其对应的字符串表达式。使用浮点数(double)存储数值是为了兼容除法可能产生的小数情况,并通过设定极小值阈值(PRECISION = 1E-6)来判断浮点数比较时的相等关系。

二、开发环境准备

本例以 Visual Studio 2019 为例进行演示。其他编译器(如 GCC 或 MinGW)在语法上通用,但工程配置略有不同。

第1步:创建 C 语言项目

打开 VS2019,依次点击 创建新项目 → 搜索或选择 C++ 空项目 模式,由于是纯 C 代码,建议将文件名后缀统一改为 .c 以避免链接器警告,或者在属性中将文件视为 C 代码处理。此处展示标准新建步骤:

第2步:配置项目名称与路径

输入项目名称(如 Game24),选择存放路径并确认创建:

第3步:添加头文件与源文件

在项目视图中分别添加两个头文件和两个源文件,保持代码模块化:

三、源代码实现细节

第1步:定义公共依赖 (Main.h)

引入标准输入输出库和标准工具库:

#pragma once
#include <stdio.h>
#include <stdlib.h>

第2步:定义数据结构与接口声明 (PointGame.h)

这里定义了核心的 `Card` 结构体。它包含一个 `value` 用于存储当前数值,以及一个 `expression` 字符串数组用于记录当前的算式推导形式。宏定义限制了最大表达式长度以防越界。

#pragma once
#include <stdbool.h>
#include <math.h>

#define PRECISION 1E-6
#define TARGET 24
#define SIZE 4

typedef struct card
{
    double value;
    // 估算最大表达式长度:(SIZE-1)个运算符 + SIZE个数字 + 括号等开销
    char expression[5 * (SIZE - 1) + SIZE * 2 + 1];
} Card, *pCard;

bool game24(pCard card, int size); // 递归入口函数声明

第3步:编写核心递归引擎 (PointGame.c)

这是最关键的逻辑层。`game24` 函数接收当前的“卡牌堆”及其剩余数量 size。循环遍历所有不重复的两两配对 (m, n),生成剩余卡片堆,并将这两张卡片的四则运算结果压入堆底作为新的递归子问题。

  • 加法/乘法:满足交换律,只需进行一次组合即可。
    sprintf(stack[size - 2].expression, "(%s + %s)", m.expr, n.expr);
  • 减法:不满足交换律,需分别计算 m - n 和 n - m。
    注:原代码为节省空间优化了条件分支,实际上两种差值都要尝试。
  • 除法:同样不满足交换律,且必须检查分母不为 0。
    if (card[m].value != 0) { ... } if (card[n].value != 0) { ... }
#define _CRT_SECURE_NO_WARNINGS
#include "Main.h"
#include "PointGame.h"

bool game24(pCard card, int size)
{
    Card stack[3]; // 临时存储下一层的4张牌变成3张牌

    /* 递归基线条件:只剩一张牌 */
    if (size == 1)
    {
        // 浮点数相等判断需考虑精度误差
        if (fabs(card[0].value - TARGET) < PRECISION)
        {
            printf("%d = %s\n", TARGET, card[0].expression);
            return true; // 找到解,可返回true终止后续搜索
        }
        else
        {
            return false;
        }
    }

    /* 遍历选取两张牌的所有组合 */
    int m, n, i, t;
    for (m = 0; m < size - 1; m++)
    {
        for (n = m + 1; n < size; n++)
        {
            /* 首先把未被选中的其余牌拷贝到新堆栈 */
            t = 0;
            for (i = 0; i < size; i++)
            {
                if (i != m && i != n)
                {
                    stack[t].value = card[i].value;
                    sprintf(stack[t].expression, "%s", card[i].expression);
                    t++;
                }
            }

            /* --- 尝试加法 --- */
            stack[size - 2].value = card[m].value + card[n].value;
            sprintf(stack[size - 2].expression, "(%s + %s)", card[m].expression, card[n].expression);
            if (game24(stack, size - 1)) return true;

            /* --- 尝试减法 (双向) --- */
            if (card[m].value > card[n].value)
            {
                stack[size - 2].value = card[m].value - card[n].value;
                sprintf(stack[size - 2].expression, "(%s - %s)", card[m].expression, card[n].expression);
            }
            else
            {
                stack[size - 2].value = card[n].value - card[m].value;
                sprintf(stack[size - 2].expression, "(%s - %s)", card[n].expression, card[m].expression);
            }
            if (game24(stack, size - 1)) return true;

            /* --- 尝试乘法 --- */
            stack[size - 2].value = card[m].value * card[n].value;
            sprintf(stack[size - 2].expression, "%s * %s", card[m].expression, card[n].expression);
            if (game24(stack, size - 1)) return true;

            /* --- 尝试除法 (分母防零) --- */
            if (card[m].value != 0)
            {
                stack[size - 2].value = card[n].value / card[m].value;
                sprintf(stack[size - 2].expression, "%s / %s", card[n].expression, card[m].expression);
                if (game24(stack, size - 1)) return true;
            }
            if (card[n].value != 0)
            {
                stack[size - 2].value = card[m].value / card[n].value;
                sprintf(stack[size - 2].expression, "%s / %s", card[m].expression, card[n].expression);
                if (game24(stack, size - 1)) return true;
            }
        }
    }
    return false;
}

第4步:主函数交互逻辑 (Main.c)

负责获取用户输入的4个整数,初始化 `Card` 结构体的初始值,并调用 game24。若存在解打印 Done,否则打印 No Answer。

#define _CRT_SECURE_NO_WARNINGS
#include "Main.h"
#include "PointGame.h"

int main()
{
    system("color 3E"); // 设置控制台背景色
    int i, poke;
    Card card[SIZE];

    printf("请输入4个数字 以判断解法:");
    for (i = 0; i < SIZE; i++)
    {
        scanf("%d", &amp;poke);
        card[i].value = (double)poke;
        // 将输入整数转为字符串存入表达式字段
        sprintf(card[i].expression, "%d", poke);
    }

    if (game24(card, SIZE))
    {
        printf("Done\n");
    }
    else
    {
        printf("No Answer\n");
    }

    system("pause");
    return 0;
}

四、编译与测试验证

第1步:生成解决方案

在 VS2019 菜单栏点击 生成 → 生成解决方案。确保无错误或警告产生:

第2步:调试运行示例

按下 F5 启动调试。以常见的牌面 3, 8, 8, 9 进行测试:

运行输出预期

程序会识别出多种等价组合,例如输出: 24 = ((9 - 8) * 8) * 3 或类似形式的算式。

五、总结与进阶思考

上述代码清晰展示了利用递归回溯算法自动穷举 24 点可能性的全过程。在实际应用中,还可以进一步扩展以下功能:

  • 去重逻辑: 当前程序会输出所有找到的解,包括互为逆运算或符号差异的重复解,可根据需求增加过滤机制。
  • 多解统计: 修改返回值类型为计数器累加,而非找到一个即停止。
  • 复杂分数运算: 虽然使用了 double,但对于某些极端有理数精度丢失场景,可使用分数类(结构体分子分母)代替浮点数以获得更严谨的数学表达。

以上就是C语言实现24点游戏计算器的详细内容,更多关于C语言经典算法解析的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表