问题描述:
给玩家4张牌,每张面值1~13,采用+、-、*、/以及括号,使其最终结果为24
解答:
1.穷举法
每个数只能使用一次,所以对4个数进行全排列共有4!=24种排列;
需要3个四则运算符号:4^3=64种;
加括号方式:(A(B(CD))), ((A(BC))D), ((AB)(CD)), (A((BC)D))), (((AB)C)D) 5种
共有:4! * 4^3 * 5 = 7680种;
2.分治法
假定集合A={1,2,3,4},首先任意取出两个数,如去除1,2,A=A - {1,2},进行不同的四则运算,1+2=3,1-2=-1, 1*2=2, 1/2=0.5, 将结果再分别加入集合A,可得:
B={3,3,4}, C={-1,3,4}, D={2,3,4}, E={0.5,3,4}四个新的集合:
集合A所有可能的值 F(A) = F(B)+F(C)+F(D)+F(E) 计算规模由4个数降低为3个数
伪代码:
F(Array) { if(Array.Length<2) { if(最终结果为24) 输出表达式; else 无法构造 } for each(从数组中任意取两个数的组合) { for each(运算符(+, -, *, /)) { 1.计算该组合在此运算符下的结果; 2.将组合中的两个数从原数组中移除,并将步骤1的结果放入数组; 3.对新数组递归调用f,找到一个表达式则返回; 4.将步骤1的结果移除,并将该组合中的两个数重新放回该数组; } } }
3.采用分支限界法求解
任然假定假定集合A,首先采用分治思想,将集合A划分为A1与A-A1,分别对他们俩进行四则运算求解F(A1)和F(A-A1),最后对这两个集合里的元素进行四则运算,所得到的所有集合的并集就是F(A)
定义两个集合A,B中的元素运算如下:
Fork(A, B)={a+b, a-b. b-a, a*b, a/b(b!=0), b/a(a!=0)}
采用4位二进制数表示集合A的真子集:
1111:{a0,a1,12,a3}, 1110: {a1,a2,a3}……
首先初始化0001,0010,0100,1000,作为递归结束的条件。
1111 = {1110,0001}U……
采用数组S[i]保存集合F(i)
最终结果S[2n-1]即为集合A的所有元素通过四则运算得到的全部结果,检查S[2n-1]即可求出所得。
下面给出实现:
#include <iostream> #include <map> #include <vector> #include <set> using namespace std; int N,m; map<int,set<int>> S; set<int>& f(int i) { if (!S[i].empty()) return S[i]; for (int x=1; x<=i/2; x++) { if( (x&i) == x) { set<int>&s1 = f(i-x); set<int>&s2 = f(x); for(set<int>::iterator it1=s1.begin(); it1!=s1.end(); it1++) for(set<int>::iterator it2=s2.begin(); it2!=s2.end(); it2++) { S[i].insert(*it1 + *it2); S[i].insert(*it1 - *it2); S[i].insert(*it1 - *it2); S[i].insert(*it1 * *it2); if(*it2!=0) S[i].insert(*it1 / *it2); if(*it1!=0) S[i].insert(*it2 / *it1); } } } return S[i]; } int main() { int a[4]; N=4; int i; for(i=0; i<N;i++) { cin>>a[i]; S[1<<i].insert(a[i]); } int num = (1<<N) - 1; f(num); int c = S[num].count(24); cout<<"count: "<<c<<endl; }
这是小菜我自己写的,只能输出能与不能。参考网上别的大牛写的,觉得好生惭愧,推荐下面这个,上面这个各位看官就当反面典型看吧~
#include <iostream> #include <set> #include <string> #include <cmath> using namespace std; #define N 4 // 4张牌,可变 #define RES 24 // 运算结果为24,可变 #define EPS 1e-6 struct Elem { Elem(double r, string& i):res(r),info(i){} Elem(double r, char* i):res(r),info(i){} double res; // 运算出的数据 string info; // 运算的过程 bool operator<(const Elem& e) const { return res < e.res; // 在set的红黑树插入操作中需要用到比较操作 } }; int A[N]; // 记录N个数据 //用二进制数来表示集合和子集的概念,0110表示集合包含第2,3个数 set<Elem> vset[1<<N]; // 包含4个元素的集合共有16个子集0-15 set<Elem>& Fork(int m) { // memo递归 if (vset[m].size()) { return vset[m]; } for (int i=1; i<=m/2; i++)//因为分别计算Fork(i)与Fork(m-i),所以计算一半就行了 if ((i&m) == i) { set<Elem>& s1 = Fork(i); set<Elem>& s2 = Fork(m-i); // 得到两个子集合的笛卡尔积,并对结果集合的元素对进行6种运算 for (set<Elem>::iterator cit1=s1.begin(); cit1!=s1.end(); cit1++) for (set<Elem>::iterator cit2=s2.begin(); cit2!=s2.end(); cit2++) { string str; str = "("+cit1->info+"+"+cit2->info+")"; vset[m].insert(Elem(cit1->res+cit2->res,str)); str = "("+cit1->info+"-"+cit2->info+")"; vset[m].insert(Elem(cit1->res-cit2->res,str)); str = "("+cit2->info+"-"+cit1->info+")";; vset[m].insert(Elem(cit2->res-cit1->res,str)); str = "("+cit1->info+"*"+cit2->info+")"; vset[m].insert(Elem(cit1->res*cit2->res,str)); if (abs(cit2->res)>EPS) { str = "("+cit1->info+"/"+cit2->info+")"; vset[m].insert(Elem(cit1->res/cit2->res,str)); } if (abs(cit1->res)>EPS) { str = "("+cit2->info+"/"+cit1->info+")"; vset[m].insert(Elem(cit2->res/cit1->res,str)); } } } return vset[m]; //这一步一定不要忘了 } int main() { int i; for (i=0; i<N; i++) cin >> A[i]; // 递归的结束条件; for (i=0; i<N; i++) { char str[10]; sprintf(str,"%d",A[i]); vset[1<<i].insert(Elem(A[i],str)); } Fork((1<<N)-1);//开始1111 表示四个数; // 显示算出24点的运算过程; for (set<Elem>::iterator it=vset[(1<<N)-1].begin(); it!=vset[(1<<N)-1].end(); it++) { if (abs(it->res-RES) < EPS) cout << it->info << endl; } }