目录
一、实验背景
离散数学前几课时课时便是关于命题逻辑的学习,谈到命题逻辑的各种指派与对应值,比较详细且直观的便是真值表表示,那如果现在给出一个命题公式,我们该如何通过编程的方式实现一个能够自动生成给出命题公式所对应的真值表呢?离散数学课便设计了实验让我们探索如何实现此功能,在加深对命题公式理解的同时提高编程能力,培养编程思维。
二、实验目的
1、掌握基本联结词的真值表。
2、掌握命题公式真值表的计算方法。
3、设计逆波兰表达式求值的合理数据结构及方法,并使用C++实现。
4、设计生成命题公式真值表的合理数据结构及方法,并使用C++实现。
三、实验内容
PropositionalFormular类:
成员变量:
m_strPropFmlr - 命题公式
m_strRevertPolishNotation - 命题公式对应的逆波兰表达式
m_OperatorStack - 用来存储联结词的栈
m_PriorityMap - 静态成员。这是一个<char, int>的map,用联结词作为key,用联结词的优先级做为value。value值越大,表示优先级越高。
m_OperandStack - 用于存储操作数的栈。该栈在解析逆波兰式时暂存操作数(即命题变元和常元)
m_vecTableTitle - 真值表表头。假设向量中有n个元素,前n-1个为命题变元,第n个元素为命题公式。例如,对于公式P&Q>R,该向量有4个元素,依次为P、Q、R、P&Q>R。注意:命题公式中可能存在命题常元T和F,注意特殊处理。
另外,应该意识到:m_vecTableTitle.size() - 1 = 公式中命题变元的数量
这里规定命题变元的数量不超过16个
成员函数:
Convert2RPN() - 将命题公式m_strPropFmlr转换为逆波兰式,请注意将得到的逆波兰式存储在m_strRevertPolishNotation中。
PropositionalFormular() - 默认构造函数。
~PropositionalFormular() - 析构函数。
PropositionalFormular(string formular) - 构造函数。该构造函数将m_strPropFmlr初始化为formular,并调用Convert2RPN()将命题转换为逆波兰式
FillTableTitle() - 解析命题公式m_strPropFmlr,生成真值表表头m_vecTableTitle。例如,对于命题公式 P&Q>T,由于T是命题常元,所以该命题公式中有两个变元,所以解析后m_vecTableTitle中应该有三个元素:P、Q、P&Q>T
EvalRPN(map<char, bool> varvals) - 计算并返回逆波兰表达式的值。其中varvals存储的是命题公式的一种指派。例如,对于命题公式 P&Q>T,其中有两个命题变元,当P = false,Q = true,则varvals['P'] = false,varvals['Q'] = true
GenerateTrueValueTable() - 生成并输出命题公式的真值表。以P&Q>T为例,要求输出格式如下:
P Q P&Q>T
0 0 1
1 0 1
0 1 1
1 1 1
注意:各列之间以制表符('\t')分隔。
提示:真值表的每一行的前n-1列实际是该公式的一种指派。而每一行的最后一列对应一个逆波兰式的值,这个值是用EvalRPN(map<char, bool> varvals)来求。参数varvals中存储的就是这个指派。
ClearOperatorStack() - 清空运算符栈。在把中缀表达式转为逆波兰式之前调用。
ClearOperandStack() - 清空操作数栈。在对逆波兰表达式求值之前调用。
CalcConjunction(bool left, bool right) - 返回left与right的合取
CalcCompatileDisjunction(bool left, bool right) - 返回left与right的析取
CalcNegation(bool right) - 返回right的否定
CalcImplication(bool left, bool right) - 返回left->right的真值
关于命题公式和命题联结词的说明:
1、每个命题变元由一个大写字母表示;
2、为简化问题,本实验只考虑否定、合取、析取、条件四种联结词。
3、每种联结词用一个字符表示,分别是:否定('!')、合取('&')、析取('|')、条件('>')。
4、命题公式中可能存在括号。具体实例可见主函数。
5、命题公式中可能存在命题常元:T和F,注意特殊处理。
6、这里规定命题变元的数量不超过16个。
注意:所有字符均为半角字符(英文输入法)。
四、相关知识
逆波兰表达式求值算法
首先需要设置一个暂存操作数的栈(下文简称为栈),将其初始化为空。然后从左到右,对逆波兰表达式进行逐字符处理如下:
(1)若当前字符为操作数,则直接将其真值入栈;
(2)若当前字符为命题联结词,则根据不同的联结词,取出栈顶的1(否定联结词)或2(合取、析取、条件联结词)个操作数与联结词结合,求值后,将结果入栈。
当逆波兰式扫描结束后,栈中只有一个元素,该元素即为表达式的值。
下面以( 逆波兰式为 )为例,对求值过程图解如下
五、实验代码
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <iostream>
#include <bitset>
#include <algorithm>
#include <cmath>
#include <utility>
using namespace std;
string ops="&|>()!";
class PropositionalFormular
{
private:
string m_strPropFmlr;
string m_strRevertPolishNotation;
stack<char> m_OperatorStack;
static map<char, int> m_PriorityMap;
stack<char> m_OperandStack;
vector<string> m_vecTableTitle;
vector<map<char,bool>> assign;
private:
//void Convert2RPN();
void Convert2RPN(string formular);
void FillTableTitle();
public:
bool EvalRPN(map<char, bool> varvals);
public:
PropositionalFormular();
~PropositionalFormular() {}
PropositionalFormular(string formular);
void PrintPriority();
void PrintRPN();
void ClearOperatorStack();//清空运算符栈。在把中缀表达式转为逆波兰式之前调用。
void ClearOperandStack();//清空操作数栈。在对逆波兰表达式求值之前调用。
bool CalcConjunction(bool left, bool right);
bool CalcCompatileDisjunction(bool left, bool right);
bool CalcNegation(bool right);
bool CalcImplication(bool left, bool right);
void GenerateTrueValueTable();
string RPN(string formular);
void generateAssignments(unsigned int n, vector<map<char, bool>>& assignments);
};
PropositionalFormular::PropositionalFormular()//默认构造函数的实现
{
m_strPropFmlr = "";
m_strRevertPolishNotation = "";
//Convert2RPN();
}
PropositionalFormular::PropositionalFormular(string formular)//含参构造函数的实现
{
m_strPropFmlr = formular;
Convert2RPN(formular);
}
void PropositionalFormular::PrintPriority()//
{
map<char, int>::iterator it;
it = m_PriorityMap.begin();
for (; it != m_PriorityMap.end(); it++)
cout << it->first << '\t' << it->second << endl;
}
void PropositionalFormular::ClearOperatorStack()//清空运算符栈。在把中缀表达式转为逆波兰式之前调用。
{
while(!m_OperatorStack.size())
{
m_OperatorStack.pop();
}
}
void PropositionalFormular::ClearOperandStack()//清空操作数栈。在对逆波兰表达式求值之前调用。
{
while(!m_OperandStack.size())
{
m_OperandStack.pop();
}
}
bool PropositionalFormular::CalcConjunction(bool left, bool right)
{
return (left&&right)?true:false;
}
bool PropositionalFormular::CalcCompatileDisjunction(bool left, bool right)
{
return (left||right)?true:false;
}
bool PropositionalFormular::CalcNegation(bool right)
{
return !right;
}
bool PropositionalFormular::CalcImplication(bool left, bool right)
{
return (left&&!right)?false:true;
}
void PropositionalFormular::PrintRPN()
{
cout << m_strRevertPolishNotation <<endl;
}
map<char, int> PropositionalFormular::m_PriorityMap = map<char, int>({ { '!', 99 },{ '&', 98 },{ '|', 97 },{ '>', 96 } });
string PropositionalFormular::RPN(string formular)
{
ClearOperatorStack();
stack<char> str;//辅助栈
stack<char> m_OperatorStack;
string result;
for (unsigned int i = 0; i < formular.size(); i ++)
{
if (ops.find(formular[i]) == ops.npos) // 扫描到的是操作数
{
str.push(formular[i]);
}
else // 扫描到的是操作符,现将累加的操作数字符串加入
{
char cur_op = formular[i];
if (m_OperatorStack.empty()) // 栈为空,直接入栈
{
m_OperatorStack.push(cur_op);
}
else if (cur_op == '(') // 当前操作数为左括号,直接入栈
{
m_OperatorStack.push(cur_op);
}
else if (cur_op == ')') // 当前操作数为右括号,则需要将m_OperatorStack中直到左括号前的所有的元素弹出
{
while (m_OperatorStack.top() != '(')
{
str.push(m_OperatorStack.top());
m_OperatorStack.pop();
}
m_OperatorStack.pop(); // 将左括号弹出
}
else if (m_OperatorStack.top() == '(') // 在当前操作符不是括号的情况下,如果栈顶元素为左括号,则直接入栈
{
m_OperatorStack.push(cur_op);
}
else if (m_PriorityMap[cur_op] > m_PriorityMap[m_OperatorStack.top()]) // 在当前操作符和栈顶元素为+-*/的情况下,若当前操作符优先级大于栈顶元素,直接入栈
{
m_OperatorStack.push(cur_op);
}
else // 最后一种情况就是当前操作符的优先级低于或等于栈顶元素优先级时
{
while ((m_OperatorStack.top() != '(') && (m_PriorityMap[m_OperatorStack.top()] >= m_PriorityMap[cur_op]))
{
str.push(m_OperatorStack.top());
m_OperatorStack.pop();
// 若栈已空,则直接返回
if (m_OperatorStack.empty())
{
break;
}
}
m_OperatorStack.push(cur_op); // 符合要求的操作符弹出后,当前操作符入栈
}
}
}
// 最后m_OperatorStack可能还会有剩余元素,全部弹出
while(!m_OperatorStack.empty())
{
str.push(m_OperatorStack.top());
m_OperatorStack.pop();
}
while(!str.empty())
{
result += str.top();
str.pop();
}
reverse(result.begin(),result.end());
return result;
}
void PropositionalFormular::Convert2RPN(string formular)
{
m_strRevertPolishNotation=RPN(formular);
}
bool PropositionalFormular::EvalRPN(map<char, bool> varvals)
{
ClearOperandStack();
stack<bool> m_OperandStack;
int len = m_strRevertPolishNotation.size();
int i = 0;
while(i < len)
{ //对后缀表达式求值
if(m_strRevertPolishNotation[i] != '&' && m_strRevertPolishNotation[i] != '|' && m_strRevertPolishNotation[i] != '>'&& m_strRevertPolishNotation[i] != '!')
{
bool tmp = true;
if(m_strRevertPolishNotation[i] == 'T')
{
tmp = true;
}
else if(m_strRevertPolishNotation[i] == 'F')
{
tmp = false;
}
else
{
tmp = varvals.find(m_strRevertPolishNotation[i])->second; //操作数(在map里找key与当前操作数相同的value值)
}
m_OperandStack.push(tmp); //将操作数压入栈中
i++;
}
else if(m_strRevertPolishNotation[i] == '!')
{
bool opm_OperandStack = m_OperandStack.top();
m_OperandStack.pop();
bool res = CalcNegation(opm_OperandStack);
m_OperandStack.push(res);
i++;
}
else//每遇到一个操作符,就将两个操作数出栈,运算完后将结果压栈
{
bool opm_OperandStack1 = m_OperandStack.top();
m_OperandStack.pop();
bool opm_OperandStack2 = m_OperandStack.top();
m_OperandStack.pop();
bool res = true; //运算结果
char op= m_strRevertPolishNotation[i]; //字符串的第一个字符(实际上也只有一个字符)
switch(op)
{ //注意两个操作数的顺序,栈顶元素作为第二个操作数
case '&':
res = CalcConjunction(opm_OperandStack2,opm_OperandStack1);
break;
case '|':
res = CalcCompatileDisjunction(opm_OperandStack2,opm_OperandStack1);
break;
case '>':
res = CalcImplication(opm_OperandStack2,opm_OperandStack1);
break;
default:
break;
}
m_OperandStack.push(res);
i++;
}
}
return m_OperandStack.top(); //最后栈中必定只剩下一个数,就是运算结果
}
void PropositionalFormular::FillTableTitle()
{
m_vecTableTitle.clear();
for(char ch:m_strPropFmlr)
{
if(ch>='A'&&ch<='Z'&&ch!='T'&&ch!='F')
{
string sss="";
sss+=ch;
m_vecTableTitle.emplace_back(sss);
}
else
{
continue;
}
}
m_vecTableTitle.emplace_back(m_strPropFmlr);
}
void PropositionalFormular::generateAssignments(unsigned int n, vector<map<char,bool>>& assignments)
{
for (int i = 0; i < pow(2, n); i++)
{
map<char, bool> assignment;
for (unsigned int j = 0; j < n; j++)
{
assignment.insert(pair<char, bool>(m_vecTableTitle[j][0],(i & (1 << j)) > 0));
}
assignments.push_back(assignment);
}
}
void PropositionalFormular::GenerateTrueValueTable()
{
FillTableTitle();
for(auto it:m_vecTableTitle)
{
cout<<it<<"\t";
}
cout<<"\n";
generateAssignments(m_vecTableTitle.size()-1,assign);//利用二进制特性指定所有指派
for(auto it:assign)
{
map<char, bool> varvals=it;
for(unsigned int i=0;i<(m_vecTableTitle.size()-1);i++)
{
cout<<varvals.find(m_vecTableTitle[i][0])->second<<"\t";
}
cout<<EvalRPN(varvals)<<"\n";
}
return;
}
int main()
{
PropositionalFormular pf1("P&(Q>!R)|S");
pf1.PrintRPN();
pf1.GenerateTrueValueTable();
system("pause");
PropositionalFormular pf2("P>Q|R>S");
pf2.PrintRPN();
pf2.GenerateTrueValueTable();
system("pause");
PropositionalFormular pf3("((P))>Q|R>S");
pf3.PrintRPN();
system("pause");
PropositionalFormular pf4("P>Q|R>(S>K)");
pf4.PrintRPN();
system("pause");
PropositionalFormular pf5("P&T>F");
pf5.PrintRPN();
pf5.GenerateTrueValueTable();
system("pause");
return 0;
}