红雪  

在这里很早就申请了blog了却只写了一篇短短10几个字的blog,呵呵。
今儿说说算24点
首先说说传统算24点思想,给4个数(牌)[范围:1-13]用+,-,*,/,括号
来组成一个四则运算的表达式,使其结果等于24,并且表达式中每个数字只是用一便,
在除法中除数不能为0,ok这就是传统算24的要求了;表达式也有两种:其一,算出一个正确的结果及退出程序,其二算出来所有正确的表达式退出程序。
其实从这些要求中就能看出传统算24点的局限了,其一输入的数字限4个,其二输入的数字的范围1-13显然数字很小。

大家都知道一个算法在处理小数和处理大数的性能是有显著的差别的
那么我们能否拓展下这个传统算法的要求呢。比如输入20个数字甚至2万个数字或则更多(数字随机),再如
输入数字范围1-999,或则1-99999或则更大,当我们拓展了传统算法的24点后,能再用传统算法来解决现在的问题吗?显然是不能的,即使能那算法的性能也是最差的。且看网上的一个经典算24的程序如下:

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

const double PRECISION = 1E-6;
const int COUNT_OF_NUMBER  = 4;
const int NUMBER_TO_CAL = 24;

double number[COUNT_OF_NUMBER];
string expression[COUNT_OF_NUMBER];

bool Search(int n)
{
    if (n == 1) {
        if ( fabs(number[0] - NUMBER_TO_CAL) < PRECISION ) {
            cout << expression[0] << endl;
            return true;
        } else {
            return false;
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            double a, b;
            string expa, expb;

            a = number[i];
            b = number[j];
            number[j] = number[n - 1];

            expa = expression[i];
            expb = expression[j];
            expression[j] = expression[n - 1];

            expression[i] = '(' + expa + '+' + expb + ')';
            number[i] = a + b;
            if ( Search(n - 1) ) return true;
           
            expression[i] = '(' + expa + '-' + expb + ')';
            number[i] = a - b;
            if ( Search(n - 1) ) return true;
           
            expression[i] = '(' + expb + '-' + expa + ')';
            number[i] = b - a;
            if ( Search(n - 1) ) return true;
                       

            expression[i] = '(' + expa + '*' + expb + ')';
            number[i] = a * b;
            if ( Search(n - 1) ) return true;

            if (b != 0) {
                expression[i] = '(' + expa + '/' + expb + ')';
                number[i] = a / b;
                if ( Search(n - 1) ) return true;
            }
            if (a != 0) {
                expression[i] = '(' + expb + '/' + expa + ')';
                number[i] = b / a;
                if ( Search(n - 1) ) return true;
            }

            number[i] = a;
            number[j] = b;
            expression[i] = expa;
            expression[j] = expb;
        }
    }
    return false;
}

这个算法可谓小巧玲珑(递归回朔这样使得程序的判断减少了而且易于阅读,如果用迭代来计算那代码量比这里多得多了),对于输入数少,数字小的计算这个算法效率还是不错的。
仔细研究的读者应该能发现这个算法中是存在
问题1:重复计算的比如 : a-b=-(b-a),a+b=b+a,a*b=b*a
问题2:除法中没有判断是否整除
问题1原因:是运算表达式具有交换律的,虽然在最后的表达式出来后看到的是每个数字使用一次但是其在计算过程中已经重复了,所以我们应该排除这个不必要                 的计算使其只计算一次
解决方法:if 当前数字<上一次计算结果 then 计算 end 这样在计算过程中就避免了不必要的重复。
问题2原因:当计算到两个数不能整除时,严格意义上说这样算出来的结果是不等于24的,其结果是23.X或24.X类型的数字了,这也是这个算法里用 double  的
              类型的原因了。
解决方法: if b<>0 and a%b=0 then 计算 end 反之 if a<>0 and b%a=0 then 计算 end
那么这样改变之后对于我们拓展的要求能否满足呢?答案是否定的。
那么我还是从传统要求出发考虑: 数字小,数字少。
解决方案:
  1,对输入数字排序(排序选择上要考虑效率高并且稳定的算法)
  2,对排序后的数字两两相减。
     在对数字两两相减要注意:
    a,保存相减后的结果,保存相减的表达式。
           b,对于相减过程中如果数字过于小那么我就加上一个数,使其大小保持在一个有效的范围。
           c,在相减后的数中可能会出现重复的数字,折衷的方法是用你觉得舒服的数字来代替所有的重复数字,或则保留重复的一个数字其他的重复数字抵消。
           d,要缩减到多少个数后再计算24才是正确稳定呢?惟一方法是测试了,呵呵,运行1000次如果每次输入数在4到10个稳定,有效那么缩减的上限就是4-10               了。
基于上面的解决方案,我就做到了:数字小,数字少的要求了。到这里也许有人就要说了还不跟传统算法一样吗?诚然,这个改变后和传统的确一样
但是很多事情(不止是算法)改变外在的形式就能产生不同的效果,正如辩证法中,外因是事物发展的直接因素,内因是事物发展的根本。当我们没有找到改变内在根本方法时改变他的外在形式也能产生不同的效果。呵呵
经过这样改变后的算法在新的要求中第一次运行8毫秒,后面的几乎都是0毫秒了(机器不同这个结果也会不一样的)。呵呵
posted on 2010-01-15 17:32  战锋  阅读(1902)  评论(0编辑  收藏  举报