課本是高等教育出版社出版的《離散數學及其應用》。
程序會自動分析輸入的表達式,並且列出真值表,最後打印出主析取範式和主合取範式,最多支持256 個變元。
主要用到的算法:中綴表達式轉後綴表達式、後綴表達式求值還有一個二進制加法模擬。
下面上2 個圖,第一個是表達式開頭沒有非運算的(課本P85 例3.5.5):
第二個不但表達式開頭有非運算,而且非運算之後並不是一個數值,而是一個操作符(課本P83 例3.5.4):
下面是代碼,如果CSDN 的編輯器弄亂了就將就着看吧,本來代碼的縮進和排版都是整潔的。
file:///main.c
/* main.c
* use MinGW Developer Studio to compile
* by iSpeller ([email protected])
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define _BUF_LEN (1<<10)
#define _STACK_LEN (_BUF_LEN/2)
#define _PROP_LEN (1<<7)
#define VOID_NUM (0) /* 不存在的運算數 */
typedef char * string;
typedef int data_t;
typedef int bool_t;
#define TRUE (1)
#define FALSE (0)
/* 範式類型, 析取, 合取 */
enum paradigm { EXTRACT, CONJUNCT, };
/* 優先級大小 等於, 小於, 大於 */
enum priorities { EG=0, NGE=-1, NLE=1, };
/* 聯結詞(包括英文圓括號)優先級,全真 */
/* 左括號, 非, 合取, 析取, 蘊含, 右括號, 結束符 */
enum { LEFT=5, NOT=4, AND=3, OR=3, CONT=3, RIGHT=2, END=1, };
/* 4個棧,expr 儲存後綴表達式,truth_expr 是expr 的真值解釋
* ops 儲存聯結詞, truth 做後綴表達式求值棧,truth 最後存放表達式真值
*/
struct stack {
data_t data[_STACK_LEN];
int32_t len;
uint32_t len_max;
}expr, truth_expr, ops, truth;
string input_buf = NULL; /* 存放輸入的表達式 */
struct prop {
char p; /* 變元名 */
bool_t v; /* 真值 */
};
struct table {
struct prop data[_PROP_LEN];
int32_t len;
uint32_t len_max;
} prop_table; /* 原子命題變元的列表 */
typedef int truth_item;
struct truth_table {
truth_item data[_STACK_LEN];
int32_t len;
uint32_t len_max;
} truth_table; /* 真值表 */
#define push(s,a) ((s).data[++(s).len] = (a))
#define pop(s) ((s).data[(s).len--])
#define is_empty(s) ((s).len+1)
#define get_top(s) ((s).data[(s).len])
#define init(s,m) (((s).len = -1) || ((s).len_max = (m)))
#define STRCMP(a,r,b) (strcmp ((a), (b)) r 0)
/* 判斷字符是否是原子命題變元
*/
bool_t
is_op (char c) {
switch (c) {
case '|': return OR; break;
case '&': return AND; break;
case '!': return NOT; break;
case '>': return CONT; break;
case '(': return LEFT; break;
case ')': return RIGHT; break;
case '#': return END; break;
default : return FALSE;
}
}
/* 判斷運算符的優先級
*/
enum priorities
get_priority (char op1, char op2) {
if (is_op (op1) == is_op (op2))
return EG;
else if (is_op (op1) < is_op (op2))
return NGE;
else
return NLE;
}
/* 進行數據運算
*/
data_t
do_op (char op, bool_t num1, bool_t num2) {
bool_t truth = FALSE;
if('!' == op)
return num1 ? FALSE : TRUE;
switch (op) {
case '|': truth = (num2 || num1); break;
case '&': truth = (num2 && num1); break;
case '>': truth = (num2 && !num1) ? FALSE : TRUE;
break;
default: fprintf (stderr, "Boy, WHAT Did U Have Done!!??\n");
exit (0);
}
return truth;
}
/* 判斷識別的命題變元是否已經存在
*/
bool_t
prop_find (struct prop item) {
int32_t count = 0;
for (count=0; count<prop_table.len+1; ++count)
if (item.p == prop_table.data[count].p)
return TRUE;
return FALSE;
}
/* 判斷變元真值是否設置完畢
* 如果真值全真返回真
*/
bool_t
set_truth_end (void) {
int32_t count;
for (count=0; count<prop_table.len+1; ++count)
if (!prop_table.data[count].v)
return FALSE;
return TRUE;
}
/* 輸出“你好”
*/
void
start (void) {
printf ("輸入諸如\"P|(Q&R)>!P\" 的表達式,程序將會列出真值表並求出主範式。\n\n");
printf ("其中:\"|\"表示析取;\"&\"表示合取;\"!\"表示非;\">\"表示蘊含。支持英文圓括號。\n");
printf ("字符串處理不是算法的核心,所以不會處理非法輸入\n");
printf ("\t----因此當你輸入了非法的表達式,你也會得到一個非法的結果 :D\n");
printf ("exit 指令退出。\n\n");
}
/* 要求用戶輸入表達式和原子命題變元的真值
*/
void
get_input () {
char *loc = input_buf;
uint32_t size = 0;
struct prop item;
/* 清棧 */
init (expr, _STACK_LEN);
init (ops, _STACK_LEN);
init (truth_expr, _STACK_LEN);
init (truth, _STACK_LEN);
init (truth_table, _STACK_LEN);
init (prop_table, _PROP_LEN);
if (!(input_buf = (char *)malloc (_BUF_LEN))) {
perror ("malloc ()");
exit (1);
}
/* 獲取表達式 */
do {
printf (" # ");
if (!fgets (input_buf, _BUF_LEN-1, stdin)) {
perror ("fgets ()");
exit (1);
}
input_buf[strlen (input_buf)-1] = '#'; /* 結束符號 */
if (STRCMP ("exit#", ==, input_buf)) {
printf ("再見 :D\n");
exit (0);
}
} while (STRCMP ("#", ==, input_buf));
/* 識別原子命題變元和聯結詞並壓入變元列表 */
size = strlen (input_buf);
for (loc = input_buf; loc-input_buf < size; ++loc) {
if (' ' != *loc)
if (!is_op (*loc)) {
item.p = *loc;
if (!prop_find (item))
push (prop_table, item); /* 現在並不賦真值 */
}
}
}
/* input_buf 中的表達式轉換爲後綴表達式
*/
void
make_postfix_expr () {
data_t item;
int32_t count;
enum priorities level;
push (ops, '#'); /* 棧底元素,結束符號,優先級最小 */
for (count=0; count<strlen (input_buf); ++count) {
item = input_buf[count];
if (' ' == item)
continue;
if (!is_op (item)) /* 是操作數則壓入表達式棧 */
push (expr, item);
else if (')' == item || '#' == item) { /* 去除成對的括號和結束標記'#' */
while ('#' != (item = pop (ops)))
push (expr, item);
pop (ops);
} else {
level = get_priority (item, get_top (ops));
/* 通過壓入一個不存在的操作數,把單目運算符'!'
* 當作雙目運算符來處理
*/
if ('!' == item)
push (expr, VOID_NUM);
if (NLE == level) { /* 如果後進運算符高於棧頂元素 */
push (ops, item); /* 壓入運算符棧 */
if ('(' == item) /* 如果壓入了一個左括號 */
push (ops, '#'); /* 壓入運算符棧一個結束標記來保持正確的優先級 */
} else { /* 否則 */
push (expr, pop (ops)); /* 棧頂元素壓入表達式棧 */
push (ops, item); /* 後進運算符壓入運算符棧 */
}
}
}
free (input_buf);
}
/* 設置變元的初始真值,全假
*/
void
init_props_truth (void) {
int32_t count = 0;
for (count=0; count<prop_table.len+1; ++count)
prop_table.data[count].v = FALSE;
for (count=0; count<prop_table.len+1; ++count)
printf ("%d ", prop_table.data[count].v);
/* 立刻計算一次真值,因爲其後的計算不包含全假的情況 */
void find_truth (void);
find_truth ();
}
/* 設置變元的真值,把所有變元當做一個二進制數
* 用二進制加法模擬真值,每次調用函數都會給二進制數加一
*/
void
set_props_truth (void) {
bool_t carry = FALSE; /* 進位標誌 */
int32_t count, count2;
for (count=0, carry=TRUE; carry && (count<prop_table.len+1); ++count) {
if (prop_table.data[count].v) {
prop_table.data[count].v = (carry ? 0 : 1);
if (prop_table.len>0)
/* 同時要處理前面的位 */
for (count2=1; count2<count+1; ++count2)
prop_table.data[count-count2].v = FALSE;
carry = (prop_table.data[count].v ? FALSE : TRUE);
} else {
prop_table.data[count].v = (carry ? 1 : 0);
carry = FALSE;
}
}
for (count=0; count<prop_table.len+1; ++count)
printf ("%d ", prop_table.data[count].v);
void find_truth (void);
find_truth ();
}
/* 調用函數之時默認prop_table 已經設置了一組有效的真值
* 函數計算在這組真值下整個後綴表達式的真值
*/
void
find_truth (void) {
truth_item item;
int32_t count, count2;
data_t data, num1, num2, ans;
/* 首先把truth_expr 中的變元全部換成真值 */
truth_expr = expr;
for (count=0; count<truth_expr.len+1; ++count) {
data = truth_expr.data[count];
if (!is_op (data)) {
for (count2=0; count2<prop_table.len+1; ++count2)
if (data == prop_table.data[count2].p) {
truth_expr.data[count] = prop_table.data[count2].v;
break;
}
}
}
/* 後綴表達式求值 */
for (count=0; count<truth_expr.len+1; ++count) {
data = truth_expr.data[count];
if (!is_op (data)) /* 非運算符 */
push (truth, data);
else { /* 是運算符 */
num1 = pop (truth);
num2 = pop (truth);
ans = do_op (data, num1, num2);
push (truth, ans);
}
}
/* 儲存真值 */
item = pop (truth);
push (truth_table, item);
/* 順便打印真值 */
printf ("\t%d\n", truth_table.data[truth_table.len]);
}
/* 打印主範式
*/
void
print_main_paradigm (enum paradigm type) {
int32_t count;
bool_t has_find;
if ((EXTRACT!=type) && (CONJUNCT!=type))
exit (0);
printf ("主%s範式爲 : ", (EXTRACT==type) ? "析取" : "合取");
for (count=0, has_find=FALSE; count<truth_table.len+1; ++count) {
if ((EXTRACT==type)
? truth_table.data[count]
: !truth_table.data[count]) {
has_find = TRUE;
printf ("%c%d %s ", (EXTRACT==type) ? 'm' : 'M',
count,
(EXTRACT==type) ? "∨" : "∧");
}
}
if (has_find)
printf ("\b\b \n");
else
printf ("爲空");
}
/* MAIN
*/
int
main (int argc, char *argv[]) {
int32_t count;
start ();
while (TRUE) {
get_input ();
make_postfix_expr ();
for (count=0; count<prop_table.len+1; ++count) {
printf ("%c ", prop_table.data[count].p);
}
printf ("\t真值\n\n");
init_props_truth ();
while (!set_truth_end ())
set_props_truth ();
print_main_paradigm (EXTRACT); /* 主析取範式 */
print_main_paradigm (CONJUNCT); /* 主合取範式 */
}
return 0;
}