代码改变世界

子集算法的完整数学推导过程

2010-05-19 23:33  SeasonLee  阅读(4862)  评论(5编辑  收藏  举报

前言

自从笔者的《从离散数学到编译原理--百姓网编程题后序》一文发布以后,得到众多网友的肯定和赞赏,你们的支持是笔者写blog的源动力。而同时也由于笔者的水平所限,有网友指出在《百姓网编程题后序》中存在着不严谨的数学证明过程,而这些确实是笔者粗心的过失,实在感到十分羞耻。为了不给读者造成误解,笔者决定撰写本文来对《百姓网编程题后序》中的数学证明给出补充,以消除读者心中的疑念。

 

本文假设读者有一定的离散数学(Discrete Mathematics)基础,特别是对数学逻辑(Mathematical Logic)和集合论(Set Theory)有一定的认识,否则本文会让你难于理解。由于大陆与港台在专用术语的翻译上存在一定差异,在一些大陆专用的中文术语中,我都会在后面加上英文的对照版本以及相应的wiki link,港台的读者可以通过wiki link来消除你对该中文术语的困惑。

 

在这里笔者要感谢国家。感谢网友ff1在《百姓网编程题后序》中指出的错误,没有您的意见反馈,笔者不会知道在文中所犯下的错误。同时希望您能在本文中继续给出您宝贵的意见。感谢wukegui同学在数学证明上给我的帮助,您让我学回很多遗忘已久的数学知识。感谢赖总王建硕先生和mikster对本文的review和意见。

 

限于笔者水平,本文中的疏漏和错误在所难免,欢迎批评和指正。同时也希望能与您进行更多深入的交流,请联系我myonlylee@gmail.com或者@season_lee


 

正确还是错误?

笔者曾在《百姓网编程题后序》一文中简单证明过以下等价关系对于集合成立。

原命题 等价命题
A ⊆ (B ∩ C) (A ⊆ B) ∧ (A ⊆ C)
A ⊆ (B ∪ C) (A ⊆ B) ∨ (A ⊆ C)
(A ∪ B) ⊆ C (A ⊆ C) ∧ (B ⊆ C)
(A ∩ B) ⊆ C (A ⊆ C) (B ⊆ C)

 

但是,以上等价命题都是成立的吗?

否。对于红色字体的2个命题,它们的等价命题并不成立,而另外2个黑色字体的等价命题是成立的。

为了证明等价命题不成立,我们只要能找到一个反例就可以了。

 

>>对于命题A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C),考虑以下集合图。

集合图 图1

 

在图中,集合A有一部分属于B,而另一部分属于C,在这种特殊情况下,(A ⊆ B)不成立,(A ⊆ C) 也不成立,而A ⊆ (B ∪ C)却是成立的,所以等价关系A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C)是错误的。

 

>>对于命题(A ∩ B) ⊆ C ⇔ (A ⊆ C) (B ⊆ C),考虑以下集合图。

集合图2 图2

 

在图中,A,B,C完全不存在子集的关系,但是(A ∩ B) ⊆ C确实成立。而等价命题(A ∩ B) ⊆ C(A ⊆ C) (B ⊆ C)是错误的。


重要推论和定律

下面是笔者自命名的表达式和推论,是不会在数学书籍或者文献中出现的,这些表达式名称是作为本文的数学约定,仅仅为了帮助读者的理解,将使用下划线来做标识,而数学定律均使用加粗字体来表示。

 

在本文中,应该理解为维(dimension)或者变量(variable),它是对应于查询语句中的一个字段名称。

一元单项式,即 x > 10或者y < 5,而不含∪或者∩。

一元多项式,即 一个∩或者一个∪或者两者或者多个∩、∪组成的关于x的表达式,即(x>10∪x<5) ∩ ( x > 6 ∩ x < 9)。

一元多项并集式,即 一个∪或者多个∪组成的关于x的表达式,即 x > 10 ∪ x < 5。

一元多项交集式,即 一个∩或者多个∩组成的关于x的表达式,即  x > 6 ∩ x < 9。

 

下面是推论:

A ⊆ (B ∩ C) ⇔ (A ⊆ B) ∧ (A ⊆ C)  对于任意集合都成立。  (推论1)

(A ∪ B) ⊆ C ⇔ (A ⊆ C) ∧ (B ⊆ C)  对于任意集合都成立。  (推论2)

A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C) 当且仅当B,C均为一元多项式(B、C不能描述同一个元)的时候成立,我们在下面称之为推论3

(A ∩ B) ⊆ C ⇔ (A ⊆ C) (B ⊆ C) 当且仅当A,B均为一元多项式(A、B不能描述同一个元)的时候成立,我们在下面称之为推论4

 

误区1

为什么A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C)对于A,B,C为任意集合时不成立,而当且仅当B,C均为一元多项式时就成立?


首先回答“A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C)对于A,B,C为任意集合时不成立”,其实在例图1中已经给出例子,也就是当A有一部分属于B,而另一部分属于C的时候,正如例图1中所示,A不属于B也不属于C,但是A却是B与C并集的子集。

然后“A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C) 当且仅当B,C均为一元多项式”,其实这里不难理解B、C实际需要满足什么条件。如果B只作一维上(假设是x维)的约束,而C作另外一维上(假设是y维)的约束,那么A要属于B、C的并集,A只需要满足x维上的约束或者满足y维上的约束。


 

为帮助读者的理解,请考虑以下特例:
x>10 ∩ x < 30  ⊆ (x >0 ∩ x <= 15) ∪ (x>15 ∩ x < 40)
x>10 ∩ x < 30不是x >0 ∩ x <= 15的子集,也不是x>15 ∩ x < 40的子集,但是x>10 ∩ x < 30却是这两者的并集的子集。
就是说对于ab ⊆ mn ∩ pq的分拆,不管是ab  ⊆ mn  ∨ ab ⊆ pq还是ab  ⊆ mn  ∧ ab ⊆ pq,最终的布尔表达式都是FALSE,但事实上应该是TRUE的。

所以A ⊆ (B ∪ C) ⇔ (A ⊆ B) ∨ (A ⊆ C) 并不是一个真命题,请慎用!同理(A ∩ B) ⊆ C(A ⊆ C) (B ⊆ C)也不是一个真命题。

 

接下来,我将使用到一些集合论中的法则(你可以在《离散数学及其应用》一书中找到)。其中符号A表示A的绝对补集(Complement)。

幂等律(Idempotent Laws):

A ∩ A = A

A ∪ A = A

结合律(Associative Laws):

A ∩ (B ∩ C) = (A ∩ B) ∩ C

A ∪ (B ∪ C) = (A ∪ B) ∪ C

分配律(Distributive Laws):

A ∩ (B ∪ C) = (A ∩ B) ∪ (A ∩ C)

A ∪ (B ∩ C) = (A ∪ B) ∩ (A ∪ C)

重补集律(Complementation Law):

(A)=  A 

德·摩根律(De Morgan’s Laws):

(A ∪ B)C = AC ∩ BC

(A ∩ B)C = AC ∪ BC

子集的逆否律(读者可以使用归纳法把这个证明出来):

A ⊆ B ⇔ BC ⊆ AC


子集算法推导过程(了解过的朋友请跳过这一节)

由于推论3推论4无法适用于集合的所有情况,所以在以下算法中,我们需要对查询表达式作特殊的处理,进行若干变换,最终转化成适用于推论3推论4的多个表达式,再使用一元多项交集式一元多项并集式的子集判断算法,以完成完整的子集判断。

我们要把任意的查询表达式化为(a ∩ b ∩ c ∩….) ∪ (d ∩ e ∩ f ∩….) ∪…..这样多个交集所组成的并集,把它称为标准式。注意对于式子中的a,b,c,d……都是一元单项式

如 (x > 10 ∪ y < 5) ∩ z < 0:

标准式为: (x > 10 ∩ z < 0) ∪ (y < 5 ∩ z < 0)                               分配律

 

>>步骤1

假设集合A表示成 (a∩b) ∪ (c∩d),集合B表示成 (m∩n) ∪ (p∩q),要判断A ⊆ B ,则:

(a∩b) ∪ (c∩d) ⊆ (m∩n) ∪ (p∩q)
[(m ∩ n) ∪ (p ∩ q)]C ⊆ [(a ∩ b) ∪ (c ∩ d)]C                                                    逆否律
(mC ∪ nC )∩(p∪ qC)⊆(a∪ bC)∩(c∪ dC)                                             德·摩根律
(mC ∩ pC )∪(mC ∩ qC)∪(n∩ pC)∪(n∩qC)⊆ (a∪bC)∩(c∪dC)                      分配律 

[mC ∩ pC ⊆ (aC ∪ bC )∩(cC ∪ dC )] ∧ [mC ∩ qC ⊆ (aC ∪ bC )∩(cC ∪ dC)] ∧ [nC ∩ pC ⊆ (aC ∪ bC )∩(cC ∪ dC ) ]∧ [nC ∩ qC ⊆ (aC ∪ bC )∩(cC∪ dC )]                                                                     推论1

 

>>步骤2

展开由于上式中是多项取逻辑与的关系,下面我们只考虑其中一项: 

mC ∩ pC ⊆ (aC ∪bC )∩(cC ∪dC )

mC ∩ pC ⊆ (aC ∪ bC ) ∩ (cC ∪ dC )
(mC ∩ pC ⊆ aC ∪ bC )∧ (mC ∩ pC ⊆ cC ∪ dC )                        推论1

(a ∩ b ⊆ m ∪ p) ∧ (c∩d ⊆ m∪p )                                       逆否律

因此结合步骤1和步骤2,

我们可以把原式:

标准式

(a∩b) ∪ (c∩d) ⊆ (m∩n) ∪ (p∩q)                                         

变换为:

中间式

(a ∩ b ⊆ m ∪ p) ∧ (c∩d ⊆ m∪p ) ∧ (a∩b ⊆ m∪q ) ∧ (c∩d ⊆ m∪q ) ∧ (a∩b ⊆ n∪p ) ∧ (c∩d ⊆ n∪p ) ∧ (a∩b ⊆ n∪q ) ∧ (c∩d ⊆ n∪q )                                    

 

>>步骤3

我们着重考虑上面式子的一项a ∩ b ⊆ m ∪ p,因为a、b、m、p都是一元单项式
假设a∩b和m∪p都是关于x,y的二元多项表达式。我们可以很轻松的合并元x,y:

a ∩ b ⊆ m ∪ p
[(x的多项交集式)∩(y的多项交集式)] ⊆ [(x的多项并集式)∪(y的多项并集式)]

下面X1表示x的x的多项交集式,用Y1表示y多项交集式,X2表示x的多项并集式,用Y2表示y的多项并集式。

a ∩ b ⊆ m ∪ p

X1∩Y1 ⊆ X2∪Y2

(X1∩Y1 ⊆ X2) ∨ (X1∩Y1 ⊆ Y2 )                                         使用推论3推论4

//Y1 ⊆ X2,Y1与X2并不是描述同一个元,所以不存在子集关系。

(X1 ⊆ X2)  ∨  (Y1 ⊆ X2) ∨ (X1 ⊆ Y2)  ∨  (Y1 ⊆ Y2)

(X1 ⊆ X2)  ∨  (Y1 ⊆ Y2)

 

>>综述

根据步骤1、2、3,集合A:(a∩b) ∪ (c∩d),集合B:(m∩n) ∪ (p∩q),

A ⊆ B

标准式

[(a∩b) ∪ (c∩d)] ⊆ [(m∩n) ∪ (p∩q) ]                                          

可以先化简为:

中间式

(a ∩ b ⊆ m ∪ p) ∧ (c∩d ⊆ m∪p ) ∧ (a∩b ⊆ m∪q ) ∧ (c∩d ⊆ m∪q ) ∧ (a∩b ⊆ n∪p ) ∧ (c∩d ⊆ n∪p ) ∧ (a∩b ⊆ n∪q ) ∧ (c∩d ⊆ n∪q )                                            

再利用合并相同元的并集和交集:

最终式

[(X1 ⊆ X2)  ∨  (Y1 ⊆ Y2)] ∧ [(V1 ⊆ V2)  ∨  (W1 ⊆ W2)] ∧ [(J1 ⊆ J2)  ∨  (K1 ⊆ K2)] ∧…...∧[(E1 ⊆ E2)  ∨  (F1 ⊆ F2)]                                                        

上式的X1,Y1,V1,W1,J1,K1……E1,F1均为一元多项交集式,而X2,Y2,V2,W2,J2,K2……E2,F2均为一元多项并集集式

 


浅谈一元多项交集式一元多项并集式的子集判断。

在上一节中,我们把集合A与集合B的子集关系化简为:由一元多项交集式一元多项并集式的子集关系,任意与或组合,形成的布尔表达式。

在下面我将浅谈一元多项交集式一元多项并集式的子集判断。

举例:

一元多项交集式:集合A:x > 10 ∩ x > 15 ∩ x < 30……

一元多项并集式: 集合B: x>10 ∪ x>15 ∪ x< 30……

 

简单来讲集合A是表示一个区间,而集合B是表示多个区间,要判断 A ⊆ B,只需要遍历集合B中的所有区间,看是否能找到一个包含集合A的区间,如果找到则 A ⊆ B成立,如果找不到则A ⊆ B不成立。

下面使用伪代码(其实是python代码):

#集合A只包含一个区间
a_interval = set_A[0]
is_sub_set
= False
for interval in set_B:
#a_interval 在 interval 里面
if is_sub_interval(a_interval, interval) == True:
is_sub_set
= True
break

 


总结

1.主要由于推论3推论4无法适用于集合的所有情况,所以在本文中,需要对查询表达式进行若干变换,最终转化成适用于推论3推论4的多个表达式,再使用一元多项交集式一元多项并集式的子集判断算法,以完成完整的子集判断。

2.推导过程比较繁琐,其实你只需要关注标准式--中间式--最终式之间的关系即可,推导过程只为了证明数学上的完备。

3.由于本文用到的算法比我在上篇中的算法要复杂多,限于本人水平和时间,暂时未能提供完整的程序代码。有兴趣的读者可以尝试把自己算法实现出来,并发布出来让大家参考参考。

 


 

更多

其实在原题下面的评论中,已经有不少更好的解法,下面我简单的说说。

mikster使用了多维空间的思想来描述了这道题,下面引用他本人的原话:

实际上是高维空间里的覆盖问题,离散化后逐个检查是朴素做法,复杂度取决于每次排除后的分裂速度。逆波兰是将空间立方体化的手段而已。如果高维线段树空间和速度都不好,所以朴素做法还是最好的,但是看起来还是不能满足要求
我比较怀疑这个问题的下限。

 

而另外一位朋友阿暖就使用了更为巧妙的中点检测法,对所有的区间作验证,有兴趣的朋友可以去看看:

A:(age>21)and(level>1)
B:(age>20)or(level>0)

首先 把每个字段分段
age :分成 [0 ,20 ,21] 三段
level :分成 [0, 1] 两端
这很容易实现
然后 每段取中点 作为测试值
age: [10,20.5,22]
level: [0.5,2]
然后 age*level 产生测试用例
放到A和B里测试
如果存在一个值属于A却不属于B......

怎么样 暴力吧
这叫乱拳打死老师傅
想想看 效率反而可能是最高的