⇑ ⇑ ⇑ ⇑ ⇑ \Uparrow\Uparrow\Uparrow\Uparrow\Uparrow ⇑⇑⇑⇑⇑ 上方专栏内有更多内容 ⇑ ⇑ ⇑ ⇑ ⇑ \Uparrow\Uparrow\Uparrow\Uparrow\Uparrow ⇑⇑⇑⇑⇑
扩展OBDD
集合的OBDD表示
对于一个集合的子集来说,可以用一个特征函数来定义。在此定义中,每个元素可以用二进制位编码来代表。而函数取1则代表此子集存在。
例如给出集合
{
p
0
,
p
1
,
p
2
,
p
3
,
p
4
,
p
5
}
\{p_0,p_1,p_2,p_3,p_4,p_5\}
{p0,p1,p2,p3,p4,p5}
和编码
p
0
=
x
2
′
x
1
′
x
0
′
p
1
=
x
2
′
x
1
′
x
0
p
2
=
x
2
′
x
1
x
0
′
p
3
=
x
2
′
x
1
x
0
p
4
=
x
2
x
1
′
x
0
′
p
5
=
x
2
x
1
′
x
0
\begin{matrix} p_0=x_2'x_1'x_0'&p_1=x_2'x_1'x_0&p_2=x_2'x_1x_0'&\\p_3=x_2'x_1x_0&p_4=x_2x_1'x_0'&p_5=x_2x_1'x_0& \end{matrix}
p0=x2′x1′x0′p3=x2′x1x0p1=x2′x1′x0p4=x2x1′x0′p2=x2′x1x0′p5=x2x1′x0
(
x
0
,
x
1
,
x
2
∈
{
0
,
1
}
x_0,x_1,x_2\in\{0,1\}
x0,x1,x2∈{0,1},这里省略布尔
∗
*
∗运算符号,不是排列)
现在有子集
{
p
0
,
p
2
,
p
3
}
\{p_0,p_2,p_3\}
{p0,p2,p3}
那么,对于变量
{
x
0
,
x
1
,
x
2
}
∈
{
0
,
1
}
\{x_0,x_1,x_2\}\in\{0,1\}
{x0,x1,x2}∈{0,1}
可以构造出一个函数
ϕ
{
p
0
,
p
2
,
p
3
}
(
x
0
,
x
1
,
x
2
)
=
x
2
′
x
1
′
x
0
′
+
x
2
′
x
1
x
0
′
+
x
2
′
x
1
x
0
\phi_{\{p_0,p_2,p_3\}}(x_0,x_1,x_2)=x_2'x_1'x_0'+x_2'x_1x_0'+x_2'x_1x_0
ϕ{p0,p2,p3}(x0,x1,x2)=x2′x1′x0′+x2′x1x0′+x2′x1x0
当
ϕ
=
1
\phi=1
ϕ=1即表示此子集成立。
可以看出,此表示方法由原来n个变量降维到
l
o
g
2
(
n
)
log_2(n)
log2(n)个变量。
对于这个特征函数,可以发现,就是一个布尔函数,既然是布尔函数自然就可以用OBDD来表示。
特征函数的操作
特征函数既然是表示集合,那么集合的操作也应该可以降维到函数的运算。
(以下是布尔符号运算)
空集:
χ
∅
=
0
\chi_\emptyset=0
χ∅=0
并集运算:
χ
S
∪
T
=
χ
S
+
χ
T
\chi_{S\cup T}=\chi_S+\chi_T
χS∪T=χS+χT
交集运算:
χ
S
∩
T
=
χ
S
∗
χ
T
\chi_{S\cap T}=\chi_S*\chi_T
χS∩T=χS∗χT
差运算:
χ
S
−
T
=
χ
S
∗
(
χ
T
)
′
\chi_{S- T}=\chi_S*(\chi_T)'
χS−T=χS∗(χT)′ (当
S
⊂
T
S\subset T
S⊂T时
χ
S
−
T
=
0
)
\chi_{S- T}=0)
χS−T=0)
矩阵的OBDD
OBDD可以表示关系矩阵(元素只有0,1)。
矩阵行元素和列元素同样是集合。
需要注意的是
在OBDD表示矩阵行列元素与我们通常做法不同。
通常我们用
X
0
,
X
1
,
…
X
n
X_0,X_1,…X_n
X0,X1,…Xn表示第1,2,…,n行
在OBDD中,依然可以把
X
0
,
X
1
,
…
X
n
X_0,X_1,…X_n
X0,X1,…Xn看作集合
X
=
{
X
0
,
X
1
,
…
X
n
}
X=\{X_0,X_1,…X_n\}
X={X0,X1,…Xn}编码得到新集合
X
=
{
(
x
t
′
x
t
−
1
′
…
x
0
′
)
,
(
x
t
′
x
t
−
1
′
…
x
0
)
,
…
…
,
(
x
t
x
t
−
1
…
x
0
)
}
,
t
≤
l
o
g
2
(
n
)
X=\{(x_t'x_{t-1}'…x_0'),(x_t'x_{t-1}'…x_0),……,(x_tx_{t-1}…x_0)\},t\le log_2(n)
X={(xt′xt−1′…x0′),(xt′xt−1′…x0),……,(xtxt−1…x0)},t≤log2(n)
对Y同理。
于是矩阵中元素可以表示为笛卡尔积
X
×
Y
=
{
x
t
x
t
−
1
…
x
0
y
t
y
t
−
1
…
y
0
∣
x
i
,
y
j
∈
{
0
,
1
}
}
X\times Y=\{x_tx_{t-1}…x_0y_ty_{t-1}…y_0|x_i,y_j\in\{0,1\}\}
X×Y={xtxt−1…x0ytyt−1…y0∣xi,yj∈{0,1}}
简单实例
已知有向图G(V,E),给定一个起始节点s和终点t,找出所有从s到t的最短路径。
按照一般的做法可以运用搜索相关的知识。这里介绍OBDD视角。
参考集合的OBDD表示法,对节点V进行编码[
x
0
x
1
x
2
x_0x_1x_2
x0x1x2]
0
=
[
000
]
1
=
[
001
]
2
=
[
010
]
3
=
[
011
]
4
=
[
100
]
0=[000] \quad1=[001]\quad 2=[010]\quad 3=[011]\quad 4=[100]
0=[000]1=[001]2=[010]3=[011]4=[100]
有向图的特征函数可以表示为
f
G
(
v
x
,
v
y
)
=
x
0
′
x
1
′
x
2
′
y
0
′
y
1
′
y
2
+
x
0
′
x
1
′
x
2
′
y
0
′
y
1
y
2
′
+
x
0
′
x
1
′
x
2
y
0
′
y
1
y
2
′
+
x
0
′
x
1
′
x
2
y
0
′
y
1
y
2
+
x
0
′
x
1
′
x
2
y
0
y
1
′
y
2
′
+
x
0
′
x
1
x
2
′
y
0
′
y
1
y
2
f_G(v_x,v_y)=x_0'x_1'x_2'y_0'y_1'y_2\\ +x_0'x_1'x_2'y_0'y_1y_2'\\ +x_0'x_1'x_2y_0'y_1y_2'\\ +x_0'x_1'x_2y_0'y_1y_2\\ +x_0'x_1'x_2y_0y_1'y_2'\\ +x_0'x_1x_2'y_0'y_1y_2
fG(vx,vy)=x0′x1′x2′y0′y1′y2+x0′x1′x2′y0′y1y2′+x0′x1′x2y0′y1y2′+x0′x1′x2y0′y1y2+x0′x1′x2y0y1′y2′+x0′x1x2′y0′y1y2
其中
v
x
,
v
y
v_x,v_y
vx,vy表示编码后的节点。这样特征函数就表示了边。即
f
G
(
v
i
,
v
j
)
=
1
⇔
⟨
v
i
,
v
j
⟩
∈
E
f_G(v_i,v_j)=1 \Leftrightarrow \langle v_i,v_j \rangle \in E
fG(vi,vj)=1⇔⟨vi,vj⟩∈E
对应OBDD
(左边:
f
G
f_G
fG的实际OBDD,右边:将0节点和1节点模仿决策树展开的图。这里展开图作为辅助理解的工具,实际上0节点和1节点都是唯一的节点。)
始点s对应的点的特征函数为
X
s
(
v
)
=
x
0
′
x
1
′
x
2
′
X_s(v)=x_0'x_1'x_2'
Xs(v)=x0′x1′x2′
终点t对应点的特征函数为
X
t
(
v
)
=
x
0
′
x
1
x
2
X_t(v)=x_0'x_1x_2
Xt(v)=x0′x1x2
这里用
X
(
⋅
)
X(\cdot)
X(⋅)表示单独使用
x
0
,
x
1
,
x
2
x_0,x_1,x_2
x0,x1,x2作为变量的函数,相对应的,单独使用
y
0
,
y
1
,
y
2
y_0,y_1,y_2
y0,y1,y2作为变量的函数可以被记为
Y
(
⋅
)
Y(\cdot)
Y(⋅)。
接下来就是搜索操作。
首先我们知道,起始点s与特征函数与(合取)操作的结果就是以起始点s为起点的合法有向边的集合,暂且定义为
f
(
1
)
f^{(1)}
f(1)。(OBDD的合取操作在上一节有说明,忘记了的可以回去再看看)
f
(
1
)
(
v
i
,
v
j
)
:
=
X
s
(
v
i
)
∧
f
G
(
v
i
.
v
j
)
f
(
1
)
(
v
i
,
v
j
)
=
1
⇔
v
i
=
s
&
⟨
v
i
,
v
j
⟩
∈
E
f^{(1)}(v_i,v_j):=X_s(v_i) \land f_G(v_i.v_j)\\ f^{(1)}(v_i,v_j)=1 \Leftrightarrow v_i=s \& \langle v_i,v_j \rangle \in E
f(1)(vi,vj):=Xs(vi)∧fG(vi.vj)f(1)(vi,vj)=1⇔vi=s&⟨vi,vj⟩∈E
得到
f
(
1
)
f^{(1)}
f(1)也就意味着得到对s距离为1的可达点,把它们的特征函数定义为
X
(
1
)
(
v
)
X^{(1)}(v)
X(1)(v)
Y
(
1
)
(
v
)
:
=
∃
x
,
f
(
1
)
(
x
,
v
)
X
(
1
)
(
v
)
:
=
Y
(
1
)
(
v
)
Y^{(1)}(v):=\exist x ,f^{(1)}(x,v)\\X^{(1)}(v):=Y^{(1)}(v)
Y(1)(v):=∃x,f(1)(x,v)X(1)(v):=Y(1)(v)
这里使用
Y
(
1
)
(
v
)
Y^{(1)}(v)
Y(1)(v)作为中转函数。这个中转函数是必要的。因为在OBDD中
x
x
x和
y
y
y表达的意义是不一样的。
接下来重复这些步骤就可以找出距离s步长为2,3,4……的可达点。即递推过程
{
f
(
i
)
(
v
i
,
v
j
)
:
=
X
(
i
−
1
)
(
v
i
)
∧
f
G
(
v
i
.
v
j
)
Y
(
i
)
(
v
)
:
=
∃
x
,
f
(
i
)
(
x
,
v
)
X
(
i
)
(
v
)
:
=
Y
(
i
)
(
v
)
s
.
t
.
X
(
0
)
(
v
)
=
X
s
(
v
)
i
∈
N
\begin{cases} f^{(i)}(v_i,v_j):=X^{(i-1)}(v_i)\wedge f_G(v_i.v_j)\\ Y^{(i)}(v):=\exist x, f^{(i)}(x,v)\\ X^{(i)}(v):=Y^{(i)}(v) \end{cases} s.t. \begin{matrix} X^{(0)}(v)=X_s(v)\\ i\in \mathbb N \end{matrix}
⎩⎪⎨⎪⎧f(i)(vi,vj):=X(i−1)(vi)∧fG(vi.vj)Y(i)(v):=∃x,f(i)(x,v)X(i)(v):=Y(i)(v)s.t.X(0)(v)=Xs(v)i∈N
直到出现边界条件
∃
n
,
X
(
n
)
(
t
)
=
1
\exist n,X^{(n)}(t)=1
∃n,X(n)(t)=1,(
t
t
t指终点)。
(判断有没有环,有没有连通等等操作也可以修改边界条件达成)
连通性是知道了,但是还有那么多额外的边,
X
(
n
)
(
v
)
X^{(n)}(v)
X(n)(v)也不是只有
t
t
t一个点。怎么排除这些多余的路径?反向搜索就可以了。具体过程留给读者思考。我给出一个大致的框架。
{
f
s
h
o
r
t
e
s
t
(
j
)
(
v
i
,
v
j
)
:
=
Y
s
h
o
r
t
e
s
t
(
j
)
(
v
i
)
∧
f
(
j
)
(
v
i
.
v
j
)
X
s
h
o
r
t
e
s
t
(
j
−
1
)
(
v
)
:
=
∃
y
,
f
s
h
o
r
t
e
s
t
(
j
)
(
v
,
y
)
Y
s
h
o
r
t
e
s
t
(
j
−
1
)
(
v
)
:
=
X
s
h
o
r
t
e
s
t
(
j
−
1
)
(
v
)
s
.
t
.
Y
s
h
o
r
t
e
s
t
(
n
)
(
v
)
=
X
t
(
v
)
j
∈
N
,
j
<
n
\begin{cases} f^{(j)}_{shortest}(v_i,v_j):=Y^{(j)}_{shortest}(v_i)\wedge f^{(j)}(v_i.v_j)\\ X^{(j-1)}_{shortest}(v):=\exist y, f^{(j)}_{shortest}(v,y)\\ Y^{(j-1)}_{shortest}(v):=X^{(j-1)}_{shortest}(v) \end{cases} s.t. \begin{matrix} Y^{(n)}_{shortest}(v)=X_t(v)\\ j\in \mathbb N,j<n \end{matrix}
⎩⎪⎨⎪⎧fshortest(j)(vi,vj):=Yshortest(j)(vi)∧f(j)(vi.vj)Xshortest(j−1)(v):=∃y,fshortest(j)(v,y)Yshortest(j−1)(v):=Xshortest(j−1)(v)s.t.Yshortest(n)(v)=Xt(v)j∈N,j<n
观察式子可知,这是一个反向递推的过程,实践中完全可以把
X
s
h
o
r
t
e
s
t
(
j
)
,
Y
s
h
o
r
t
e
s
t
(
j
)
,
f
s
h
o
r
t
e
s
t
(
j
)
X^{(j)}_{shortest},Y^{(j)}_{shortest},f^{(j)}_{shortest}
Xshortest(j),Yshortest(j),fshortest(j)直接覆盖到原来的
X
(
j
)
,
Y
(
j
)
,
f
(
j
)
X^{(j)},Y^{(j)},f^{(j)}
X(j),Y(j),f(j)。
至此我们可以用我们已知的方法来找出最短路径链。下面是一种dfs的思路。
typedef OBDD_Of_Points OP
typedef OBDD_Of_Edges OE
Array[Chain] ans;
int n;//由前面的步骤算出来的n
//初始的u=s(初始点),i=0
function DFS(Array[OP] X,Array[OE] f,Chain c,Point u,int i)
if i==n
ans.add(c.copy())
return
Array[Point] any_v=X[i+1].toPointArray() //将OBDD转换为点集
for Point v in any_v
if f[i+1].judge(u,v)==1
c.add(v);
DFS(X,f,c,v,i+1)
c.remove(v)