首先先感谢班级里大佬提供的解题思路和分析:https://www.cnblogs.com/ChildishChange/p/9926892.html
我这里只是简单介绍我自己写代码的思路
题目如下:
以 C 或 javascript 实现命题逻辑公式语法检查及公式求值.
- 命题变元
字母及数字构成的字符串且首字符是字母 - 第一类逻辑联结词:
0,1,¬,∧,∨,→,⊕,↔ - 第二类逻辑联结词:
字母及数字构成的字符串且首字符是字母 - 新定义逻辑联结词
给出逻辑联结词的元数及真值表的最后一列
- 对于不是命题逻辑公式的字符串,至少指明一处语法错误
- 进行永真及永假判断, 对于不是永真及永假的命题逻辑公式, 给出使得该公式成立的所有真值赋值
- 对于给定的逻辑联结词的有限集合, 判断它是否为完全的
- 作为命题变元及新定义联结词的字符串的长度不超出 10, 新定义联结词的个数不超出 10, 新定义逻辑联结词的元数不超出 10.
简而言之就是一个简易的编译器,或者说计算器
首先实现计算,最先想到的算法便是建树,后序遍历,基于堆栈计算
首先便是建树问题
建树过程中的第一个问题是,¬,∧,∨,→,⊕,↔ 这几个连接符直接输命令行里没法转换成对应的char字符,特别是¬和↔,基本没法通过命令行输入(希望大佬能给思路),因为∧,∨,→,⊕的utf8编码长度为2,需要对分词的过程做一个调整
这里套用一个简单的分词代码,此处感谢这一位https://www.cnblogs.com/chinxi/p/6129774.html
void splitWord(const string & word, vector<string> & characters)
{
int num = word.size();
int i = 0;
while (i < num)
{
int size = 1;
if (word[i] & 0x80)
{
char temp = word[i];
temp <<= 1;
do {
temp <<= 1;
++size;
} while (temp & 0x80);
}
string subWord;
subWord = word.substr(i, size);
if (subWord.length() == 3) {
//源代码这里有bug会把运算符连上之后的字符
string formal = subWord.substr(0, 2);
string later = subWord.substr(2, 1);
characters.push_back(formal);
characters.push_back(later);
}
else
characters.push_back(subWord);
i += size;
}
}
实际进一步分词把运算符之间的字符结合到一块,这个不用过多说明了
分词之后,接下来就是建树的过程了,首先要确定树的数据结构
我选择的数据结构是父节点指向最左子孩子,然后每个孩子指向右边的兄弟(如果没有函数的要求的话,直接二叉树就完事了)
文法怎么样大佬已经写了,这里写一写自己做的想法
首先,没必要用循环状态机来做,因为实际的结构很简单,特别是大部分的运算符都是二元运算符,所以我选择了递归的方式
另外值得注意的是,运算的优先级是从左到右并且运算符之间的优先级是¬,∧,∨,⊕,→,↔ 依次减小。
这里用了一个比较讨巧的方法,把每一个运算字符串从右到左处理(特别是我选择堆栈存字符串这个还不需要额外处理,选择堆栈的原因之后再说)从右到左处理,会自然把右侧的运算符放在树的上层,左侧的运算符放在下层。
递归的方式其实比较简单
从右向左查看字符串{
依次判断运算符(优先级){
在运算符分割字符串,对左右字符串递归处理
运算符作为根节点,左侧建树结果作为左侧第一孩子,右侧字符串是做为左一孩子的右兄弟
}
}
剩余的字符串可能是形如p或¬p的字符串分情况建成对应的叶节点
建树的思路是:
1.先计算括号内的内容,通过堆栈即可实现,括号内处理完之后,在栈顶压入表明建成的树的根节点的内容
2.括号中内容建完之后,对每个函数进行建树操作,其实函数的实际结构就是函数名+n个子字符串,所以分别处理即可,压入栈的内容与括号相同
3.这时剩余的字符串没有任何的括号存在,只需要在进行一次建树操作即可
树如何进行后续遍历就不用说了
这时建树之前建立的变量表和函数表就发挥作用了,直接参与后序遍历的计算即可,用堆栈的方式计算后续遍历的结果即可
对于变量值得每种组合,使用枚举的方式,也可以通过类似二进制的方式实现
vector<int> changesave;
//changesave初始化全为1
for (int i = 0; i < pow(2, changesave.size()); i++) {
int addflag = 1;
for (int j = changesave.size() - 1; j >= 0; j--) {
if (addflag == 1 && changesave[j].value == 1) {
changesave[j].value = 0;
}
else {
changesave[j].value += addflag;
addflag = 0;
}
}
}
完全集的计算,大佬只是给了一个简单的思路
为了避嫌,我这里只提供一个更为详细一点的思路
对pq分别编码,代表最初的两种状态
在容器里随便取n个状态(可重复){
对这n个状态计算新的状态,计算方式就是按位进行F函数计算
新的状态产生,继续迭代
没新的状态产生,迭代结束
}
这里头疼如何取状态(容器扩张一般迭代方式没法写),最后思路是,每次取状态序列前,设立一个初始的界限,迭代计算之后,判断新状态就用全部容器来判断,一次序列结束之后,对增大后的容器再次执行取状态序列
每个状态需要生成一个新的字符串,按照这个结构生成即可F(第一个状态对应的字符串,第二个对应的,……,第 n个对应的),这个字符串就是新状态的字符串
最后需要计算出来¬p 和p→q,这里有个有趣的事,这个式子的产生实际上并不需要再把生成的字符串再算一次判断真值表,实际上状态的编码就足够了
例如p的编码为0101,q的为0011(初始编码其实随便),那么¬p 的编码就是1010,p→q的就是1011,其实就是按位计算的结果
搞定,祝诸君好运