[離散] 編程求命題公式真值表

[離散] 編程求命題公式真值表

概述

真值表是離散數學中的一個重要概念,由真值表我們能求得任意命題公式的主析取範式和主合取範式。下面我們先來回顧一下真值表的概念:

將命題公式A在所有賦值下取值情況列成表,稱作A的真值表

由真值表的定義我們不難得出我們將要做些什麼,

  1. 分析命題公式A
  2. 對命題公式進行賦值並求值
  3. 輸出真值表

本文將利用C語言編寫一個程序,實現上述過程。

分析問題

要求真值表,就必須分析命題公式,將輸入的命題公式,如 p&!q|(q>p) ,轉化爲計算機能理解的對象,方便後續求解。

:這裏用 & 表示合取, 用 | 表示析取,用 > 表示蘊含,用 = 表示等價,表示命題的否定


人求解的思路

一個解析命題公式最直接的也是最樸素的想法就是模擬,也就是模擬你是怎麼想的,讓計算機按照你分析公式的思路進行分析。

這樣,我們就需要仔細想想,我們在分析命題公式,求真值表的時候都在做些什麼呢 ?


:給定如下命題公式((p>q)&(q>r))>(p>r) ,求它的真值表

讀者不妨自己在紙上寫下這個公式,然後自己想一想,理清思路


一個常見的思路

  1. 給定一個賦值,從左到右按順序求出每個子公式的值,最終得到公式的值
  2. 如,假設 p =1,q = 0, r = 1
  3. 計算p>q 得到 0,計算 q>r得到1,計算p>r得到1
  4. 最後計算 (0 & 1 ) > 1得到1

我們分析這個思路,會發現,這裏3步驟裏計算哪個表達式選擇往往因人而異,同時現實中很可能根本就是隨意選取,這樣是不利於計算機實現的。

如果將第三步限定爲從左到右選取,那麼這個思路,我們要按順序解析出每個形如 A[符號]B的命題,還要記下子命題之間的運算符,並且還需要用別的方法,搞清楚我們最後算總命題的順序。

不是不能做,也能做,有興趣的也可以嘗試編寫對應代碼試一試。不過在這裏,我要介紹另一種利用遞歸的方法。


給出參考思路:

  1. 我們在看到這個公式的時候,首先尋找了優先級最低的符號(即>),然後將這個表達式分成了兩份。
  2. 觀察分好的表達式,如果是形如A[符號]B的形式 ,則直接計算,如果不是進入下一步。
  3. 對新的子式重複做 步驟1,直到分成的每個子式都最簡
  4. 然後按順序反回去計算整個表達式的值

熟悉遞歸的朋友,可能一下就會發現這就是個遞歸,將大問題拆分成子問題,用子問題的返回值,對父問題進行解答。


抽象人的思路

如果我們用一個樹來表達我們的拆解過程,那麼可以得到下面一顆非完全二叉樹,我們的拆解過程,就是一個構建樹的過程。

還是之前的例子:((p>q)&(q>r))>(p>r) ,通過遞歸,我們得到了這樣一顆樹。

這裏寫圖片描述

我們接下了的所有操作都將以這個樹爲基礎,所以構建這個顆樹極爲關鍵,是整個算法的核心

下面給出完整思路(忽略實現細節)

  1. 建樹(建樹思路前面給出)。
  2. 從樹的葉子節點開始,自下而上,同層優先,進行計算。
  3. 得到根節點的值,然後重新賦值
  4. 回到步驟2,直到真值表構建完畢

有了這個思路框架,我們就可以開始編寫具體的代碼,並補充一些其他需要注意的東西。


實現細節

建樹

節點的存儲

定義一個結構體 node 存儲節點信息,除葉子節點外,每個節點都有至少有一個左兒子或右兒子(當然大部分是都有),這些節點要存儲的信息如下:

  • 一個字符串存儲命題公式
  • 一個字符存儲,兩個兒子之間的運算符
  • 一個int型整數存儲該節點存儲的命題公式的真值

遞歸建樹

考慮遞歸結束的條件:

  1. 噹噹前命題公式不含符號時
  2. 噹噹前命題公式爲命題的否定時

按照運算的優先級,從低到高枚舉第一個出現的符號,按符號將公式分成兩個子式①

優先級:= <> < |< &< ! < ()

考慮一些特殊情況

  1. 第一個滿足①的符號,包括在括號裏②
  2. 命題公式以!開頭③

對於第一種,我們需要先檢測公式第一個字符是不是右括號

  • 如果是,我們找到對應的左括號,並以左括號旁邊的括號爲分界點分出子式。
  • 如果不是,按原樣處理。可以證明,如果第一個符號不是右括號或取反符號! ,則一定存在滿足①的符號將子式正確分成兩份。

特殊中的特殊:出現多重括號。需要找到和最外層匹配的左括號。

對於第二種,暫時忽略掉感嘆號,從第二個字符開始處理,則可以按①或②進行處理,最後生成子串的時候記得感嘆號任然保留。



計算公式的真值

根據我們之前的設計,我們要從最底層的葉子節點開始,並且同層優先,自下而上進行計算。

而我們求真值的時候真的需要這麼做嗎?

答案是:不需要

同樣的,我們利用遞歸,從根節點開始計算,遞歸表達式如下:

f(father)=f(left_kid) [operator] f(right_kid)

值得注意的一點:

在C語言中,與運算和或運算,當能夠確定真值時,將前者能夠確定真值時將不再計算後者。即進行與運算的兩個公式,如果前者爲假,後者將不再計算。

所有我們在書寫程序時,不能直接return A&&B ,而要先計算 C=A&&B ,在返回 return C

當然,我們還需要按順序對命題變元賦值,循環計算公式的真值,並進行存儲。

本教程將他們放到了打印真值表裏進行,當然你也可以不這麼做。



打印真值表

在這部分,我們需要實現大約3個模塊

  • 賦值需要知道每個葉子節點在樹中的位置
  • 打印真值表,需要存儲樹的每一層含有的節點
  • 一個循環計算真值表

葉子節點的位置,在建樹的時候記憶
每一層含有的節點,單獨寫一個函數計算

給出僞代碼:

print(樹) //從下向上,按層級打印
for(i取遍所有賦值) //二進制轉十進制
{
  for() //給命題變元賦值
  for() //打印命題變元的值
  cal(1) //計算總公式的值和子式的值
  for() //打印子式和總公式的值
  print(換行)
}

注意:後面打印的真值要和第一行的命題公式一一對應



參考代碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 100
#define Maxn 100

struct point
{
    char str[MAXN];
    char oper;
    int value;
} exp[Maxn];

const char op[6] = {'=', '>', '|', '&'};

int kid_bj[MAXN * 2], kid[MAXN];
int kid_num = 0;

int f_value[MAXN * 2];
int layer[10][10], lay;

void strcopy(char temp1[], char str[], int start, int end)
{
    int i, j = 0;
    for (i = start; i < end; i++)
    {
        temp1[j++] = str[i];
    }
    temp1[j] = '\0';
}

void kill_bracket(char s[])
{
    int i = 1;
    int left = 0;
    if (s[0] == '(')
    {
        left++;
        while (i < strlen(s) - 1)
        {
            if (s[i] == ')')
            {
                if (left == 1)
                    return;
                left--;
            }
            if (s[i] == '(')
                left++;
            i++;
        }
        char temp[MAXN];
        strcopy(temp, s, 1, strlen(s) - 1);
        memset(s, 0, sizeof(s));
        strcpy(s, temp);
    }
}

void build_tree(char s[], int node)
{
    int i, k, len, mid, find;
    char s1[MAXN], s2[MAXN];
    len = strlen(s);
    kill_bracket(s);
    if (s[0] == '!' && s[1] == '(' && s[len - 1] == ')')
    {
        exp[node].oper = s[0];
        strcpy(exp[node].str, s);
        strcopy(s1, s, 2, len - 1);
        build_tree(s1, node * 2);
        return;
    }

    if (len <= 2)
    {
        if (len == 1)
        {
            strcpy(exp[node].str, s);
            if (kid_bj[s[0]] == 0)
            {
                kid_bj[s[0]] = 1;
                kid[kid_num++] = s[0];
            }
            return;
        }
        else
        {
            char temp[MAXN];
            temp[0] = s[1];
            temp[1] = '\0';
            strcpy(exp[node].str, s);
            exp[node].oper = s[0];
            strcpy(exp[node * 2].str, temp);
            if (kid_bj[temp[0]] == 0)
            {
                kid_bj[temp[0]] = 1;
                kid[kid_num++] = temp[0];
            }
            return;
        }
    }
    find = 0;
    for (k = 0; k < 4; k++)
    {
        i = 0;
        if (find)
            break;
        while (i < len)
        {
            if (s[i] == '(')
            {
                i++;
                int num = 1;
                while (i < len)
                {
                    if (num == 0)
                        break;
                    if (s[i] == '(')
                        num++;
                    if (s[i] == ')')
                        num--;
                    i++;
                }
            }
            if (s[i] == op[k])
            {
                mid = i;
                find = 1;
                break;
            }
            i++;
        }
    }
    // printf("%c\n",s[mid]);
    printf("%d  %s\n", strlen(s), s);
    strcpy(exp[node].str, s);
    exp[node].oper = s[mid];
    // if (s[mid - 1] == ')' && s[0] == '(')
    //     strcopy(s1, s, 1, mid - 1);
    // else
        strcopy(s1, s, 0, mid);
    // if (s[mid + 1] == '(')
    //     strcopy(s2, s, mid + 2, len - 1);
    // else
        strcopy(s2, s, mid + 1, len);
    printf("%d  %s\n", strlen(s1), s1);
    printf("%d  %s\n", strlen(s2), s2);
    build_tree(s1, node * 2);
    build_tree(s2, node * 2 + 1);
}

int cal_val(int node)
{
    if (strlen(exp[node].str) == 1)
    {
        // printf("****%c %d***",exp[node].str[0],f_value[exp[node].str[0]]);
        return f_value[exp[node].str[0]];
    }

    int lkid, rkid;
    int a, b;

    lkid = node * 2;
    rkid = node * 2 + 1;

    switch (exp[node].oper)
    {
    case '=':
        if (cal_val(lkid) == cal_val(rkid))
        {
            exp[node].value = 1;
            return exp[node].value;
        }
        else
        {
            exp[node].value = 0;
            return exp[node].value;
        }
        break;

    case '>':
        if (cal_val(lkid) == 1 && cal_val(rkid) == 0)
        {
            exp[node].value = 0;
            return exp[node].value;
        }
        else
        {
            exp[node].value = 1;
            return exp[node].value;
        }
        break;

    case '|':
        a = cal_val(lkid);
        b = cal_val(rkid);
        exp[node].value = a || b;
        return exp[node].value;
        break;

    case '&':
        a = cal_val(lkid);
        b = cal_val(rkid);
        exp[node].value = a && b;
        return exp[node].value;
        break;

    case '!':
        if (cal_val(lkid) == 1)
        {
            exp[node].value = 0;
            return exp[node].value;
        }
        else
        {
            exp[node].value = 1;
            return exp[node].value;
        }
        break;
    }
}

void getLayer(int node, int l)
{

    int lens = strlen(exp[node].str);
    if (lens > 1)
    {
        if (l > lay)
            lay = l;
        layer[l][++layer[l][0]] = node;
        if (lens == 2 || (exp[node].str[0] == '!' && exp[node].str[lens - 1] == ')'))
            getLayer(node * 2, l + 1);
        else
        {
            getLayer(node * 2, l + 1);
            getLayer(node * 2 + 1, l + 1);
        }
    }
    return;
}

void print_table()
{
    int i, count = 1, j, temp, k;
    printf("layer=%d\n", lay);
    for (i = kid_num - 1; i >= 0; i--)
    {
        count *= 2;
        printf("%10c  ", kid[i]);
    }

    printf("kidnum=%d\n", kid_num);
    // 打印各層節點
    // for (i = lay; i > 0; i--)
    //     for (j = 1; j <= layer[i][0]; j++)
    //         printf("%10s  ", exp[layer[i][j]].str);
    printf("\n");

    for (i = 0; i < count; i++)
    {
        temp = i;
        for (j = 0; j < kid_num; j++)
        {
            f_value[kid[j]] = temp % 2;
            temp /= 2;
        }
        for (j = kid_num - 1; j >= 0; j--)
            printf("%10d  ", f_value[kid[j]]);

        cal_val(1);
        // 打印各層節點
        // for (k = lay; k > 0; k--)
        //     for (j = 1; j <= layer[k][0]; j++)
        //         printf("%10d  ", exp[layer[k][j]].value);
        //打印根節點
        printf("%10d  ", exp[layer[1][1]].value);
        printf("\n");
    }
}

int main()
{
    char s[MAXN];
    scanf("%s", s);
    build_tree(s, 1);
    getLayer(1, 1);
    print_table();
    system("pause");
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章