西西河

主题:【原创】将24进行到底 -- 泰让

共:💬28 🌺15 新:
全看树展主题 · 分页 下页
家园 【原创】将24进行到底

这是答应过面壁同学的c原码,因为周末在玩新买的电视,加上犯懒,所以晚了.

稍后将给出算法的解释.

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <string.h>

#define TAGET 24

#define NOP 4

#define EOP NOP*2-1

int comp[EOP];

double op[NOP];

int validate(void ){

int top=0;

double stack[NOP];

int i=0;

for (i=0;i<EOP;i++) {

if (comp[i]<=NOP) {

stack[top]=op[comp[i]-1];

top++;

} else {

switch (comp[i]-NOP) {

case 1:

top--;

stack[top-1]+=stack[top];

break;

case 2:

top--;

stack[top-1]-=stack[top];

break;

case 3:

top--;

stack[top-1]*=stack[top];

break;

case 4:

top--;

if (stack[top]==0)

return 0;

stack[top-1]/=stack[top];

break;

}

}

}

if (fabs(stack[0]-TAGET)<=1e-5)

return 1;

else

return 0;

}

void output(void ) {

int i;

int top=0;

char* stack[EOP];

char tempstr[255];

for (i=0;i<EOP;i++) {

stack[i]=malloc(255*sizeof(char));

}

for (i=0;i<EOP;i++) {

if (comp[i]<=NOP) {

sprintf(stack[top]," %d ",(int)op[comp[i]-1]);

top++;

} else {

switch (comp[i]-NOP) {

case 1:

top--;

sprintf(tempstr,"(%s+%s)",stack[top-1],stack[top]);

strcpy(stack[top-1],tempstr);

break;

case 2:

top--;

sprintf(tempstr,"(%s-%s)",stack[top-1],stack[top]);

strcpy(stack[top-1],tempstr);

break;

case 3:

top--;

sprintf(tempstr,"(%s*%s)",stack[top-1],stack[top]);

strcpy(stack[top-1],tempstr);

break;

case 4:

top--;

sprintf(tempstr,"(%s/%s)",stack[top-1],stack[top]);

strcpy(stack[top-1],tempstr);

break;

}

}

}

printf("%s\n",stack[0]);

for (i=0;i<EOP;i++) {

free(stack[i]);

}

}

int expandable(int i, int j){

int repeat=0;

int k;

if (j<=NOP) {

repeat=0;

for (k=0;k<=i-1;k++)

if (comp[k]==j) {

return 0;

}

}

int op=0;

int act=0;

for (k=0;k<=i-1;k++) {

if (comp[k]<=NOP)

op++;

else

act++;

if (op<act)

return 0;

}

return 1;

}

void expand(int i) {

int j;

if (i==EOP){

if (validate())

output();

}

else {

for (j=1;j<=NOP+4;j++) {

if (expandable(i+1,j)==1) {

comp[i]=j;

expand(i+1);

}

}

}

}

int main(int argc,char **argv) {

int a;

int i;

for (i=0;i<=(NOP-1);i++) {

scanf("%d",&a);

op[i]=a;

}

expand(0);

}


本帖一共被 1 帖 引用 (帖内工具实现)
家园 谢谢老弟的捧场。

你的程序我编译运行了一下,四张牌是没有什么问题的。但是我牌数改为6,Target改为5188,程序就似乎没有解了。你能看一下这个问题吗?

我改动了你程序中的两个常量

#define TAGET 5188

#define NOP 6

这里是程序运行结果

点看全图

外链图片需谨慎,可能会被源头改

家园 谢谢指出

错误原因在第9行

double op[4];

这个magic number 4应该改成NOP

家园 另外71行处理输出的时候漏了一对括号

sprintf(tempstr,"%s+%s",stack[top-1],stack[top]);

应该是

sprintf(tempstr,"(%s+%s)",stack[top-1],stack[top]);

家园 交换律和结合律

我这段时间也再想这个问题

算出结果不难,但是要考虑交换律和结合律还是要费点工夫

楼主的程序显然没有考虑交换律的问题,屏幕上打印出的结果实际上是同一个

家园 还是烦请老兄再添点儿注解。。。

还是烦请老兄再添点儿注解,这样应该更能领会得深一些谢谢了。

另外,我这VC编译器上只能这样才行:59行:stack[i]=(char*)malloc(255*sizeof(char));

家园 【注释之1】总体原理

首先说一下总体上的思路。其实很多朋友已经看出,如果想穷尽4张牌的所有可能的计算,需要找到一个排列方法,能够不遗漏的列举每个算式。运算的数好办,因为它们都只严格的出现1次,问题是对于运算符,简单的插入三个运算符在四个数的空隙中是不行的,因为要是我们加入括号改变运算顺序,其结果也就不同了。但是如果我们要考虑所有插入括号的方式,虽然仍然是可行,但问题就变的相当复杂了。

好在算术表达式不仅仅有常见的中缀法,还有后缀法,或者称为逆波兰法(RPN)。这种方法先写两个参加运算的部分(可以是数,或者小一极的表达式)。简单的说就是原本用1+2来表示的运算,写成rpn就是1 2+,再比如说我们用中缀表示的算式(4-4/7)*7用后缀可以表示成4 4 7 / - 7 *。注意我们这里假定运算数间有可分辨的分隔,这样4 ,4 ,7三个数不至于同四百四十七混淆。

这样表示有什么好处?第一:这么写以后可以不用括号表达所有算式,而如果我们把一个数看作一位,一个运算符也看作1位的话,这个表达式长度总是7位。第二:这样写后计算过程简单。对于有括号的四则运算的解析和运算其实是相当复杂的问题,编译原理中会有一章专门讲这个问题。对于RPN计算就简单很多,我们可以很机械的执行以下步骤:遇到运算数则压栈,如果遇到运算符则将栈顶两运算数进行相应操作。最后如果RPN没有语法错误的话,栈应该只剩下一个数,就是结果。

回到原本的问题,我们现在要做的就是生成所有可能的RPN,然后运算之,检查它是否结果是24。如果我们用1到4代表输入的4张牌点,5到8代表加减乘除四种运算的话,问题就变成了我们如何生成一个7个数的组合,满足以下条件:

1:一到四出现1次且仅出现一次

2:五到八可以重复出现,也可以不出现

3:在任何时候,一到四出现的总数,比五到八出现的次数多(这一条是保证生成的是合乎语法的RPN)

生成了一个组合后,再用上面说的计算方法来算出结果,检查下是否是目标就可以了。当然注意运算过程中别发生除零的错误。

家园 【注释之2】程序实现

有了思路后编程就不是很难的事情,下面对代码本身解释一下:

comp数组是存放组合的,op数组存放输入的四张牌点。validate函数计算已经生成的7位排列是否算出目标值,output把comp的逆波兰转化成熟悉的中缀表示法。注意这两个函数很象,只不过一个算浮点,另一个算字符串而已。

expand和expandable是生成排列的主体,其中expand用递归的办法从左向右试探comp的每一位,如果7位全部填完则调用validate和output输出结果。而expandable函数则试探在特定的位置上是否可以放置特定的数。其中第一个循环判断条件1(运算数出现最多1次),第二个循环测试条件3(从左往右扫描的时候运算数总比运算符多)。当试探成功后则在comp数组相应位置放上这个数,由于是递归,回溯可以自动进行。

程序应该可以在大多数c编译器上通过。我用的是windows 下的LCC和linux下的gcc。由于目标和牌数都定义成常量,所以容易改动,以解决更多张牌或者不同目标数的情况,也可以把它们定义成用户输入的变量。

虽然我知道比较不同语言的程序运行速度意义不大,可还是忍不住大致做了下测试。对于跑完全部4张牌的情况,在赛扬2.4G Windows XP SP2.0下大概需要9秒钟左右。同样的Binary在另一台双核1.8G笔记本上速度约7秒。当然,程序没有用到任何多线程的东西,所以在双核下没有太多的优势。

家园 泰让、highway兄请来看看

我的下门考试已经看到了曙光,所以继续学习两位老兄的Codes。

俺的程序改用了递归,输出依然还是问题。为了学习方便,两位老兄是否考虑几个验算输出。即:任意4个数,如果有解,最多有多少个解?或者说,A+B,B+A算2个解,简单的全排列一下,总共有多少个解才不会有漏掉的排列?

俺用泰让兄的程序算了一下,1 3 5 4(52个解),1 5 5 5(12个解),4 4 7 7(8个解)。

仔细考虑了一下,这个想法完全没有必要!

家园 今天晚上我把souce code给你打包发过去。

你可以自己在上面根据你的需要更改。怎么样?

家园 发我一份呀

想学习学习multi-threading部分

家园 那就贻笑大方了。

这里是多线程部分。用了一些JDK 5.0以后的new feature,所以可能看起来有些“异怪”。加了很简单一些注释,希望能有点小小帮助。

package game;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* This one has ability to scale up to multiple CPUs. It is

* subclass of plain version of PokerGame

*/

public class PokerGameMT extends PokerGame

{

//uses the latest Java concurrent feature--Thread pool

private final ExecutorService pool =

Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

private final Object OBJ = new Object();

/**

* Inner class, do real calculation job. Basically it is just another

* way to execute calculate() function defined in base class

*/

private class Runner implements Runnable

{

private int[] set;

private Map<String, Object> solutions;

private CountDownLatch latch;

Runner(int[] set, Map<String, Object> solutions, CountDownLatch latch)

{

this.set = set;

this.solutions = solutions;

this.latch = latch;

}

public void run()

{

for (Operator[] opCombo : opCombos)

{

Set<int[]> calcSeq = PokerGameMT.this.getCalcSequence(opCombo);

for (int[] seq : calcSeq)

{

String solution = PokerGameMT.this.calculate(set, opCombo, seq);

if (solution != null)

{

solutions.put(solution, OBJ);

}

}

}

latch.countDown();

}

}

/**

* Given a number of poker, find the total sulotions

*/

protected Set<String> findSolution(int[] nums)

{

/**

* use ConcurrentHashMap is for performance reason. Otherwise,

* Set<String> would be just fine.

*/

Map<String, Object> solutions = new ConcurrentHashMap<String, Object>();

Map<Integer, int[]> workingSet = new ConcurrentHashMap<Integer, int[]>();

int[] aPokeSet = new int[nums.length];

for(int[] set : this.permutations)

{

for (int i = 0; i < nums.length; i++)

{

aPokeSet[i] = nums[set[i]];

}

/**

* avoid duplication calculation. Only different set

* is sent to runner threads.

*/

int code = this.getPokeHashCode(aPokeSet);

if (!workingSet.containsKey(code))

{

int[] aCalcset = new int[nums.length];

System.arraycopy(aPokeSet, 0, aCalcset, 0, nums.length);

workingSet.put(code, aCalcset);

}

}

int totalPerm = workingSet.size();

//new feature of JDK 5.0 or after. Just like old join()

CountDownLatch latch = new CountDownLatch(totalPerm);

//submit job to runner thread

for(int[] set : workingSet.values())

{

pool.execute(new Runner(set, solutions, latch));

}

try

{

//wait for runner finish calculation

latch.await();

}catch(InterruptedException ex)

{

ex.printStackTrace();

}

return solutions.keySet();

}

/**

* tear down the thread pool

*/

protected void close()

{

super.close();

pool.shutdownNow();

}

/**

* Constructor. first one specify poke number and

* second one specify expected value, 24 for instance

* @param pokeNum

* @param answer

*/

public PokerGameMT(int pokeNum, int answer)

{

super(pokeNum, answer);

}

}

家园 哈,你们论剑的时候别忘了俺!

俺这个算法还没搞定,又是多线程,哎,谁教俺面壁是出了名的慢。不过,嘿嘿,出题权还在俺!

家园 俺的程序惨不忍睹

忙活了一个星期天,参照highway兄得输出办法,终于把输出解决了,但程序已经惨不忍睹。早知花上一个周末,还不如把逆波兰表达式吃透。不敢再玩了,就先这样吧。这个程序如果没有输出的问题,我认为这个算法还是很不错的。4个数两两相运算,变成3个数,再两两相运算,变成2个数,最后得结果。如果没有输出,适用N个数。

#include<iostream>

#include<vector>

using namespace std;

#define N 4

int z = 0,k1=0,k2=0;

vector<double> Operand(4);

vector<int> Operator(3);

void print(vector<double> A,vector<int> op,int i);

void PRINT(vector<double> A,vector<int> op,int i); // (a#b)#(c#d)

void print_op(int i)

{

switch (i)

{

case 0 : cout<<" + "; break;

case 1 : cout<<" - "; break;

case 2 : cout<<" * "; break;

case 3 : cout<<" / "; break;

case 4 : cout<<" - "; break;

case 5 : cout<<" / "; break;

}

}

inline double operation(const double x,const double y,int i)

{

double res;

switch (i)

{

case 0 : res = x + y; break;

case 1 : res = x - y; break;

case 2 : res = x * y; break;

case 3 : res = x / y; break;

case 4 : res = y - x; break;

case 5 : res = y / x; break;

}

return res;

}

inline void rechnen(vector<double> a,int n)

{

if (n==2) // Rekursion end

{

for (int i=0;i<6;i++)

{

if (operation(a[0],a[1],i)==24.0)

{

z++;

int k3=0; if (i>3) k3++;

Operator[2]=i;

print(Operand,Operator,k1+k2+k3); cout<<endl;

break; // z.B. a+b=24, a#b(b#a)!=24

}

}

}

else

{

vector<double> b(n-1);

for (int u=0;u<n;u++)

{

for (int v=u+1;v<n;v++)

{

if (n==4) { Operand[0]=a[u]; Operand[1]=a[v]; }

if (n==3) { Operand[2]=a[v-u]; Operand[3]=a[3-v+u]; }

// A, B, C, D

for (int i=0,j=1;j<n-1;i++)

{ if (i!=u&&i!=v) b[j++]=a[i]; }

// "n=4" b[1],b[2]

// "n=3" b[1]

for (int i=0;i<6;i++)

{

Operator[4-n]=i;

// op[0], op[1]

b[0]=operation(a[u],a[v],i);

if (i>3) { if (n==4) k1=4; if (n==3) k2=2; }

if (n==3&&u==1)

// (a#b)#(c#d)

{

for (int i=0;i<6;i++)

{

if (operation(b[1],b[0],i)==24.0)

{

z++;

int k3=0; if (i>3) k3++;

Operator[2]=i;

PRINT(Operand,Operator,k1+k2+k3);

cout<<endl; break;

}

}

}

else rechnen(b,n-1);

k2=0;

if (n==4) k1=0;

}

}

}

}

}

int main()

{

while (1)

{

double f; vector<double> a(N);

cout<<"give me "<<N<<" nummer, please:"<<endl;

for (int i=0;i<N&&cin>>f;i++) a[i]=f;

// for (int i=0;i<4;i++) cout<<a[i]<<' ';

rechnen(a,N);

cout<<"\nso there are "<<z<<" kinds of ideas.\n\n";

}

return 0;

}

家园 print函数

1 3 5 4 有12个结果,4 4 7 7 有4个结果, 1 5 5 5 有6个结果。

void print(vector<double> A,vector<int> op,int i)

{

if (i==4||i==5||i==6||i==7) { swap(A[0],A[1]); i=i-4; }

switch (i)

{

case 0 : cout<<"(("<<A[0]; print_op(op[0]); cout<<A[1]<<')';

print_op(op[1]); cout<<A[2]<<')';

print_op(op[2]); cout<<A[3]; break;

// ((A#B)#C)#D

case 1 : cout<<A[3]; print_op(op[2]);

cout<<"(("<<A[0]; print_op(op[0]); cout<<A[1]<<')';

print_op(op[1]); cout<<A[2]<<')'; break;

// D#((A#B)#C)

case 2 : cout<<'('<<A[2]; print_op(op[1]);

cout<<'('<<A[0]; print_op(op[0]); cout<<A[1]<<"))";

print_op(op[2]); cout<<A[3]; break;

// (C#(A#B))#D

case 3 : cout<<A[3]; print_op(op[2]);

cout<<'('<<A[2]; print_op(op[1]);

cout<<'('<<A[0]; print_op(op[0]); cout<<A[1]<<"))"; break;

// D#(C#(A#B))

}

}

void PRINT(vector<double> A,vector<int> op,int i)

{

if (i==4||i==5||i==6||i==7) swap(A[0],A[1]);

if (i==2||i==3||i==6||i==7) swap(A[2],A[3]);

if (i%2==0)

{

cout<<'('<<A[0]; print_op(op[0]); cout<<A[1]<<')';

print_op(op[2]);

cout<<'('<<A[2]; print_op(op[1]); cout<<A[3]<<')';

// (A#B)#(C#D)

}

else

{

cout<<'('<<A[2]; print_op(op[1]); cout<<A[3]<<')';

print_op(op[2]);

cout<<'('<<A[0]; print_op(op[0]); cout<<A[1]<<')';

// (C#D)#(A#B)

}

}

全看树展主题 · 分页 下页


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河