每日一题(13)——24点 (分治&递归)

问题描述:

    给玩家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)

 

每日一题(13)——24点 (分治&递归)_第1张图片

 

定义两个集合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;
	}
}


 

 

 

 

 

 

 

你可能感兴趣的:(每日一题(13)——24点 (分治&递归))