zoukankan      html  css  js  c++  java
  • [转载]24点游戏

    原文链接:http://eol.gzu.edu.cn/eolcourse/common/blog/userBlogArticleView.jsp?bkt=%2Feolcourse%2Fcommon%2Fblog%2FuserBlogColumnArticle.jsp%3FblogId%3D6444%26columnId%3D2664&articleId=6164

    相当推荐,暂时我还没有时间看,自己写了一个简单的,呵呵呵呵,留着备用

    一、前言

    24点游戏是一个常见游戏,出题者给出4个整数,要求答题者在指定时间内给出一个四则运算的表达式,恰好用上这这个整数各一次,计算结果为24,超出时间为输。

    二、分析

    用计算机来算这个题,搜索速度当然有很大优势,我编程喜欢考虑通用一点,不限制输入数量和结果数,甚至不限制运算符数量。这样组合数就很大,如果输入数比较多,则搜索时间会非常长。

    我用两个方法来提高搜索速度:一、是大家都能考虑到的重复搜索问题,比如1,2,3和2,3,1所有的组合情况是相同的,我只搜索使用递增序的数组,则可以降低一个组合数的数量级别;二、使用动态规划中的备忘录方法,比如你计算出2和3所有可能的技术结果,则他们与4结合的时候,要用到,与1结合的时候,也要用到,使用备忘录,可以只计算一次,大大降低运算复杂度。

    设A表示输入数组,V表示希望值,O表示运算符集合。

    设F(A,O)表示数组A可以选择任意O个运算符,最终可以得到的不同值。

    例F({1,2},{+,-})={-1,1,3}。

    设A的全非空真子集为T

    则F(A,Q)={x|x=F(a,O) o F(b,O),a∈T,o∈O,b=T-a} 这就是动态规划的递归式

    三、设计

    整体设计:分别设计4个类:游戏、表达式、运算、分数,各司其责,结构清晰,易于扩展。

    详细设计:

    1、 运算类,非常简单,可以快速跳过

    class Operator
    {
        public char Symbol;     // 运算符
        public bool Exchangable;// 是否有交换律
        public int Priority; // 优先级,值越大越有效
     
        public Operator(char symbol, bool exchangable, int priority)
        {
            Symbol = symbol;
            Exchangable = exchangable;
            Priority = priority;
        }
        public static Operator Add = new Operator('+', true, 0);
        public static Operator Sub = new Operator('-', false, 0);
        public static Operator Mul = new Operator('*', true, 1);
        public static Operator Div = new Operator('/', false, 1);
        static List<Operator> operators = null;
        public static List<Operator> Operators
        {
            get
            {
                if (operators == null)
                {
                    operators = new List<Operator>(4);
                    operators.Add(Add);
                    operators.Add(Sub);
                    operators.Add(Mul);
                    operators.Add(Div);
                }
                return operators;
            }
        }
    }
     
    2、分数类,设置这个类的目的是可以让除法运算不丢失有效数字
    class Fraction : IComparable<Fraction>
    {
        public int Numerator; // 分子
        public int Denominator = 1; // 分母
        public double Value { get { return Numerator * 1.0 / Denominator; } }
        public Fraction(int numerator)
        {
            Numerator = numerator;
        }
        /// 进行分数运算
        public static Fraction Operate(Fraction f1, Fraction f2, Operator opt)
        {
            int n1 = f1.Numerator;
            int n2 = f2.Numerator;
            int d1 = f1.Denominator;
            int d2 = f2.Denominator;
            Fraction ret = new Fraction(0);
            switch (opt.Symbol)
            {
                case '+':
                    ret.Numerator = n1 * d2 + n2 * d1;
                    ret.Denominator = d1 * d2;
                    ret.Compact();
                    return ret;
                case '-':
                    ret.Numerator = n1 * d2 - n2 * d1;
                    ret.Denominator = d1 * d2;
                    ret.Compact();
                    return ret;
                case '*':
                    ret.Numerator = n1 * n2;
                    ret.Denominator = d1 * d2;
                    ret.Compact();
                    return ret;
                case '/':
                    if (d2 == 0 || n2 == 0) break;
                    ret.Numerator = n1 * d2;
                    ret.Denominator = d1 * n2;
                    ret.Compact();
                    return ret;
            }
            return null;
        }
        /// 实现IComparable<Fraction>接口
        public int CompareTo(Fraction other)
        {
            int v1 = Numerator * other.Denominator;
            int v2 = Denominator * other.Numerator;
            return v1 - v2;//Value.CompareTo(other.Value)不如这样精确
    }
     
        /// 归约分子分母到统一形式,以保证同值的分数比较结果相同
        public void Compact()
        {
            //分子分母约分
            int gcd = GCD(Math.Abs(Numerator), Math.Abs(Denominator));
            if (gcd != 0)
            {
                Numerator /= gcd;
                Denominator /= gcd;
            }
            if (Denominator < 0)
            {//调整分母为正
                Denominator = -Denominator;
                Numerator = -Numerator;
            }
        }
     
        static void Swap(ref int x, ref int y)
        {
            x ^= y; y ^= x; x ^= y;
        }
     
        /// 辗转相减法求最大公约数
        static int GCD(int x, int y)
        {
            if (0 == x) return y;
            if (0 == y) return x;
            do
            {
                if (x < y)
                    Swap(ref x, ref y);
                x -= y;
            } while (x != 0);
            return y;
        }
    }
     
    3、表达式类
    class Expression
    {
        /// 单数值表达式
        public int Number;
        /// 左子表达式
        public Expression LChild;
        /// 与子表达式运算的运算符
        public Operator Operator;
        /// 右子表达式
        public Expression RChild;
     
        public Expression(int number)
        { Number = number;}
        public Expression(){}
     
        // 构造表达式字符串
        public override string ToString()
        {
            if (Operator == null)
            {//无子表达式的单独数,直接返回
                if (Number < 0) //负数加括号
                    return "(" + Number.ToString() + ")";
                else
                    return Number.ToString();
            }
            string s1 = LChild.ToString();
            //如果左子表达式比当前运算符优先级低,需加括号
            if (LChild.Operator != null)
            {
                int dp = Operator.Priority - LChild.Operator.Priority;
                if (dp > 0)
                    s1 = "(" + s1 + ")";
            }
     
            string s2 = RChild.ToString();
            //如果右表达式2比当前运算符优先级低 或者 两者同优先级但当前运算符不具备交换律,加括号
            if (RChild.Operator != null)
            {
                int dp = Operator.Priority - RChild.Operator.Priority;
                if (dp > 0 || dp == 0 && Operator.Exchangable == false)
                    s2 = "(" + s2 + ")";
            }
            return s1 + Operator.Symbol + s2;
        }
     
        public override bool Equals(object obj)
        {
            Expression other = obj as Expression;
            if (Operator == null || other.Operator == null)
            {
                if (Operator != null || other.Operator != null)
                    return false;//操作符存在情况不一致
                return Number == other.Number;
            }
            if (Operator.Symbol != other.Operator.Symbol)
                return false;
     
            if (Operator.Exchangable == false)
            {//不可交换运算,左右子表达式必须严格相等
                if (LChild.Equals(other.LChild) ==false  || RChild.Equals(other.RChild) ==false )
                    return false ;
            }
            else
            {//可交换运算,左右交叉也认为是相同表达式
                if ((LChild.Equals(other.RChild) == false || RChild.Equals(other.LChild) == false) &&
                    (LChild.Equals(other.LChild) == false || RChild.Equals(other.RChild) == false))
                    return false ;
            }
            return true ;
        }
    }
    4、游戏类,核心算法在这里
    class EquationGame
    {
        /// 备忘录,记录由哪组数,可以达到哪些值,对应的(1个)表达式是怎样
        public Dictionary<string, SortedList<Fraction, Expression>> memo = new Dictionary<string, SortedList<Fraction, Expression>>();
        ///记录哪个子数组和另外一个子数组是否交叉计算过
        public Hashtable hashTable = new Hashtable();
        public int[] array;//数组
        public int n;//数组大小
        public EquationGame(int[] array)
        {
            Array.Sort(array);
            this.array = array;
            this.n = array.Length;
        }
        public Expression GetOneExpression(int r)
        {
            //计算所有可能的最终结果,存入备忘录
            BuildAllPossbile(array);
            //查找最终结果为要求数的表达式
            string strAll = GetKeyString(array);
            Fraction fra = new Fraction(r);
            if (memo[strAll].ContainsKey(fra))
                return memo[strAll][fra];
            return null;
        }
        /// 构造由arr里面的每个数用一次运算的不同计算结果
        /// 并保留对应表达式,核心算法在这里
        void BuildAllPossbile(int[] arr)
        {
            string strKey = GetKeyString(arr);
            if (memo.ContainsKey(strKey) == true)
                return;//已存在,不用再计算
            SortedList<Fraction, Expression> list = new SortedList<Fraction, Expression>();
            if (arr.Length == 1)
            {//单个数直接添加
                Expression exp = new Expression(arr[0]);
                list.Add(new Fraction(arr[0]), exp);
            }
            //子串长度,根据组合公式C(n,r)=C(n,n-r),只搜索到一半就完成
            for (int len = 1; len <= arr.Length / 2; len++)
            {
                //将arr分成两个数组,一个长度为len,一个为arr.Legnth-len,求所有可能的组合
                int[] a1 = new int[len];
                int[] a2 = new int[arr.Length - len];
                int[] select = new int[arr.Length];//当前组合选入a1的数在arr中的下标
                for (int i = 0; i < len; i++)
                    select[i] = i;
                while (true)
                {
                    //将选择映射到两个数组
                    int c1 = 0, c2 = 0, si = 0;
                    for (int i = 0; i < arr.Length; i++)
                    {
                        if (select[si] == i)
                        {
                            a1[c1++] = arr[i];  //前半数组
                            si++;
                        }
                        else
                            a2[c2++] = arr[i];  //后半数组
                    }
     
                    string hKey = GetKeyString(a1) + ' ' + GetKeyString(a2);
                    if (hashTable.Contains(hKey) == false)
                    {//如果a1和a2没有计算过,则进行计算
                        hashTable.Add(hKey, null);
                        BuildAllPossbile(a1);
                        BuildAllPossbile(a2);
                        CalculateAll(a1, a2, list);
                    }
                    //调整到下一个可能 比如5选2有
                    //找下一个可调整位置
                    int t = len - 1;
                    while (t >= 0 && select[t] == arr.Length - len + t)
                        t--;
                    if (t == -1) break;
     
                    select[t]++;
                    for (int j = t + 1; j < len - 1; j++)
                        select[j] = select[j - 1] + 1;
                }
            }
            memo[strKey] = list;
        }
        /// 计算a1和a2所有可能的运算结果,保存到list
        void CalculateAll(int[] a1, int[] a2, SortedList<Fraction, Expression> list)
        {
            SortedList<Fraction, Expression> list1 = memo[GetKeyString(a1)];
            SortedList<Fraction, Expression> list2 = memo[GetKeyString(a2)];
            Fraction fra;
            foreach (Fraction fra1 in list1.Keys)
                foreach (Fraction fra2 in list2.Keys)
                    foreach (Operator opt in Operator.Operators)
                        for (int k = 0; k < 2; k++)
                        {
                            if (k == 1 && opt.Exchangable == true)//满足交换律只算一次
                                break;
                            if (k == 0)
                                fra = Fraction.Operate(fra1, fra2, opt);
                            else
                                fra = Fraction.Operate(fra2, fra1, opt);
                            if (fra == null) break;
                            //如果当前计算值不存在,才加入list,如果要求所有解,则是不相同就能加入list
                            if (list.ContainsKey(fra) == false)
                            {
                                //构造新的表达式加入进来
                                Expression exp = new Expression();
                                exp.Operator = opt;
                                if (k == 1)
                                {
                                    exp.LChild = list2[fra2];
                                    exp.RChild = list1[fra1];
                                }
                                else
                                {
                                    exp.LChild = list1[fra1];
                                    exp.RChild = list2[fra2];
                                }
                                list[fra] = exp;
                            }
                        }
        }
        /// 根据数组产生备忘录的索引字符串
        string GetKeyString(int[] arr)
        {
            StringBuilder sb = new StringBuilder();
            int index = 0;
            int last = arr.Length - 1;
            foreach (int num in arr)
            {
                sb.Append(num);
                if (index < last)
                    sb.Append(',');
                index++;
            }
            return sb.ToString();
        }
     
        public List<Expression> GetAllExpresses(int r)
        {
            //计算所有可能的最终结果,存入备忘录
            BuildAllPossbile(array);
            return GetAllExpresses(array, new Fraction(r));
        }
        /// 求arr拆分之后,所有可能达到r的表达式的集合
        List<Expression> GetAllExpresses(int[] arr, Fraction r)
        {
            List<Expression> list = new List<Expression>();
            string strKey = GetKeyString(arr);
            if (memo.ContainsKey(strKey) == false) return list;//查表不可达到,直接放弃搜索
            if (arr.Length == 1)
            {
                if (new Fraction(arr[0]).CompareTo(r) == 0)
                    list.Add(new Expression(arr[0]));
                return list;
            }
            //子串长度,根据组合公式C(n,r)=C(n,n-r),只搜索到一半就完成
            for (int len = 1; len <= arr.Length / 2; len++)
            {
                //将arr分成两个数组,一个长度为len,一个为arr.Legnth-len,求所有可能的组合
                int[] a1 = new int[len];
                int[] a2 = new int[arr.Length - len];
                int[] select = new int[arr.Length];//当前组合选入a1的数在arr中的下标
                for (int i = 0; i < len; i++)
                    select[i] = i;
                while (true)
                {
                    //将选择映射到两个数组
                    int c1 = 0, c2 = 0, si = 0;
                    for (int i = 0; i < arr.Length; i++)
                    {
                        if (select[si] == i)
                        {
                            a1[c1++] = arr[i];  //前半数组
                            si++;
                        }
                        else
                            a2[c2++] = arr[i];  //后半数组
                    }
     
                    CalculateEqualFra(a1, a2, list, r);
                    //调整到下一个可能 比如5选2有
                    //找下一个可调整位置
                    int t = len - 1;
                    while (t >= 0 && select[t] == arr.Length - len + t)
                        t--;
                    if (t == -1) break;
     
                    select[t]++;
                    for (int j = t + 1; j < len - 1; j++)
                        select[j] = select[j - 1] + 1;
                }
            }
            return list;
        }
     
        /// 计算a1和a2所有可能的运算结果,保存到list
        void CalculateEqualFra(int[] a1, int[] a2, List<Expression> list, Fraction r)
        {
            SortedList<Fraction, Expression> list1 = memo[GetKeyString(a1)];
            SortedList<Fraction, Expression> list2 = memo[GetKeyString(a2)];
            Fraction fra;
            foreach (Fraction fra1 in list1.Keys)
                foreach (Fraction fra2 in list2.Keys)
                    foreach (Operator opt in Operator.Operators)
                        for (int k = 0; k < 2; k++)
                        {
                            if (k == 1 && opt.Exchangable == true)//满足交换律只算一次
                                break;
                            if (k == 0)
                                fra = Fraction.Operate(fra1, fra2, opt);
                            else
                                fra = Fraction.Operate(fra2, fra1, opt);
                            if (fra == null) break;
     
                            //如果计算值等于r,则加入list
                            if (fra.CompareTo(r) == 0)
                            {
                                //构造新的表达式加入进来
                                Expression exp = new Expression();
                                //递归的求子表达式有哪些情况
                                List<Expression> l1 = GetAllExpresses(a1, fra1);
                                List<Expression> l2 = GetAllExpresses(a2, fra2);
                                exp.Operator = opt;
                                //然后组合在一起
                                foreach (Expression exp1 in l1)
                                    foreach (Expression exp2 in l2)
                                    {
                                        if (k == 1)
                                        {
                                            exp.LChild = exp2;
                                            exp.RChild = exp1;
                                        }
                                        else
                                        {
                                            exp.LChild = exp1;
                                            exp.RChild = exp2;
                                        }
     
                                        if (list.Contains(exp) == false)
                                            list.Add(exp);
                                    }
                            }
                        }
        }
    }

    四:后记

    现在编个算法题也搞这么多类,面向对象思想已经深入大脑了,因为其概念清晰,不然混在一堆,乱糟糟的。原来以为写个穷举搜索也就10分钟,但又不甘心写穷举,能优化的当然要优化,加上很久不编算法程序,花了大半个白天,逐步得出这套思路。

  • 相关阅读:
    docker 之 docker-compose 初探
    docker 之 .net core 镜像制作
    docker 之 registry私有仓库(harbor)
    ASP.NET Core 学习笔记(认证授权)
    ASP.NET Core 学习笔记(http请求处理)
    ASP.NET Core 学习笔记(依赖注入)
    Linux基础编程之网络编程TCP实例
    功能包和CMakeLists.txt
    ROS的主节点(名称服务器)---roscore
    关于ros开发
  • 原文地址:https://www.cnblogs.com/yinger/p/2096518.html
Copyright © 2011-2022 走看看