在布尔代数或集合代数中,如何使用已有表达式以最简步骤凑出目标表达式
作者在编写本文时只有高中学历,所以本文力求使高中及以下学历的人看懂。
这同时也是因为本文不敢讨论高深的数学难题,只打算实现一个程序。如果你发现本文有任何在数学方面可以改进的内容(我知道有很多)或者任何你无法看懂的地方,请不要吝啬,给我指出来吧,谢谢你!
标题的这个问题看起来很奇怪,但是考虑一下布尔表达式的化简——只有单个字母也构成一个表达式不是吗?所以布尔表达式的化简也是这个问题的一部分,其中已有的表达式都是单个字母,目标表达式则是要被化简的表达式的最简形式。
虽然听起来很牵强,可能确实也很牵强,但是这个问题确实存在,而且困扰了我一段时间。
什么是布尔表达式
布尔代数就是只有 \(\mathrm{T}\) 和 \(\mathrm{F}\) (或称真和假)这两个数且只有 \(\land \lor \lnot\) (或称与、或、非,在计算机语言中常用 &
、 |
、 !
表示)之类的运算的一套体系。
本文的主题是布尔表达式,就是说本文主要是在操作 \((A \land B) \lor (\lnot A) 且 A, B \in \{\mathrm{T},\mathrm{F}\}\) 这样子的代数表达式。
什么是集合表达式
请翻高一课本。
布尔表达式的结果的表示
布尔表达式有一个显而易见的特点,就是因为布尔代数中的“数”是有限的——只有真和假——所以虽然我们使用代数字符来表示任意数,但是结果是有限的。
比如 \(\{a+b|a,b\in R\}\) 中的元素是无限的,但 \(\{a\lor b|a,b\in \{\mathrm{T},\mathrm{F}\}\}\) 中的元素是有限的。
通过枚举 \(a\) 和 \(b\) ,我们在这里可以使用数列 \(\mathrm{F},\mathrm{T},\mathrm{T},\mathrm{T}\) 来表示 \(a\lor b\) 这个表达式的结果。
你可能猜到这是什么意思了:在这里我们还可以用 \(\mathrm{F},\mathrm{T},\mathrm{F},\mathrm{T}\) 来表示 \(a\) 这个表达式的结果,用 \(\mathrm{F},\mathrm{F},\mathrm{T},\mathrm{T}\) 来表示 \(b\) 这个表达式的结果。或者更方便一点,我们说这两个数列就分别代表了这两个字母。
这些数列的长度为 \(4\) ,因为 \(a \lor b\) 这个表达式中有两个不同的字母。如果是 \((a \lor b) \land c\) 这个表达式,就要用 \(\mathrm{F},\mathrm{F},\mathrm{F},\mathrm{F},\mathrm{F},\mathrm{T},\mathrm{T},\mathrm{T}\) 这个长度为 \(8\) 的数列来表示了——代表 \(a\) 和 \(b\) 的数列分别重复了自己一次,代表 \(c\) 的数列则是 \(\mathrm{F},\mathrm{F},\mathrm{F},\mathrm{F},\mathrm{T},\mathrm{T},\mathrm{T},\mathrm{T}\) ,这样才能把结果都表示出来。
如果你没有总结出我上面所说数列的规律,我这里用更标准的语言描述一下:
如果表达式里有 \(L\) 个不同的字母,那数列的长度就应该是 \(2^L\) 。
对于任意字母 \(\alpha\) ,如果它是字母表里的第 \(k\) 个,那代表它的数列即为
其中 \(\lfloor x \rfloor\) 表示向下取整,类似还有 \(\lceil x \rceil\) 是向上取整。
而想要表示任意表达式 \(f(a, b, c, \dots, \alpha)\) 的结果,可以用数列
为了之后方便思考,也是因为我们在讨论写程序的问题,我们可以把数列用二进制数来表示。比如 \(a \lor b\) 的结果数列是 \(\mathrm{F},\mathrm{T},\mathrm{T},\mathrm{T}\) ,用二进制数表示就是 0111
,简洁还直观。
布尔代数和集合代数的联系
如果你看了标题(没看的话现在去看),你可以注意到我把布尔代数和集合代数放在了一起,这是因为我发现在这个问题中,处理布尔表达式和集合表达式用的是相同的方法。
还是以 \(a\lor b\) 为例,如果 \(a\) 表示“一个元素是否在集合 \(A\) 里”,而 \(b\) 表示“一个元素是否在集合 \(B\) 里”,那么 \(a \lor b\) 就表示“一个元素是否在 \(A \cup B\) 里”。
并且我们也同样可以用 0111
来表示 “一个元素是否在 \(A \cup B\) 里”的结果——二进制数的四位分别表示“若这个元素既不在 \(A\) 也不在 \(B\) 里”“若这个元素在 \(A\) 但不在 \(B\) 里”“若这个元素在 \(B\) 但不在 \(A\) 里”和“若这个元素又在 \(A\) 又在 \(B\) 里”。
如果你没法一眼看出来,可以在脑海中想象下面这样的韦恩图:
--------------------------
| U |
| /-------\/-------\ |
| / /\ \ |
| | A | | B | |
| \ \/ / |
| \-------/\-------/ |
--------------------------
而二进制数的四位就是当元素分别在这个韦恩图中四块连续区域——即 \(\complement_U (A \cup B)\) 、 \(\complement_U B \cap A\) 、 \(\complement_U A \cap B\) 和 \(A \cap B\) ——的情况。
这样子,还可以有一种更简单的理解:就是 0111
表示了 \(A \cup B\) 覆盖了全集 \(U\) 中全部四块连续区域的哪几块。
以结果的角度思考
不难发现,对于相同的结果,可以有不同的表达式。比如 \((\complement_U B \cap A) \cup (A \cap B)\) 和 \(A\) 这两个表达式的结果相同,所以可以说 \((\complement_U B \cap A) \cup (A \cap B) = A\) 。
那对我们来说,表达式长啥样就不重要了(因为有的时候长得很复杂),只需要关注结果就行了。
于是我们可以开始思考如何解决标题的问题。因为结果的表示形式是二进制数,所以用结果的角度思考,问题就变成了:如何使用已有数字和“与或非”三种位运算以最简步骤凑出目标数字。
根据前文对结果数列的定义,我们能很轻易地得出一个表达式的结果,不过我没说怎么得到结果对应的表达式。你可能就会问:凑出了目标结果后怎么才能变成目标表达式?
其实这个问题也包含在了标题的问题里。因为单个字母也是一个表达式,所以如果用所有的单字母表达式的结果当作已有结果,那凑出的最简步骤就是目标结果对应的最简表达式。
判断能不能凑出来
在思考如何凑之前,我们可以先来思考如何判断一个目标结果能不能被已有结果凑出来。
观察问题,我们可以猜想到,一个目标结果能被凑出来的充要条件应该是对于每一对在目标结果上不同的两位都至少有一个已有结果在这两位也不同。
我们来试着证明一下。
必要性
必要性的证明很简单。观察我们能做的与或非操作,不难发现,他们都是“批量”的。
也就是说,如果目标结果中有两位不同——比如一位是 1
,而另一位是 0
——那么也必须有至少一个在这两位上不同的已有结果,否则我们凑不出来目标结果。
这是因为我们没有能单独操作这两位的方法,所以如果所有的已有结果在这两位都相同,那我们没办法让这两位变得不同。
充分性
然后我们来证明充分性。充分性还有另一种表述:一组长度相等且在任意两位都至少有一个在这两位不同的已有结果,能凑出相等长度的任意目标结果。
为了证明这一点,我们可以先思考如何凑出特定长度的任意目标结果。
联想到上面对集合代数的理解,最容易想到的方法就是,只要我们有所有的单个连续部分,我们就能用他们来拼凑出任意图形。用计算机的语言来描述就是只要我们有所有的 \(2\) 的幂,我们就能通过位或运算来凑出任意数字。
我们可以把这种 \(2\) 的幂叫做基本结果。
于是,问题就变成了如何证明一组长度相等且在任意两位都至少有一个在这两位不同的已有结果能够通过运算得到所有的基本结果。
这个问题很明显不需要证明,因为我们真能运算得到:基本结果的特征是除了某一位是 1
之外,其他位全是 0
。也就是说,其他的每位跟是 1
的这位都不相同。
只要通过非运算把每个已有结果的这一位都变成 1
,再把这些已有结果全都进行一次与运算,我们就能得到有关这一位的基本结果。
小猜想
我这里还有个小猜想:如果想要凑出长度为 \(l\) 的任意目标结果,那所需要的最少已有结果的个数应该是 \(L=\lceil \log _2 l \rceil\) 。
其中的一组已有结果应该是这样:
你可能会发现这个公式和前面结果的表示那里很相似,这也是为什么我有这个小猜想。
不过我暂时还没有能力验证。
实现
我这里想到的实现是遍历每一位,通过非运算把目标结果和每个已有结果的所遍历位变成 1
。
之后把所有已有结果与起来,就得到一个数,可以称为标志数。这个数的 0
位就是能跟所遍历位不同的位。
用标志数和目标结果与一下,再非一下,再和标志数与一下,就得到了判别数。其中,是 1
的位就是目标结果中需要与所遍历位不同,但标志数却显示其不能不同的那位。
一旦有这种位,也就是判别数一旦大于 \(0\) ,那么目标结果就无法凑出。反之,如果遍历了所有位,判别数都是 \(0\) ,那就说明已有结果可以凑出目标结果。
卡住了
到现在为止,我的研究遇到了瓶颈。
我尝试通过暴力枚举的方法解决这个问题,代码在这,但是效果非常不好。
但是我目前也找不到其他方法了,也可能是我枚举的代码不是很好,总之我先放弃了。
如果你关于这个问题产生了一些灵感,欢迎在评论区说出来。
博客园原文链接:https://www.cnblogs.com/QiFande/p/18608879,转载请注明。
如果你对本篇文章感兴趣,不如来看看肉丁土豆表的其他文章,说不定也有你喜欢的。