今天在大本营看到了一个人家写的24程序,感觉人家的思路非常清晰,于是自己也着手写了一个,顺便温习了一下标准c++。在我的程序中,使用了 stringstream类做parse,后缀表达式穷举所有可行解,而用中缀表达式计算用户输入的表达式。因为比较懒,当用户算不出来时,按出来的答案是个后缀表达式。
===============================================
4.21更新
1.发现对后缀表达式的理解错了。修改了穷举算24的函数,穷举空间改为4!*4^3 * 5,其中3个操作符在长度为7的后缀表达式的可能位置为5种。
2.使用二叉树和堆栈实现了后缀表达式向中缀的转化,可以给出算24的答案
3.加入了接受用户输入的4个数,也可以让系统产生四个数。
功能上已经比较完整了,用户界面上还不是很好,不过这个已经不是很重要的问题的。这部分代码,如果要做
图形界面,应该是很好移过去的。
有关源代码如下:
//Token.h :用于Parse,以及操作符,优先级的定义
#ifndef TOKEN_H
#define TOKEN_H
enum Token_type {Numeric = 0,Op};
enum Operator{ADD_OPR = 0,MINUS_OPR,
MUL_OPR,DIV_OPR,
LEFT_BRA,RIGHT_BRA,
TER};
enum PRI{HIGHER = 0,LOWER,EQUAL,NO_POSSIBLE};
class Token
{
public:
Token(){}
Token(Token_type _type,double _x,Operator _op):type(_type),
x(_x),op(_op){}
Token_type type;
double x;
Operator op;
};
void Parse(string expression,vector<Token>& tokens);
bool isOp(char c);
#endif
//Token.cpp
#include <string>
#include <vector>
#include <sstream>
using namespace std;
#include "Token.h"
extern int OpTypeNum;
char operators[7] = {'+','-','*','/','(',')','#'};
bool isOp(char c,int &index)
{
for (int i = 0;i < OpTypeNum;i++)
{
if (c == operators[i])
{
index = i;
return true;
}
}
return false;
}
void Parse(string expression,vector<Token>& tokens)
{
stringstream ss (stringstream::in | stringstream::out);
ss << expression;
char c;
int val,index;
while (ss >> c)
{
if (isdigit(c))
{
ss.putback(c);
ss >> val;
tokens.push_back(Token(Numeric,val,Operator(0)));
}else if (isOp(c,index))
tokens.push_back(Token(Op,-1,Operator(index)));
}
}
//ExpCalc.h 用堆栈实现的中缀和后缀表达式的计算
#ifndef EXPCALC_H
#define EXPCALC_H
class tree_Node
{
public:
tree_Node(){}
~tree_Node();
void Print();
tree_Node(tree_Node * _left,tree_Node * _right,Token _token):
left(_left),right(_right),token(_token){}
tree_Node * left;
tree_Node * right;
Token token;
};
class ExpCalc
{
public:
void ShowInfixExp(vector<Token>& tokens);
bool PostfixCalc(vector<Token> & tokens,double & res);
bool infixCalc(vector<Token>& tokens,double& res);
bool Calc(double x1,double x2,Operator op,double & res);
void Clear();
private:
bool doWhenHigher(Operator op);
bool doWhenLower(Operator op,Operator nxt_op);
bool doWhenEqual();
stack<double> operands_stack;
stack<Operator> operators_stack;
};
#endif
//ExpCalc.cpp
#include <stack>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
#include "Token.h"
#include "ExpCalc.h"
#include "24Game.h"
int OpTypeNum = 7;
PRI operatorPRIs[7][7] ={{HIGHER,HIGHER,LOWER,LOWER,LOWER,HIGHER,HIGHER},
{HIGHER,HIGHER,LOWER,LOWER,LOWER,HIGHER,HIGHER},
{HIGHER,HIGHER,HIGHER,HIGHER,LOWER,HIGHER,HIGHER},
{HIGHER,HIGHER,HIGHER,HIGHER,LOWER,HIGHER,HIGHER},
{LOWER,LOWER,LOWER,LOWER,LOWER,EQUAL,NO_POSSIBLE},
{NO_POSSIBLE,NO_POSSIBLE,NO_POSSIBLE,NO_POSSIBLE,\
NO_POSSIBLE,NO_POSSIBLE,NO_POSSIBLE},
{LOWER,LOWER,LOWER,LOWER,LOWER,NO_POSSIBLE,EQUAL}};
extern char operators[7];
bool ExpCalc::Calc(double x1,double x2,Operator op,double& res)
{
bool flag = true;
switch(op)
{
case ADD_OPR:
res = x1 + x2;
break;
case MINUS_OPR:
res = x1 - x2;
break;
case MUL_OPR:
res = x1 * x2;
break;
case DIV_OPR:
if (fabs(x2) < 1e-6)
flag = false;
else
res = x1 / x2;
break;
default:
flag = false;
}
return flag;
}
bool ExpCalc::PostfixCalc(vector<Token> &tokens,double& res)
{
Clear();
for (int i = 0;i<tokens.size();i++)
{
if (tokens[i].type == Numeric)
operands_stack.push(tokens[i].x);
else
{
if (operands_stack.size()>=2)
{
double x2 = operands_stack.top();
operands_stack.pop();
double x1 = operands_stack.top();
operands_stack.pop();
double r;
bool isOk = Calc(x1,x2,tokens[i].op,r);
if (!isOk) return false;
operands_stack.push(r);
}else{
return false;
}
}
}
if (operands_stack.size()!=1)
return false;
res = operands_stack.top();
return true;
}
void ExpCalc::Clear()
{
while (!operands_stack.empty())
operands_stack.pop();
while (!operators_stack.empty())
operators_stack.pop();
}
bool ExpCalc::doWhenHigher(Operator op)
{
if (operands_stack.size()<2)
return false;
double x2 = operands_stack.top();
operands_stack.pop();
double x1 = operands_stack.top();
operands_stack.pop();
double res;
bool isOk = Calc(x1,x2,op,res);
if (!isOk) return false;
operands_stack.push(res);
operators_stack.pop();
return true;
}
bool ExpCalc::doWhenLower(Operator op,Operator nxt_op)
{
operators_stack.push(nxt_op);
return true;
}
bool ExpCalc::doWhenEqual()
{
if (operators_stack.empty())
return false;
operators_stack.pop();
return true;
}
bool ExpCalc::infixCalc(vector<Token>& tokens,double& res)
{
Clear();
operators_stack.push(TER);
double x1,x2;
bool isOk;
for (int i = 0;i<tokens.size();i++)
{
if (tokens[i].type == Numeric)
{
operands_stack.push(tokens[i].x);
}else{
if (operators_stack.empty())
{
return false;
}
Operator nxt_op = tokens[i].op;
bool over = false;
while (!operators_stack.empty())
{
Operator op = operators_stack.top();
PRI pri = operatorPRIs[int(op)][int(nxt_op)];
switch(pri)
{
case HIGHER:
isOk = doWhenHigher(op);
break;
case LOWER:
isOk = doWhenLower(op,nxt_op);
over = true;
break;
case EQUAL:
isOk = doWhenEqual();
over = true;
break;
case NO_POSSIBLE:
return false;
default:
return false;
}
if (!isOk)
return false;
if(over)
break;
}
}
}
if (!operators_stack.empty() || operands_stack.size()!=1)
return false;
res = operands_stack.top();
return true;
}
void ExpCalc::ShowInfixExp(vector<Token>& tokens)
{
stack<tree_Node *> tn_stack;
tree_Node * root = NULL;
for (int i = 0;i<tokens.size();i++)
{
if (tokens[i].type == Numeric)
{
tn_stack.push(new tree_Node(NULL,NULL,tokens[i]));
}else{
if (tn_stack.size()<2)
{
cout << "后缀表达式有错!"<<endl;
return;
}
tree_Node * x2 = tn_stack.top();
tn_stack.pop();
tree_Node * x1 = tn_stack.top();
tn_stack.pop();
double res;
bool isOk = Calc(x1->token.x,x2->token.x,tokens[i].op,res);
if (!isOk)
{
cout << "计算过程有误"<<endl;
return;
}
root = new tree_Node(x1,x2,Token(Numeric,res,tokens[i].op));
tn_stack.push(root);
}
}
if (root == NULL)
{
cout << "后缀表达式有误"<<endl;
return;
}
root->Print();
cout << endl;
delete root;
}
void tree_Node::Print()
{
if (left == NULL && right == NULL)
{
cout << token.x << " ";
return;
}
cout << "(";
if (left!=NULL)
left->Print();
cout << operators[token.op] << " ";
if (right!=NULL)
right->Print();
cout << ")";
}
tree_Node::~tree_Node()
{
if (left!=NULL)
{
delete left;
left = NULL;
}
if (right!=NULL)
{
delete right;
right = NULL;
}
}
//24Game.h 算24游戏的有关逻辑
#ifndef GAME_H
#define GAME_H
class Game
{
public:
Game();
void GenNewNumbers();
bool hasSolutions();
void ShowNums();
void ShowSolution();
bool CheckInput(vector<int>& inputs);
bool Calc(vector<Token>& tokens,double & res);
bool Check(vector<Token>& tokens);
static int minOperand;
static int maxOperand;
private:
vector<Token> solu_tokens;
bool hasSolu;
string expression;
ExpCalc calc;//表达式计算器
int generated_operands[4];
int input_operands[4];
char input_operators[3];
};
#endif
//24Game.cpp
#include <stack>
#include <time.h>
#include <stdlib.h>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
#include "Token.h"
#include "ExpCalc.h"
#include "24Game.h"
int Game::minOperand = 1;
int Game::maxOperand = 13;
extern char operators[7];
Game::Game()
{
srand(time(NULL));
}
bool Game::CheckInput(vector<int>& inputs)
{
for (int i = 0;i<4;i++)
{
generated_operands[i] = inputs[i];
if (generated_operands[i] > maxOperand ||
generated_operands[i] < minOperand)
{
return false;
}
}
return true;
}
void Game::GenNewNumbers()
{
while (1)
{
for (int i = 0;i<4;i++)
generated_operands[i] = rand()%(maxOperand-minOperand+1) + minOperand;
if (hasSolutions()) break;
}
//generated_operands[0] = 4;
//generated_operands[1] = 8;
//generated_operands[2] = 4;
//generated_operands[3] = 9;
}
//若有解,只保存一组先
bool Game::hasSolutions()
{
vector<double> a(4);//操作数
double res;
copy(generated_operands,generated_operands+4,a.begin());
sort(a.begin(),a.end());
int operandPos[5][4] = {{0,1,2,3},{0,1,2,4},{0,1,2,5},{0,1,3,4},{0,1,3,5}};
int opPos[5][3] = {{4,5,6},{3,5,6},{3,4,6},{2,5,6},{2,4,6}};
vector<Token> tokens(7);
while (next_permutation(a.begin(),a.end()))
{
for (int p = 0;p<5;p++)
{
for (int idx = 0;idx<4;idx++)
{
tokens[operandPos[p][idx]].type = Numeric;
tokens[operandPos[p][idx]].x = a[idx];
}
for (int idx = 0;idx<3;idx++)
tokens[opPos[p][idx]].type = Op;
for (int i = ADD_OPR;i<=DIV_OPR;i++)
{
for (int j = ADD_OPR;j<=DIV_OPR;j++)
{
for (int k = ADD_OPR;k<=DIV_OPR;k++)
{
tokens[opPos[p][0]].op = Operator(i);
tokens[opPos[p][1]].op = Operator(j);
tokens[opPos[p][2]].op = Operator(k);
bool isOk = calc.PostfixCalc(tokens,res);
if (isOk && fabs(res - 24) < 1e-6)
{
solu_tokens = tokens;
hasSolu = true;
return true;
}
}
}
}
}
}
hasSolu = false;
return false;
}
void Game::ShowNums()
{
for (int i = 0;i<4;i++)
cout << generated_operands[i] << " ";
cout << endl;
}
void Game::ShowSolution()
{
calc.ShowInfixExp(solu_tokens);
cout << endl;
}
bool Game::Calc(vector<Token>& tokens,double & res)
{
return calc.infixCalc(tokens,res);
}
bool Game::Check(vector<Token>& tokens)
{
vector<double> a;
for (int i = 0;i<tokens.size();i++)
{
if (tokens[i].type == Numeric)
a.push_back(tokens[i].x);
}
sort(a.begin(),a.end());
vector<double> opr_copy(4);
copy(generated_operands,generated_operands+4,opr_copy.begin());
sort(opr_copy.begin(),opr_copy.end());
if (opr_copy.size()!=a.size())
{
return false;
}
for (int i = 0;i<a.size();i++)
{
if (a[i]!=opr_copy[i])
{
return false;
}
}
return true;
}
//24Points:主文件
#include <string>
#include <iostream>
#include <vector>
#include <stack>
#include <cmath>
using namespace std;
#include "Token.h"
#include "ExpCalc.h"
#include "24Game.h"
int main()
{
Game game;
double res;
while (1)
{
cout << "开始本轮算24游戏(输入g:系统随机产生测试数,输入s:用户自行输入4个要测试的数)"<<endl;
char c;
cin >> c;
if (c == 'g')
{
game.GenNewNumbers();
game.ShowNums();
}else if(c == 's')
{
vector<int> inputs(4);
cout << "输入四个1到13的整数,以空格隔开:";
for (int i = 0;i<4;i++)
cin >> inputs[i];
bool isOk = game.CheckInput(inputs);
if (!isOk){
cout << "输入的数应在1到13之间"<<endl;
continue;
}
if(!game.hasSolutions())
{
cout << "您的输入无解"<<endl;
continue;
}
}else
{
cout << "无效命令"<<endl;
continue;
}
cin.ignore();//吃掉回车键
while(1)
{
cout << "输入你的表达式:以#结尾,只输入#将显示答案,案例:(4*9 -4-8)"<<endl;
string expression;
getline(cin,expression);
if (expression == "#")
{
game.ShowSolution();
break;
}
vector<Token> tokens;
Parse(expression,tokens);
if (!game.Check(tokens))
{
cout << "输入的数字不对"<<endl;
continue;
}
bool isOk = game.Calc(tokens,res);
if (!isOk)
cout << "输入格式有误" <<endl;
else if (fabs(res - 24) < 1e-6 )
cout <<"您算对了"<<endl;
else
cout << "您算错了"<<endl;
}
}
return 0;
}
截图