[离散] 编程求命题公式真值表

[离散] 编程求命题公式真值表

概述

真值表是离散数学中的一个重要概念,由真值表我们能求得任意命题公式的主析取范式和主合取范式。下面我们先来回顾一下真值表的概念:

将命题公式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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章