递归算法实例应用(五)

简介: 递归算法实例应用(五):(POJ 2787)算24

递归算法实例应用(五)

算24 (POJ 2787)

Description

给出4个小于10的正整数,你可以使用加减乘除4种运算以及括号把这4个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24。

这里加减乘除以及括号的运算结果和运算的优先级跟我们平常的定义一致(这里的除法定义是实数除法)。

比如,对于5,5,5,1,我们知道5 * (5 – 1 / 5) = 24,因此可以得到24。

又比如,对于1,1,4,2,我们怎么都不能得到24。

Input

输入数据包括多行,每行给出一组测试数据,包括4个小于10的正整数。

最后一组测试数据中包括4个0,表示输入的结束,这组数据不用处理。

Output

对于每一组测试数据,输出一行,如果可以得到24,输出“YES”;否则,输出“NO”。

Sample Input

5 5 5 1
1 1 4 2
0 0 0 0

Sample Output

YES
NO




算法思想:

同样拿到题之后呢,首先第一个想法依旧是能否将问题分解为规模更小的子问题,然后利用递归解决。

对于问题解决的第一步而言,普遍想法是,先找两个数进行一种加、减、乘、除运算,再将该结果与剩下的n-2个数进行运算,那么此时,问题规模就变为了n-1,因此本题就转变为一个递归问题。

再考虑递归的次数,我们从第一步的初始状态开始不断递归判断是否能满足题设要求,当以此为初始条件均不能满足题设要求时,应继续判定下一初始条件,所以我们应枚举所有可能的第一步的初始状态,即:枚举所有可能的两个数的组合。再以此为初始条件进行递归判断是否为解。

其次考虑递归的边界条件,基于前几题的求边界条件思想,本题的边界情况十分明显,即当问题规模递归减少为1时,仅有一个数,判断与24是否相等,若相等则应返回true,表明找到一组解,若不相等应返回false,表明本次递归的初始条件不能满足题设要求。

详细说明关于在递归时数据的处理:

  1. 选出两个数作为初始条件进行加、减、乘、除运算,选取时应取遍所有可能的组合,可通过二重循环实现。
  2. 将这两个数(a和b)的运算结果与余下的n-2个数存起来,共n-1个数,存入一个数组中,作为中间变量,在以该中间变量数组进行递归,依次判断a+b、a-b、b-a、a*b、a/b、b/a六种情况,在除法运算中,应保证除数不为0;此时的递归为将临时变量b里的n-1个数来算24,判断是否可以满足题设要求。问题的递归主体及关键也在这里。




代码逻辑:

依据上述算法思想,不难想出应设置一个初始数组存储题设输入的一组数据;因为本题中设计除法相关操作,所以数据可能为浮点类型,但是由于浮点数在计算机中存储采用IEE754标准,存在精度丢失问题,所以对于浮点类型数据是否为0的判定不能使用“==”来判定,所以,还应定义一个函数来实现对浮点数是否为0的一个判断。

  1. 在C语言中,进行浮点数为0判断时,一般考虑判断该数的绝对值是否小于一个极小值ε,通常ε选10^-6。

    代码如下:

    #define ESP 1e-6
    
    bool isZero(double num) {
        return fabs(num) <= ESP;
    }
  2. 由上述算法思想中分析,设递归函数定义为,bool count24(double a[], int n),即:对有n个元素的a组数据进行算24操作。

    • 首先进入递归函数时应判断是否满足边界条件,即,当问题规模n=1时,判断该数是否等于24。
    • 在递归主体中,首先声明中间变量数组b,用于存储剩下的n-1个数以实现递归。
    • 通过枚举每一种可能的初始条件,以期寻求所有可能的解。
    • 每找到两个数后,将其与的n-2个数存入中间变量数组b中,再对这两个数进行算法思想中第二点所分析的6种情况进行计算。
    • 假设以a+b为例,其代码可描述为:

      b[m] = a[i] + a[j];
      if (count24(b, m + 1))
          return true;
    • 假设以a/b为例,其中b不为0,其代码可描述为:

      if (!isZero(a[j])) {
          b[m] = a[i] / a[j];
          if (count24(b, m + 1))
              return true;
      }
  3. 本题思想逻辑较前几题几乎并无不同,但在代码实现上有更大的难度,主要是对不同的情况做不同的递归处理,同时对于数据的处理也应考虑递归时数据作用域的不同对函数运算结果过的影响。比如用于存储题设输入元素的数组a,应为全局变量,因为在递归处理时,我们以中间变量b来进行运算,但同时也用到了数组a来为b进行赋值。所以在每一层递归中都用到了该变量,与递归层级无关,所以设置为全局变量能更好的进行代码的编写;或者将原始数组a作为形参传入递归函数,但是由于递归操作本来就对函数调用栈有较高的占用率,若再增加没有必要的形参数量,无疑是为函数调用栈带来了更大的负载。
  4. 至此,递归章节将告一段落,以上数题基本涵盖了递归在时间和空间复杂度要求不高的情况下能仅用递归能解决问题的几种基本问题模型。




代码整合:

//
// Created by Ss1Two on 2023/1/18.
//

#include "stdio.h"
#include "stdbool.h"
#include "math.h"

#define ESP 1e-6 //

//判断浮点数num是否为0
bool isZero(double num) {
    return fabs(num) <= ESP;
}

//存储题设输入的原始数据
double a[5];

//把数组a中的n个元素进行算24判断
bool count24(double a[], int n) {

    //递归边界条件判定
    if (n == 1) {
        if (isZero(a[0] - 24))
            return true;
        else
            return false;
    }

    //递归时中间变量数组
    double b[5];

    //枚举每一种可能的(先找两个数做运算的)初始条件
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {

            int m = 0;// m作为处理剩下的n-2个数时的下标
            for (int k = 0; k < n; k++) {
                //将非初始条件之外的n-2个数据存入中间变量b数组中
                if (k != i && k != j)
                    b[m++] = a[k];
            }

            //将初始条件的两个值做加法运算,并存入中间变量b数组中
            //其中m+1==n-1
            b[m] = a[i] + a[j];

            //此时问题规模减一,即将b数组中的n-1个数,进行算24判断。
            //情况1:a + b
            if (count24(b, m + 1))
                return true;

            //情况2:a - b
            b[m] = a[i] - a[j];
            if (count24(b, m + 1))
                return true;

            //情况3:b - a
            b[m] = a[j] - a[i];
            if (count24(b, m + 1))
                return true;


            //情况4:a * b
            b[m] = a[i] * a[j];
            if (count24(b, m + 1))
                return true;


            //情况5:a / b ,其中b!=0
            if (!isZero(a[j])) {
                b[m] = a[i] / a[j];
                if (count24(b, m + 1))
                    return true;
            }

            //情况6:b / a ,其中a!=0
            if (!isZero(a[i])) {
                b[m] = a[j] / a[i];
                if (count24(b, m + 1))
                    return true;
            }
        }
    }
    return false;
}

int main() {
    while (true) {
        for (int i = 0; i < 4; i++) {
            scanf("%lf", &a[i]);
        }
        if (isZero(a[0]))break;
        if (count24(a, 4))
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}


Created by Ss1Two on 2023/01/18

目录
相关文章
|
3天前
|
机器学习/深度学习 数据采集 自然语言处理
理解并应用机器学习算法:神经网络深度解析
【5月更文挑战第15天】本文深入解析了神经网络的基本原理和关键组成,包括神经元、层、权重、偏置及损失函数。介绍了神经网络在图像识别、NLP等领域的应用,并涵盖了从数据预处理、选择网络结构到训练与评估的实践流程。理解并掌握这些知识,有助于更好地运用神经网络解决实际问题。随着技术发展,神经网络未来潜力无限。
|
3天前
|
算法 Python
利用贝叶斯算法对简单应用实现预测分类
利用贝叶斯算法对简单应用实现预测分类
6 0
|
3天前
|
机器学习/深度学习 算法 API
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
8 0
|
3天前
|
机器学习/深度学习 数据采集 算法
深入理解并应用机器学习算法:支持向量机(SVM)
【5月更文挑战第13天】支持向量机(SVM)是监督学习中的强分类算法,用于文本分类、图像识别等领域。它寻找超平面最大化间隔,支持向量是离超平面最近的样本点。SVM通过核函数处理非线性数据,软间隔和正则化避免过拟合。应用步骤包括数据预处理、选择核函数、训练模型、评估性能及应用预测。优点是高效、鲁棒和泛化能力强,但对参数敏感、不适合大规模数据集且对缺失数据敏感。理解SVM原理有助于优化实际问题的解决方案。
|
3天前
|
机器学习/深度学习 算法
理解并应用机器学习算法:决策树
【5月更文挑战第12天】决策树是直观的分类与回归机器学习算法,通过树状结构模拟决策过程。每个内部节点代表特征属性,分支代表属性取值,叶子节点代表类别。构建过程包括特征选择(如信息增益、基尼指数等)、决策树生成和剪枝(预剪枝和后剪枝)以防止过拟合。广泛应用在信贷风险评估、医疗诊断等领域。理解并掌握决策树有助于解决实际问题。
|
3天前
|
机器学习/深度学习 算法
应用规则学习算法识别有毒的蘑菇
应用规则学习算法识别有毒的蘑菇
|
3天前
|
存储 机器学习/深度学习 算法
R语言贝叶斯Metropolis-Hastings采样 MCMC算法理解和应用可视化案例
R语言贝叶斯Metropolis-Hastings采样 MCMC算法理解和应用可视化案例
|
3天前
|
数据采集 机器学习/深度学习 算法
数据分享|WEKA关联规则挖掘Apriori算法在学生就业数据中的应用
数据分享|WEKA关联规则挖掘Apriori算法在学生就业数据中的应用
|
3天前
|
机器学习/深度学习 自然语言处理 算法
机器学习算法原理与应用:深入探索与实战
【5月更文挑战第2天】本文深入探讨机器学习算法原理,包括监督学习(如线性回归、SVM、神经网络)、非监督学习(聚类、PCA)和强化学习。通过案例展示了机器学习在图像识别(CNN)、自然语言处理(RNN/LSTM)和推荐系统(协同过滤)的应用。随着技术发展,机器学习正广泛影响各领域,但也带来隐私和算法偏见问题,需关注解决。
|
3天前
|
机器学习/深度学习 算法 C语言
【C言专栏】递归算法在 C 语言中的应用
【4月更文挑战第30天】本文介绍了递归算法在C语言中的应用,包括基本概念(通过调用自身解决子问题)、特点(调用自身、终止条件、栈空间)和实现步骤(定义递归函数、分解问题、设置终止条件、组合解)。文中通过阶乘计算和斐波那契数列两个案例展示了递归的使用,强调了递归可能导致的栈溢出问题及优化需求。学习递归有助于理解和应用“分而治之”策略。