编程之美: 第一章 1.16 24点游戏

/*
24点游戏:
给玩家4张牌,每张牌的面值在1~13之间,允许其中有数值相同的牌。采用加减乘除,允许中间运算存在小数,并且可以使用括号,但每张牌只能使用一次,尝试
构造表达式,使其运算结果为24.

输入:n1,n2,n3,n4
输出:若能得到运算结果为24,输出对应表达式

输入:
11,8,3,5
输出:
(11-8)*(3+5)=24

解法1:
穷举法,运算符号4种,每个数字使用一次

每个数只能使用一次,对4个数进行全排列,共有4!=24种,4个数的四则运算需要3个运算符,共有64种,总有有24*64 = 1536种
再考虑加括号情况:4个数加括号共有5种情形
(A (B (CD))),(A ((BC) D)),((AB) (CD)),( ( A (BC) ) D ),(((AB)C)D)
共有24 * 64 * 5 = 7680种,采用逆波兰表达式,总数不变。

递归解法:
将给定的4个数放入数组Array中,将其作为参数穿入函数f中,
f(Array)
{
  if(Array.length < 2)
  {
    if(得到的最终结果为24)
	  输出表达式
	else
	  输出无法构造符合要求的表达式
  }
  foreach(从数组中任取两个数的组合)
  {
    foreach(运算符(+,-,*,/))
	{
	  1计算该组合在此运算符下的结果
	  2将该组合中的两个数从原数组中移除,并按步骤1的计算结果放入数组
	  3对新数组调用f,找到表达式则返回
	  4将步骤1的结果移除,并将该组合中的两个数重新放回到数组中对应的位置
	}
  }
}

解法2:
定义要计算的初始数据,放于集合A中,定义函数f(A)为对集合A中的元素进行所有可能的四则运算所得到的值,采用分治思想,先将A划分为两个子集A1和A-A1,
其中A1为A的非空真自己,分别计算A1和A-A1中的元素进行四则运算得到的结果集合,即f(A1)和f(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)},(a,b)属于A*B,假设A1中有n个元素,A2中有m个元素,那么将有n*m个(a,b),而每对值需要进行6个计算,
Fork(A1,A2) = 2*n*m个元素,需要去重。假设集合A中有n个元素,那么集合A的所有非空真子集个数为2^n-2,则f(A)第一层递推式中共有(2^n-2)/2个Fork函数

可以用二进制数来表示集合和子集,由于只有4个元素,可以采用4位的二进制数来表示集合A及其真子集,设A{a0,a1,a2,a3},当且仅当ai在某一个真子集中时,
该真子集所代表的二进制数对应的第i位才为1,如A1 = {a1,a2,a3}则1110表示A1,若A2 = {a0,a3},那么1001表示A2,所以A的真子集范围为1到14(1到2^n-2),
再用一个大小为2^n-1的数组S来保存f(i)(1<=i<=15),数组S中的每一个元素S[i]都是一个集合(f(i)),其中S[2^n-1]即为集合A中的所有元素通过四则运算和加括号得到
的全部结果,通过检查S[2^n-1],可得知某个输入是否有解。

24Game(Array)//Array为初始输入集合
{
  for(int i = 1 ; i <= 2^n-1 ; i++)
  {
    S[i] = 空集;//初始化将S中的各个集合置为空集,n为集合Array的元素个数,在24点中级为4,
  }
  for(int i = 1 ; i < n ; i++)
  {
    S[2^i] = {ai};//先对每个只有一个元素的真子集赋值,即为该元素本身
  }
  for(int i = 1 ;i <= 2^n - 1 ; i++)//对每个i都代表着Array的一个真子集
  {
    S[i] = f(i);//
  }
  Check(S[2^n-1]);//检查S[2^n-1]中是否有值为24的元素,并返回
}

f(int i)//i的二进制表示可代表着集合的一个真子集
{
  if(S[i] != 空集)
  {
    return S[i];
  }
  for(int x = 1 ; x < i ; i++)//只有小于i的x才可能称为i的真子集
  {
    if((x & i) == x)//&为与运算,只有当x & i == x成立时,x才为i的子集,此时i-x为i的另一个真子集,x与i-x共同构成i的一个划分
	{
	  S[i] U= Fork(f(x),f(i-x));//U为集合的bing yunsuan ,Fork的过程中,去除重复中间结果
	}
  }
}
*/
#include <stdio.h>
#include <math.h>
#include <string>
#include <iostream>
#include <stdlib.h>

using namespace std;

const double Threshould = 1E-6;//浮点数的误差值
const int CardsNumber = 4;
const int ResultValue = 24;
double number[CardsNumber];
string result[CardsNumber];



bool dot(int n)
{
	if(n == 1)//递归出口
	{
		if(fabs(number[0] - ResultValue) < Threshould)
		{
			cout << result[0] <<endl;
		}
		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 = result[i];
			expb = result[j];
			result[j] = result[n-1];//?

			result[i] = '(' + expa + '+' + expb + ')';
			number[i] = a + b;
			if(dot(n-1))
			{
				return true;
			}

			result[i] = '(' + expa + '-' + expb + ')';
			number[i] = a - b;
			if(dot(n-1))
			{
				return true;
			}

			result[i] = '(' + expa + '-'+ expb + ')';
			number[i] = b - a ;
			if(dot(n-1))
			{
				return true;
			}

			result[i] = '(' + expa + '*'+ expb + ')';
			number[i] = a * b;
			if(dot(n-1))
			{
				return true;
			}

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


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

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

void process()
{
	int x;
	for(int i = 0 ; i < CardsNumber ; i++)
	{
		char buffer[20];
		cout << "the" << i << "th number:";
		cin >> x;
		number[i] = x;
		itoa(x,buffer,10);
		result[i] = buffer;
	}
	if(dot(CardsNumber))
	{
		cout<< "成功了" <<endl;
	}
	else
	{
		cout<< "失败了" << endl;
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	system("pause");
	return 0;
}

你可能感兴趣的:(递归,编程之美)