一.實驗內容
(1)求任意一個命題公式的真值表。
(2)利用真值表求任意一個命題公式的主范式。
(3)利用真值表進行邏輯推理。
注:(2)和(3)可在(1)的基礎上完成。
二.實驗目的
真值表是命題邏輯中的一個十分重要的概念,利用它幾乎可以解決命題邏輯中的所有問題。例如,利用命題公式的真值表,可以判斷命題公式的類型、求命題公式的主范式、判斷兩命題公式是否等價,還可以進行推理等。
本實驗通過編寫一個程序,讓計算機給出命題公式的真值表,並在此基礎上進行命題公式類型的判定、求命題公式的主范式等。目的是讓學生更加深刻地理解真值表的概念,並掌握真值表的求解方法及其在解決命題邏輯中其他問題中的應用。
三.算法的主要思想
利用計算機求命題公式真值表的關鍵是:①給出命題變元的每一組賦值;②計算命題公式在每一組賦值下的真值。
真值表中命題變元的取值具有如下規律:每列中0和1是交替出現的,且0和1連續出現的個數相同。n個命題變元的每組賦值的生成算法可基於這種思想。
含有n個命題變元的命題公式的真值的計算采用的方法為“算符優先法”。
為了程序實現的方便,約定命題變元只用一個字母表示,非、合取、析取、條件和雙條件聯結詞分別用!、&、|、-、+來表示。
算符之間的優先關系如表1-32所示:
表1-32 算符優先級
|
|
+ - | & ! ( ) # |
| + - | & ! ( ) # |
> < < < < < > > > > < < < < > > > > > < < < > > > > > > < < > > > > > > > < > > < < < < < < = E > > > > > E > > < < < < < < E = |
為實現算符優先算法,我們采用兩個工作棧。一個稱作OPTR,用以寄存運算符;另一個稱作OPND,用以寄存操作數或運算結果。
四.算法的具體實現
(1)定義全局的屬性:
(這里棧就直接調用C++有的庫#include<stack>,就不用自己再寫棧的代碼)
運算符棧OPTR
操作數或運算結果棧OPND
常量操作字符集合operateChar
公式中的變元VarList
stack<bool> OPND;//操作數或運算結果棧
stack<char> OPTR;//運算符棧
const char operateChar[8] = { '+', '-', '|', '&', '!', '(', ')','#' };//運算符(+:等價、-:蘊涵、|:析取、&:合取、!:否定
string VarList;//保存公式中的變元
(2)判斷字符是否是運算符
//判斷字符是否是運算符
bool In(char ch) {
for (int i = 0; i < (int)strlen(operateChar); i++)
if (operateChar[i] == ch)
return true;
return false;
}
(3)比較運算符的優先級
這里可以直接用二維字符數組實現運算符優先級表,將OPTR的棧頂運算符(theta1)與新輸入的運算符(theta1)作比較,各自按照operateChar的順序賦值就可以得到優先級
//比較運算符的優先級
char Precede(char theta1, char theta2) {
//運算符優先級表
char operate_Table[8][9] = {
"><<<<<>>",
">><<<<>>",
">>><<<>>",
">>>><<>>",
">>>>><>>",
"<<<<<<=E",
">>>>>E>>",
"<<<<<<E="
};
int theta1_Index=0, theta2_Index=0;//定義運算符1和運算符2的索引
for (int i = 0; i < (int)strlen(operateChar); i++){
if (operateChar[i] == theta1)
theta1_Index = i ;
if (operateChar[i] == theta2)
theta2_Index = i ;
}
return operate_Table[theta1_Index][theta2_Index];
}
(4)計算表達式,並將結果返回(這里是計算雙目的,在這次試驗命題公式中只有!是單目運算符所以到時直接在計算表達式時直接計算)
//計算表達式,並將結果返回(雙目)
bool Calculate(bool x, char operate, bool y) {
switch (operate)
{
case '+': return (((!x) || y) && (x || (!y))); break;
case '-': return ((!x) || y); break;
case '|': return x || y; break;
case '&': return x && y; break;
}
return -1;
}
(5)判斷是否是字母(用來將公式中的命題變元提取出來),然后將變元按遞增順序賦值給VarList
我就只做了小寫字母的,大寫字母可以自己改下判斷條件
//判斷是否是字母(僅小寫字母)
bool isAlpha(char ch) {
if (((int)ch >= 97 && (int)ch <= 122)&&VarList.find(ch)==-1)//若字符的ascii碼在97~122中則是字母
return true;
else return false;
}
//提取公式字符串中全部命題變元,並按遞增順序存放在VarList中
void createVarList(string source) {
int indexNum=0;//比較字母用的下標
for (auto ch:source) {
if (isAlpha(ch))
VarList += ch;
}
for (int i = 0; i < VarList.length()-1;i++) {
for (int j = 0; j < VarList.length() - i-1; j++) {
if ((int)VarList[j] > (int)VarList[j + 1]) {
char temp = VarList[j];
VarList[j] = VarList[j + 1];
VarList[j + 1] = temp;
}
}
}
}
(6)計算表達式
bool InfixValue(string source) {
OPTR.push('#');
char item = source[0];
bool OPNDtop2, OPNDtop1;
char OPTRtop;
int i = 1;
while (item != '#' || OPTR.top() != '#') {
if (!In(item)) {
if (item == '0')
OPND.push(false);
else
OPND.push(true);
item = source[i++];
}
else if (OPTR.top() == '!')
{
OPTR.pop();
OPNDtop1 = OPND.top();
OPND.pop();
OPND.push(!OPNDtop1);
}
else
{
switch (Precede(OPTR.top(), item))
{
case '<':
OPTR.push(item);
item = source[i++];
break;
case '>':
OPTRtop = OPTR.top();
OPTR.pop();
OPNDtop1 = OPND.top();
OPND.pop();
OPNDtop2 = OPND.top();
OPND.pop();
OPND.push(Calculate(OPNDtop2, OPTRtop, OPNDtop1));
break;
case '=':
OPTR.pop();
item = source[i++];
break;
}
}
}
return OPND.top();
}
(7)因為我們要獲得變元所有的取值情況,所以可以當成二進制來依次遞增
比如 變元有 a b c 三個,就是從 000 開始依次取到111
這就需要一個對二進制增值的方法
//依次取值的二進制值加1
void IncreaseVarValue(char(&v)[26], int& flag) {
int m = 1;
int n = VarList.length();
for (int j = n - 1; j > -1; j--) {
int temp;
temp = int(v[j]) - 48;
flag = flag + temp;
if (flag == 2) {
v[j] = '0'; flag = 1;
}
else {
v[j] = '1'; flag = 0; break;
}
}
}
(8)得到真值表
void TruthTable(string expression,bool * &truthTab,string *&expressionValueList,string *&trowList,int &CircleNum) {
int m = 1;
int n = VarList.length();
int flag;
char trow[26];//表達式中變元的依次取值
for (int i = 0; i < n; i++) { m *= 2; trow[i] = '0'; }
string* expressionValueList_IN = new string[m];
string* trowList_IN = new string[m];
bool *truthtable_IN=new bool[m];//真值表中的值
CircleNum = m;
//轉換成用0或1表示的表達式
for (int i = 0; i < m; i++) {
string value1 = expression;//因為公式是字符串無法直接計算,所以定義value1,將里面的命題變元變成0或1
//使表達式的變元用0或1表示
for (int j = 0; j < n; j++) {
char x = VarList[j];
for (int k = 0; k < expression.length(); k++) {
char a = value1[k];
if (value1[k] == x)
value1[k] =trow[j];
}
}
trowList_IN[i] = trow;
expressionValueList_IN[i] = value1;
truthtable_IN[i] = InfixValue(value1);//將得出來的值依次給truthtable
flag = 1;
IncreaseVarValue(trow,flag);
}
truthTab = truthtable_IN;
expressionValueList = expressionValueList_IN;
trowList = trowList_IN;
}
(9)最后輸出真值表
//輸出真值表
void PrintTable(string expression) {
string* expressionValueList;//用來保存所有的表達式
string* trowList;//保存變元的所有取值
bool* truthtab;//保存所有的表達式的值
int CircleNum;//循環次數
createVarList(expression);
TruthTable(expression, truthtab, expressionValueList, trowList,CircleNum);
//打印真值表
for (int i = 0; i < VarList.length(); i++) {
cout << VarList[i] << "\t";
}
cout << expression <<"\t" <<"值"<<endl;
for (int i = 0; i < CircleNum; i++) {
for (int j = 0; j < VarList.length(); j++) {
cout << trowList[i][j] << "\t";
}
cout << expressionValueList[i] << "\t" << truthtab[i] << endl;;
}
}
完整的代碼:
#include<iostream>
#include<math.h>
#include<string.h>
#include<stack>
using namespace std;
stack<bool> OPND;//操作數或運算結果棧
stack<char> OPTR;//運算符棧
const char operateChar[8] = { '+', '-', '|', '&', '!', '(', ')','#' };//運算符(+:等價、-:蘊涵、|:析取、&:合取、!:否定
string VarList;//保存公式中的變元
//判斷字符是否是運算符
bool In(char ch) {
for (int i = 0; i < (int)strlen(operateChar); i++)
if (operateChar[i] == ch)
return true;
return false;
}
//比較運算符的優先級
char Precede(char theta1, char theta2) {
//運算符優先級表
char operate_Table[8][9] = {
"><<<<<>>",
">><<<<>>",
">>><<<>>",
">>>><<>>",
">>>>><>>",
"<<<<<<=E",
">>>>>E>>",
"<<<<<<E="
};
int theta1_Index=0, theta2_Index=0;//定義運算符1和運算符2的索引
for (int i = 0; i < (int)strlen(operateChar); i++){
if (operateChar[i] == theta1)
theta1_Index = i ;
if (operateChar[i] == theta2)
theta2_Index = i ;
}
return operate_Table[theta1_Index][theta2_Index];
}
//計算表達式,並將結果返回(雙目)
bool Calculate(bool x, char operate, bool y) {
switch (operate)
{
case '+': return (((!x) || y) && (x || (!y))); break;
case '-': return ((!x) || y); break;
case '|': return x || y; break;
case '&': return x && y; break;
}
return -1;
}
//判斷是否是字母(僅小寫字母)
bool isAlpha(char ch) {
if (((int)ch >= 97 && (int)ch <= 122)&&VarList.find(ch)==-1)//若字符的ascii碼在97~122中則是字母
return true;
else return false;
}
//提取公式字符串中全部命題變元,並按遞增順序存放在VarList中
//比如 (!a-d)-(c+b) VarList值就是 "abcd"
void createVarList(string source) {
int indexNum=0;//比較字母用的下標
for (auto ch:source) {
if (isAlpha(ch))
VarList += ch;
}
for (int i = 0; i < VarList.length()-1;i++) {
for (int j = 0; j < VarList.length() - i-1; j++) {
if ((int)VarList[j] > (int)VarList[j + 1]) {
char temp = VarList[j];
VarList[j] = VarList[j + 1];
VarList[j + 1] = temp;
}
}
}
}
//計算表達式
bool InfixValue(string source) {
OPTR.push('#');
char item = source[0];
bool OPNDtop2, OPNDtop1;
char OPTRtop;
int i = 1;
while (item != '#' || OPTR.top() != '#') {
if (!In(item)) {
if (item == '0')
OPND.push(false);
else
OPND.push(true);
item = source[i++];
}
else if (OPTR.top() == '!')
{
OPTR.pop();
OPNDtop1 = OPND.top();
OPND.pop();
OPND.push(!OPNDtop1);
}
else
{
switch (Precede(OPTR.top(), item))
{
case '<':
OPTR.push(item);
item = source[i++];
break;
case '>':
OPTRtop = OPTR.top();
OPTR.pop();
OPNDtop1 = OPND.top();
OPND.pop();
OPNDtop2 = OPND.top();
OPND.pop();
OPND.push(Calculate(OPNDtop2, OPTRtop, OPNDtop1));
break;
case '=':
OPTR.pop();
item = source[i++];
break;
}
}
}
return OPND.top();
}
//依次取值的二進制值加1
void IncreaseVarValue(char(&v)[26], int& flag) {
int m = 1;
int n = VarList.length();
for (int j = n - 1; j > -1; j--) {
int temp;
temp = int(v[j]) - 48;
flag = flag + temp;
if (flag == 2) {
v[j] = '0'; flag = 1;
}
else {
v[j] = '1'; flag = 0; break;
}
}
}
//得到真值表
void TruthTable(string expression,bool * &truthTab,string *&expressionValueList,string *&trowList,int &CircleNum) {
int m = 1;
int n = VarList.length();
int flag;
char trow[26];//表達式中變元的依次取值
for (int i = 0; i < n; i++) { m *= 2; trow[i] = '0'; }
string* expressionValueList_IN = new string[m];
string* trowList_IN = new string[m];
bool *truthtable_IN=new bool[m];//真值表中的值
CircleNum = m;
//轉換成用0或1表示的表達式
for (int i = 0; i < m; i++) {
string value1 = expression;//因為公式是字符串無法直接計算,所以定義value1,將里面的命題變元變成0或1
//使表達式的變元用0或1表示
for (int j = 0; j < n; j++) {
char x = VarList[j];
for (int k = 0; k < expression.length(); k++) {
char a = value1[k];
if (value1[k] == x)
value1[k] =trow[j];
}
}
trowList_IN[i] = trow;
expressionValueList_IN[i] = value1;
truthtable_IN[i] = InfixValue(value1);//將得出來的值依次給truthtable
flag = 1;
IncreaseVarValue(trow,flag);
}
truthTab = truthtable_IN;
expressionValueList = expressionValueList_IN;
trowList = trowList_IN;
}
//輸出真值表
void PrintTable(string expression) {
string* expressionValueList;//用來保存所有的表達式
string* trowList;//保存變元的所有取值
bool* truthtab;//保存所有的表達式的值
int CircleNum;//循環次數
createVarList(expression);
TruthTable(expression, truthtab, expressionValueList, trowList,CircleNum);
//打印真值表
for (int i = 0; i < VarList.length(); i++) {
cout << VarList[i] << "\t";
}
cout << expression <<"\t" <<"值"<<endl;
for (int i = 0; i < CircleNum; i++) {
for (int j = 0; j < VarList.length(); j++) {
cout << trowList[i][j] << "\t";
}
cout << expressionValueList[i] << "\t" << truthtab[i] << endl;;
}
}
void main() {
system("pause");
while (true) {
cout << "*****************************************************" << endl;
cout << "*** +表示等價 ***" << endl;
cout << "*** -表示蘊涵 ***" << endl;
cout << "*** &表示合取 ***" << endl;
cout << "*** |表示析取 ***" << endl;
cout << "*** 注:必須用#結束 ***" << endl;
cout << "*** 輸入1退出程序 ***" << endl;
cout << "*****************************************************" << endl;
cout << "請輸入命題公式:";
string expression;
cin >> expression;
if (expression == "1") {
break;
}
//如果沒有#
if (expression.find('#') != expression.length() - 1)
{
cout << "請以#結束!" << endl;;
system("pause");
system("cls");
continue;
}
PrintTable(expression);
system("pause");
system("cls");
}
}
