程式設計題: 命題邏輯應用系統

_TCgogogo_發表於2015-10-31

命題邏輯應用系統


1 問題描述
       該系統要求實現命題邏輯中基本演算法及其應用系統,包括真值表的計算、主析取和主合取正規化的計算。通過此課題,熟練掌握命題公式的計算機表示、命題等價常見演算法的實現,實現一個簡單的命題邏輯應用系統。


2 需要實現的功能或函式
(1)計算顯示任一個命題公式的真值表;
(2)計算命題公式的主析取正規化;
(3)計算命題公式的主合取正規化;
(4)判斷兩個命題公式是否等價(或蘊含);
(5)如有可能,試著利用命題公式的推理關係解決下列邏輯問題:
 一對夫妻帶著他們的一個孩子在路上碰到一個朋友。朋友問孩子:“你是男孩還是女孩?”朋友沒聽清孩子的回答。孩子的父母中某一個說,我孩子回答的是“我是男孩”,另一個接著說:“這孩子撒謊,她是女孩。”這家人中男性從不說謊,而女性從來不連續說兩句真話,也不連續說兩句假話。試問這小孩性別,以及誰是其父親,誰是其母親?
(6)有個簡單直觀的介面,以便顯示使用上述函式功能。


程式:

#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <stack>
#include <map>
using namespace std;
string epr, epr1, epr2;
string pdnf, pcnf; //pdnf析取, pcnf合取
map <char, bool> mp; //雜湊表
stack <char> st;    //用於得到字尾表示式
stack <int> dst;    //用於計算表示式的真值
int val[26];
int res, num;

bool CanIn(char fir, char sec) //能否入棧
{
    int i, o;
    switch(fir)    //設定棧內,棧外元素優先順序
    {  
        case '#': i = 0; break;  
        case '(': i = 1; break;  
        case '~': i = 3; break;  
        case '>': i = 5; break;  
        case '|': i = 7; break;  
        case '&': i = 9; break;  
        case '!': i = 11; break;  
        case ')': i = 12; break;  
    }  
    switch(sec)  
    {  
        case '#': o = 0; break;  
        case '(': o = 12; break;  
        case '~': o = 2; break;  
        case '>': o = 4; break;  
        case '|': o = 6; break;  
        case '&': o = 8; break;  
        case '!': o = 10; break;  
        case ')': o = 1; break;  
    }  
    if(i < o)  
        return true;  
    else  
        return false;  
}

void to_suffix()      //中綴表示式轉字尾表示式
{
    while(st.size())    //清空棧
        st.pop();
    string tmp = "";    //中間變數,用來存字尾表示式
    st.push('#');       //'#'為棧底標記
    int len = epr.length();
    for(int i = 0; i < len; i++)
    {
        if(mp[epr[i]])          //如果當前是字母,則直接加到字尾表示式中
        {
            tmp = tmp + epr[i];
            continue;
        }
        if(CanIn(st.top(), epr[i])) //拿當前的字元和棧頂元素相比,若棧頂元素優先順序小於棧外優先順序,則入棧
            st.push(epr[i]);
        else if(epr[i] == ')')  //處理括號巢狀
        {
            while(st.top() != '(')
            {
                tmp = tmp + st.top();
                st.pop();
            }
            st.pop();
        }
        else        //如果棧頂元素優先順序大於棧外元素優先順序,則將元素出棧,直到棧頂元素優先順序小於棧外優先順序
        {
            do
            {
                tmp = tmp + st.top();
                st.pop();
            }while(!CanIn(st.top(), epr[i]));
            st.push(epr[i]);
        }
    }
    while(st.top() != '#')   //把剩餘元素出棧
    {  
        tmp = tmp + st.top();     
        st.pop();  
    }  
    st.pop();               
    epr = tmp;     //此時tmp為得到的字尾表示式,賦值給epr
    //cout << "epr = " << epr << endl;
}

void Or(int a, int b)     //或運算
{
    res = a + b;
    res = res > 1 ? 1 : res;
    dst.push(res);
}

void And(int a, int b)     //與運算
{
    res = a * b;
    dst.push(res);
}

void Not()          //非運算
{
    int a = dst.top();
    dst.pop();
    res = a == 1 ? 0 : 1;
    dst.push(res);
}   

void If(int a, int b)    //條件運算
{
    res = (b == 1 && a == 0) ? 0 : 1;
    dst.push(res);
}

void Iif(int a, int b)  //雙條件運算
{
    res = (a == b) ? 1 : 0;
    dst.push(res);
}

void cal()      //計算真值
{
    while(dst.size())   //清空棧dst
        dst.pop();
    int len = epr.length();
    int a, b;
    for(int i = 0; i < len; i++)  
    {
        if(mp[epr[i]])
        {
            for(int j = 0; j < 26; j++)
            {
                if(epr[i] == (j + 'A'))
                {
                    dst.push(val[j]);
                    break;
                }
            }
            continue;
        }  
        if(epr[i] != '!')
        {
            a = dst.top();
            dst.pop();
            b = dst.top();
            dst.pop();
        } 
        switch(epr[i])  
        {  
            case '|': 
                Or(a, b); 
                break;  
            case '&': 
                And(a, b); 
                break;  
            case '!': 
                Not(); 
                break;  
            case '~': 
                Iif(a, b); 
                break;  
            case '>': 
                If(a, b); 
                break;  
        }   
    }  
}

void Input1()  //輸入一個表示式
{
    epr = "";
    printf("請輸入兩個真值表示式: (! 否定), (| 析取), (& 合取), (> 條件), (~ 雙條件)\n");
    cin >> epr; 
}

void preprea() //處理表示式
{
    num = 0;
    mp.clear();  //清空map
    int len = epr.length();
    for(int i = 0; i < len; i++)
    {
        if(epr[i] >= 'A' && epr[i] <= 'Z')  
        {
            if(mp[epr[i]] == false)
            {
                mp[epr[i]] = true;  //記錄那些字母在表示式中
                num ++;     //num表示表示式中有幾種字母
            }
        }
    }
}

void Input2()   //輸入兩個表示式
{
    epr = "";
    epr1 = "";
    epr2 = "";
    printf("請輸入兩個真值表示式: (! 否定), (| 析取), (& 合取), (> 條件), (~ 雙條件)\n");
    printf("請輸入第一個\n");
    cin >> epr1;
    printf("請輸入第二個\n");
    cin >> epr2;
}

//idx是字母的標號,cnt表示已經處理的字母的個數,p標記是否輸出真值表
void DFS(int idx, int cnt, int p)   //深度優先搜尋得到真值表和主正規化
{
    if(cnt == num)
    {
        cal();
        if(res == 1)
        {
            pdnf += "(";
            int c = 0;
            for(int i = 0; i < 26; i++)
            {
                char t[1];
                t[0] = i + 'A';
                string ch(t);
                if(mp[ch[0]])
                {
                    c ++;
                    if(val[i] == 1)
                        pdnf += ch;
                    else
                        pdnf += "!" + ch; 
                    if(c != num)
                        pdnf += "&";
                }
            }
            pdnf += ")";
            pdnf += "|";
        }
        else
        {
            pcnf += "(";
            int c = 0; //記錄字母個數
            for(int i = 0; i < 26; i++)
            {
                char t[1];
                t[0] = i + 'A';
                string ch(t);
                if(mp[ch[0]]) //判斷字母是否存在
                {
                    c ++;
                    if(val[i] == 0)
                        pcnf += ch;
                    else
                        pcnf += "!" + ch; 
                    if(c != num)
                        pcnf += "|";
                }
            }
            pcnf += ")";
            pcnf += "&";
        }
        if(!p)
        {
            for(int i = 0; i < 26; i++)
                if(mp[i + 'A'])
                    printf("%d\t", val[i]);
            printf("%d\n", res);
        }
        return;
    }
    int idxx = 0;
    for(int i = idx; i < 26; i++)
    {
        if(mp[i + 'A'])
        {
            idxx = i;
            break;
        }
    }
    val[idxx] = 1;
    DFS(idxx + 1, cnt + 1, p);
    val[idxx] = 0;
    DFS(idxx + 1, cnt + 1, p);
    return;
}

void Print(bool p)  //輸出真值表和主正規化, 若p==0,則輸出真值表
{
    if(!p)
    {
        for(int i = 0; i < 26; i++)
            if(mp[i + 'A'])
                printf("%c\t", i + 'A');
        printf("res\n");
    }
    int idx = 0;  //idx用來記錄0 - 26分別於A - Z對應
    for(int i = 0; i < 26; i++)
    {
        if(mp[i + 'A'])
        {
            idx = i;
            break;
        }
    }
    val[idx] = 1;
    DFS(idx + 1, 1, p);
    val[idx] = 0;
    DFS(idx + 1, 1, p);
}

void output_table()    //輸出真值表介面函式
{
    Input1();
    preprea();
    to_suffix();
    printf("真值表為:\n");
    Print(0);
}

void output_pcnf()    //輸出主合取正規化介面函式  
{
    Input1();
    preprea();
    to_suffix();
    pcnf = ""; //初始化
    Print(1);  
    int len = pcnf.length();
    if(len == 0)
        printf("永真式\n");
    else
    {
        pcnf.erase(pcnf.length() - 1);  
        cout << "主合取正規化:" << pcnf << endl << endl;  
    }
}

void output_pdnf()  //輸出主析取正規化介面函式  
{
    Input1();
    preprea();
    to_suffix();
    pdnf = "";
    Print(1);
    int len = pdnf.length();
    if(len == 0)
        printf("永假式\n");
    else
    {
        pdnf.erase(pdnf.length() - 1);  
        cout << "主析取正規化:" << pdnf << endl << endl;  
    }
}

//判斷兩個表示式的關係
//原理: 命題A與命題B若等價則(A<->B)永真,又永真式無主合取正規化
//     命題A與命題B若存在蘊含關係(如A蘊含B)則(A->B)永真,又永真式無主合取正規化
void judge_two()    
{
    bool flag1 = false, flag2 = false;
    Input2();
    epr = "(" + epr1 + ")" + "~" + "(" + epr2 + ")";
    preprea();
    to_suffix();
    pcnf = "";
    Print(1);
    int len = pcnf.length();
    if(len == 0)
    {
        flag1 = true;
        printf("\n兩命題公式等價\n");
    }
    if(!flag1)
    {
        epr = "(" + epr1 + ")" + ">" + "(" + epr2 + ")";
        preprea();
        to_suffix();
        pcnf = "";
        Print(1);
        len = pcnf.length();
        if(len == 0)
        {
            flag2 = true;
            cout << endl << epr1 << endl;
            printf("蘊含\n");
            cout << epr2 << endl;
        }

        epr = "(" + epr2 + ")" + ">" + "(" + epr1 + ")";
        preprea();
        to_suffix();
        pcnf = "";
        Print(1);
        len = pcnf.length();
        if(len == 0)
        {
            flag2 = true;
            cout << endl << epr2 << endl;
            printf("蘊含\n");
            cout << epr1 << endl;
        }
    }
    if(!flag1 && !flag2)
        printf("兩命題公式既不等價又不存在蘊含關係\n");
}

void solve() //解決邏輯問題
{
    printf("問題描述:\n");
    printf("一對夫妻帶著他們的一個孩子在路上碰到一個朋友.\n");
    printf("朋友問孩子:\"你是男孩還是女孩?\"朋友沒聽清孩子的回答.\n");
    printf("孩子父母中某一個說,我孩子回答的是\"我是男孩\",另一個接著說:\"這孩子撒謊,她是女孩.\"\n");
    printf("這家人中男性從不說謊,而女性從來不連續說兩句真話,也不連續說兩句假話.\n");
    printf("試問這小孩性別,以及誰是其父親,誰是其母親?\n\n");
    printf("解決如下, 設:\n\n");
    printf("P表示命題:第一個說話的是父親\n");
    printf("Q表示命題:第一個人說的話為真\n");
    printf("R表示命題:第二個人說的第一句為真\n");
    printf("S表示命題:第二個人說的第二句為真\n");
    printf("根據已知矛盾資訊,我們可以得到以下幾種矛盾情況\n");
    printf("1. P&!Q        (即第一個說話的是父親,父親說謊)\n");
    printf("2. P&Q         (即第一個說話的是父親,父親沒說謊,此時若母親兩句話都真或都假\n");
    printf("                則與已知矛盾,若一真一假,則均與父親所說的矛盾)\n");
    printf("3. !P&(!(R&S)) (即第二個說話的是父親,兩句中有假話)\n");
    printf("4. !P&Q        (即第一個說話的是母親,且母親說的是真的,則因為父親不說假話)\n");
    printf("                ,因此父親的話為真與母親的話矛盾\n");
    printf("得到矛盾的四種情況,把他們或在一起得到表示式:\n");
    printf("(P&!Q)|(P&Q)|(!P&(!(R&S)))|(!P&Q)\n");
    printf("求解該表示式的真值表:\n");
    epr = "(P&!Q)|(P&Q)|(!P&(!(R&S)))|(!P&Q)";
    preprea();
    to_suffix();
    Print(0);
    printf("\n從真值表可以看出只有0 0 1 1的時候沒有矛盾\n");
    printf("即母親先說且說謊,父親後說沒說謊,於是可得到答案:\n\n");
    printf("第一個說話的是母親\n");
    printf("第二個說話的是父親\n");
    printf("孩子是女孩\n");
}

void Menu() //選單
{
    printf("\n歡飲使用\n");
    printf("請選擇操作:\n");
    printf("1 - 輸出真值表\n");
    printf("2 - 輸出主合取正規化\n");
    printf("3 - 輸出主析取正規化\n");
    printf("4 - 比較兩命題公式是否等價或蘊含\n");
    printf("5 - 解決題目中的邏輯問題\n");
    printf("6 - 退出\n");
}

int main()
{
    Menu();
    while(true)
    {
        char tp[2]; //選擇操作
        scanf("%s", tp);
        if(tp[0] > '6' || tp[0] < '1')
        {
            printf("錯誤輸入\n");
            continue;
        }
        if(tp[0] == '1')
            output_table();
        else if(tp[0] == '2')
            output_pcnf();
        else if(tp[0] == '3')
            output_pdnf();
        else if(tp[0] == '4')
            judge_two();
        else if(tp[0] == '5')
            solve();
        else if(tp[0] == '6')
        {
            printf("謝謝使用\n");
            break;
        }   
        Menu();
    }
}




相關文章