-
2021-01-31 12:27:03更多相关内容
-
VF2算法的C++实现
2017-11-19 11:29:43对于VF2代码的C++实现,用的时候要修改一下数据文件的路径 -
VF2, VF3算法
2020-04-21 10:22:32VF算法能够解决有向/无向,有标签/无标签,图同构问题。原文中的符号和VF3算法不同,这里对某些符号采用了更为广泛地符号定义。 给定两个图G1=(V1,E1)G_1=(V_1,E_1)G1=(V1,E1), G2=(V2,E2)G_2=(V_2...1. VF2算法
1.1 基本定义和整体流程
- 原文An Improved Algorithm for Matching Large Graphs。
- VF算法能够解决有向/无向,有标签/无标签,图同构问题。原文中的符号和VF3算法不同,这里对某些符号采用了更为广泛地符号定义。
- 给定两个图 G 1 = ( V 1 , E 1 ) G_1=(V_1,E_1) G1=(V1,E1), G 2 = ( V 2 , E 2 ) G_2=(V_2,E_2) G2=(V2,E2)和一个映射 M ⊂ V 1 × V 2 M \subset V_1 \times V_2 M⊂V1×V2, 当且仅当 M M M是一个双射且对应边也是双射时, G 1 G_1 G1和 G 2 G_2 G2称为同构。
- 作者采用了一种称为State Space Representation (SSR)的方法判断同构。假设当前状态为 s s s,则 M ( s ) M(s) M(s)表示 M M M中与状态 s s s相关的映射的集合。则 M 1 ( s ) M_1(s) M1(s)表示 M ( s ) M(s) M(s)中属于 V 1 V_1 V1中的点构成的集合,同理 M 2 ( s ) M_2(s) M2(s)。 E 1 ( s ) E_1(s) E1(s)表示 E 1 E_1 E1中连接 M 1 ( s ) M_1(s) M1(s)中点的边,同理 E 2 ( s ) E_2(s) E2(s)。
- 通过 M 1 ( s ) M_1(s) M1(s)和 E 1 ( s ) E_1(s) E1(s)我们可以得到 G 1 G_1 G1的子图 G 1 ( s ) G_1(s) G1(s),同理 G 2 ( s ) G_2(s) G2(s)。
- 算法整体流程如下图所示
其中 F ( s , n , m ) F(s,n,m) F(s,n,m)是一个布尔函数,也称为feasibility function。该函数的返回值如果是true,则表明将边 ( n , m ) (n,m) (n,m)加入到当前状态 s s s( s s s状态表示当前的部分映射 M ( s ) M(s) M(s)是满足同构的)后,新状态 s ∗ s^* s∗的部分映射 M ( s ∗ ) M(s^*) M(s∗)仍然满足同构。因此最终的状态可能是 G 1 G_1 G1与 G 2 G_2 G2同构或者两个图的子图同构。返回值为false,则表明 ( n , m ) (n,m) (n,m)不应该加入到当前状态,能够起到剪枝的作用。
1.2 P ( s ) P(s) P(s)的定义
- 给定一个图 G G G和图中的一个点 n n n,我们定义 P r e d ( G , n ) Pred(G,n) Pred(G,n)为 G G G中 n n n的入度邻居节点构成的集合( n n n的前继集合),定义 S u c c ( G , n ) Succ(G,n) Succ(G,n)为 G G G中 n n n的出度邻居节点构成的集合( n n n的后继集合)。
- 我们定义out-terminal集合 T 1 o u t ( s ) T^{out}_1(s) T1out(s)为 G 1 G_1 G1中不属于 M 1 ( s ) M_1(s) M1(s)但属于 M 1 ( s ) M_1(s) M1(s)中节点的后继节点构成的集合。定义in-terminal集合 T 1 i n ( s ) T^{in}_1(s) T1in(s)为不属于 M 1 ( s ) M_1(s) M1(s)但属于 M 1 ( s ) M_1(s) M1(s)中节点的前继节点构成的集合。同理定义 T 2 o u t ( s ) T^{out}_2(s) T2out(s)和 T 2 i n ( s ) T^{in}_2(s) T2in(s)。
-
P
(
s
)
P(s)
P(s)采用如下的方式构造:
(1)如果 T 1 o u t ( s ) T^{out}_1(s) T1out(s)和 T 2 o u t ( s ) T^{out}_2(s) T2out(s)都不为空,
P ( s ) = T 1 o u t ( s ) × { m i n T 2 o u t ( s ) } P(s)=T^{out}_1(s) \times \{min\ T^{out}_2(s)\} P(s)=T1out(s)×{min T2out(s)}
m i n T 2 o u t ( s ) min\ T^{out}_2(s) min T2out(s)表示 T 2 o u t ( s ) T^{out}_2(s) T2out(s)中具有最小label的节点(任意一个排序方法均可)。
(2)如果 T 1 o u t ( s ) T^{out}_1(s) T1out(s)和 T 2 o u t ( s ) T^{out}_2(s) T2out(s)都为空,且 T 1 i n ( s ) T^{in}_1(s) T1in(s)和 T 2 i n ( s ) T^{in}_2(s) T2in(s)均不为空,
P ( s ) = T 1 i n ( s ) × { m i n T 2 i n ( s ) } P(s)=T^{in}_1(s) \times \{min\ T^{in}_2(s)\} P(s)=T1in(s)×{min T2in(s)}
(3)如果四个terminal集合都是空的,
P ( s ) = ( V 1 − M 1 ( s ) ) × { m i n ( V 2 − M 2 ( s ) ) } P(s)=(V_1-M_1(s)) \times \{min\ (V_2-M_2(s))\} P(s)=(V1−M1(s))×{min (V2−M2(s))} - 当出现只有一个in-terminal集合或者只有一个out-terminal集合为空的时候,可以证明状态 s s s不可能构造出最终的同构,因此状态 s s s不需要再继续分析。同时 P ( s ) P(s) P(s)的定义可以保证同一个状态不会被访问两次。
1.3 F ( s , n , m ) F(s,n,m) F(s,n,m)的定义
为了判断 F ( s , n , m ) F(s,n,m) F(s,n,m),算法需要检查所有与 n , m n,m n,m相连的点。
- 对于在 M 1 ( s ) M_1(s) M1(s)和 M 2 ( s ) M_2(s) M2(s)中的节点,算法检查 M 1 ( s ) M_1(s) M1(s)中这些节点和 n n n的出边入边是否与 M 2 ( s ) M_2(s) M2(s)中这些节点和 m m m的出边入边一一对应。
- 对于不在 M 1 ( s ) M_1(s) M1(s)和 M 2 ( s ) M_2(s) M2(s)中的节点,算法计算 T i i n ( s ) T^{in}_i(s) Tiin(s), T i o u t ( s ) T^{out}_i(s) Tiout(s)和 V i − M i ( s ) − T i i n ( s ) − T i o u t ( s ) V_i-M_i(s)-T^{in}_i(s)-T^{out}_i(s) Vi−Mi(s)−Tiin(s)−Tiout(s)中节点的个数。对于 n , m n,m n,m来说,这三个集合的节点数必须相等才有同构的可能性。对于子图同构来说,小图对应的三个集合的节点数必须小于等于大图对应的三个集合的节点数。
- 如果是带label的图,那么 F ( s , n , m ) F(s,n,m) F(s,n,m)还要检查对应的点和边的label是否一致。
1.4 实现细节
- 两个向量
core_1
和core_2
(维度分别为 G 1 G_1 G1和 G 2 G_2 G2中的节点个数)保存当前的映射。具体来说,如果 n n n在 M 1 ( s ) M_1(s) M1(s)中则core_1[n]
保存了 G 2 G_2 G2中与 n n n对应的节点的索引。否则保存了NULL_NODE
。 - 四个向量
in_1, out_1, in_2, out_2
表示terminal集合。具体来说,如果 n n n在 M 1 ( s ) M_1(s) M1(s)或者 T 1 i n ( s ) T^{in}_1(s) T1in(s)中,则in_1[n]
为非零值。存储的实际值为当前状态 s s s在SSR树中的深度。
2. VF3算法
2.1 基本定义和简要说明
- 原文Challenging the Time Complexity of Exact Subgraph Isomorphism for Huge and Dense Graphs with VF3 https://mivia.unisa.it/datasets/graph-database/vf3-library/
- VF3算法在VF2算法的基础上增加了分类的概念,将节点按照某些属性(出度入度,标签等属性)分类,然后确保两个不属于同一个类的节点,是无法加入到匹配集中。这样又进一步裁剪了搜索空间。VF3主要关注的是sub-graph同构, G 1 G_1 G1表示一个小图,也称为一个pattern, G 2 G_2 G2是一个大图,也称为目标图。
- 定义 λ v : V → L v \lambda_v: V\rightarrow L_v λv:V→Lv为节点到标签的映射, λ e : E → L e \lambda_e: E\rightarrow L_e λe:E→Le为边到标签的映射。 N G 1 N_{G_1} NG1表示 G 1 G_1 G1中节点的遍历顺序。
- 定义 ψ : V 1 ⋃ V 2 → C \psi : V_1 \bigcup V_2 \rightarrow C ψ:V1⋃V2→C,表示给每个节点赋予一个类别 c i ∈ C = { c 1 , c 2 , ⋯ , c q } c_i \in C=\{c_1,c_2,\cdots,c_q\} ci∈C={c1,c2,⋯,cq},使得 ( u , v ) ∈ M ⇒ ψ ( u ) = ψ ( v ) (u,v) \in M \Rightarrow \psi(u)=\psi(v) (u,v)∈M⇒ψ(u)=ψ(v)。
- P ~ 1 ( s ) \widetilde{P}_1(s) P 1(s)相当于 T 1 i n ( s ) T^{in}_1(s) T1in(s), S ~ 1 ( s ) \widetilde{S}_1(s) S 1(s)相当于 T 1 o u t ( s ) T^{out}_1(s) T1out(s)。定义 C i ( V ) = { u ∈ V : ψ ( u ) = c i , i = 1 , ⋯ , q } C_i(V)=\{u \in V : \psi(u)=c_i, i=1,\cdots,q\} Ci(V)={u∈V:ψ(u)=ci,i=1,⋯,q}。表示 V V V中属于类别 c i c_i ci的节点的集合。 V ~ 1 ( s ) = V 1 − M 1 ( s ) − P ~ 1 ( s ) − S ~ 1 ( s ) \widetilde{V}_1(s)=V_1-M_1(s)-\widetilde{P}_1(s)-\widetilde{S}_1(s) V 1(s)=V1−M1(s)−P 1(s)−S 1(s)。
- 根据类别,细化terminal集合。定义 P ~ 1 c i ( s ) = { u ∈ P ~ 1 ( s ) : ψ ( u ) = c i } \widetilde{P}^{c_i}_1(s) =\{u \in \widetilde{P}_1(s) : \psi(u)=c_i\} P 1ci(s)={u∈P 1(s):ψ(u)=ci}, S ~ 1 c i ( s ) = { u ∈ S ~ 1 ( s ) : ψ ( u ) = c i } \widetilde{S}^{c_i}_1(s) =\{u \in \widetilde{S}_1(s) : \psi(u)=c_i\} S 1ci(s)={u∈S 1(s):ψ(u)=ci}, V ~ 1 c i ( s ) = { u ∈ V ~ 1 ( s ) : ψ ( u ) = c i } \widetilde{V}^{c_i}_1(s)=\{u \in \widetilde{V}_1(s): \psi(u)=c_i\} V 1ci(s)={u∈V 1(s):ψ(u)=ci}。同理定义 P ~ 2 c i ( s ) \widetilde{P}^{c_i}_2(s) P 2ci(s), S ~ 2 c i ( s ) \widetilde{S}^{c_i}_2(s) S 2ci(s), V ~ 2 c i ( s ) \widetilde{V}^{c_i}_2(s) V 2ci(s)。
2.2 算法整体流程
- 算法整体流程如下图所示
输入为pattern G 1 G_1 G1和图 G 2 G_2 G2。首先计算 G 1 G_1 G1中每个节点能够在 G 2 G_2 G2中找到匹配的概率(后面介绍),然后根据这个概率生成 G 1 G_1 G1中节点的访问顺序。第5行对 G 1 G_1 G1和 G 2 G_2 G2中的节点分类,分类后对pattern图进行预处理(后面介绍),最后执行真正的匹配算法。 - 匹配算法如下图所示
首先判断状态 s c s_c sc是否是目标状态(既是否是同构),如果是,则将结果保存到 S o l u t i o n s Solutions Solutions并返回True。否则判断是否是dead状态,如果是则返回False,否则继续执行。然后将上一次插入的匹配设置为空,并使用GETNEXTCANDIDATE函数寻找候选的匹配。然后循环执行直到没有可选的匹配位置。在循环中,首先判断将候选匹配加入到当前状态后是否满足约束,不满足则继续寻找候选匹配。如果满足,则状态转移到 s n s_n sn,递归的判断 s n s_n sn是否可行。
2.3 GenerateNodeSequence和ComputeProbabilities
- 对于一个状态 s s s,假设它的 M ( s ) M(s) M(s)集中包含 k k k对匹配,那么到达状态 s s s的路径共有 k ! k! k!个。可以看到,如果不对访问顺序加以限制,那么不同的路径就会产生相同的状态。该问题的一个解决方法是通过定义节点的顺序( ≺ \prec ≺)并按照该顺序访问节点,以此生成一个状态空间树。此时又引入了另一个问题,排序算法对性能也有很大的影响。为了解决该问题,VF3对有更多约束的点赋予更高的优先级。例如, G 1 G_1 G1中的某个点在 G 2 G_2 G2中找到匹配点的概率非常小,此时该点应赋予更高的优先级。再例如,某个点连接了更多了的已匹配的点,此时该点应赋予更高的优先级。主要的考虑是这些点能大概率不满足,因此能够尽早的进行剪枝操作。
- 定义
P
f
(
u
)
P_f(u)
Pf(u)为点
u
∈
G
1
u \in G_1
u∈G1找到点
v
∈
G
2
v \in G_2
v∈G2的概率。概率定义的逻辑可以在原文中看到,由于我对这方面不熟,便不在此班门弄斧了,直接给出最终的公式。
P f ( u ) = P l ( λ v 1 ( u ) ) ⋅ ∑ d ′ ≥ d i n ( u ) P d i n ( d ′ ) ⋅ ∑ d ′ ≥ d o u t ( u ) P d o u t ( d ′ ) P_f(u)=P_l(\lambda_{v1}(u)) \cdot \sum\limits_{d'\geq d^{in}(u)}^{}P_d^{in}(d')\cdot \sum\limits_{d'\geq d^{out}(u)}^{}P_d^{out}(d') Pf(u)=Pl(λv1(u))⋅d′≥din(u)∑Pdin(d′)⋅d′≥dout(u)∑Pdout(d′)
P l ( l ) P_l(l) Pl(l)表示节点 v v v的标签为 l l l的概率, P d i n ( d ′ ) P_d^{in}(d') Pdin(d′)表示入度为 d ′ d' d′的概率, P d o u t ( d ′ ) P_d^{out}(d') Pdout(d′)表示出度为 d ′ d' d′的概率。同时也考虑到了图的结构信息,称之为节点映射度(node mapping degree) d M d_M dM。对于一个给定的节点 u ∈ G 1 u\in G_1 u∈G1, d M d_M dM定义为 u u u与 N G 1 N_{G_1} NG1中所有节点的入度和出度之和。 - GenerateNodeSequence首先根据 d M d_M dM排序,如果相等,则 P f P_f Pf,如果相等,则根据节点的出度入度之和,如果相等,则随机。
2.4 PreprocessPatternGraph
- 我们仔细想一下
G
1
G_1
G1的遍历顺序
N
G
1
N_{G_1}
NG1,每次从
N
G
1
N_{G_1}
NG1中取出一个节点加入到
M
(
s
)
M(s)
M(s)中时,状态树都会向下前进一层。对于每一层来说,
G
1
G_1
G1对应的terminal集是不变的,而且我们已经知道了
G
1
G_1
G1的遍历顺序
N
G
1
N_{G_1}
NG1,因此可以提前计算出状态树每层
G
1
G_1
G1对应的terminal集内容。PreprocessPatternGraph迭代的分析
N
G
1
N_{G_1}
NG1中的每个节点
u
u
u,如果其邻居节点
u
′
u'
u′是
u
u
u的后继(前继)节点,且没有插入到
S
~
1
ψ
(
u
′
)
\widetilde{S}^{\psi(u')}_1
S
1ψ(u′)(
P
~
1
ψ
(
u
′
)
\widetilde{P}^{\psi(u')}_1
P
1ψ(u′))中,则将其插入。如果
u
′
u'
u′不在这两个集合中,则将
u
u
u设置为
P
a
r
e
n
t
(
u
′
)
Parent(u')
Parent(u′)。具体算法如下图所示。
2.5 GetNextCandidate
- 为了转移到新状态 s n s_n sn,GetNextCandidate寻找一个匹配对 ( u n , v n ) (u_n,v_n) (un,vn),并将其加入到当前状态 s c s_c sc,令生成状态 s c s_c sc是加入的匹配对为 ( u c , v c ) (u_c,v_c) (uc,vc)。
- 对于pattern图
G
1
G_1
G1,
u
n
u_n
un的选取的是
u
c
u_c
uc在
N
G
1
N_{G_1}
NG1中的下一个节点。对于目标图
G
2
G_2
G2,算法将
G
2
G_2
G2中所有未匹配的并且和
u
n
u_n
un类别相同的节点作为
v
n
v_n
vn的候选节点。候选节点集表示为
R 2 ( s c , ψ ( u n ) ) = { v n ∈ V 2 : v n ∉ M 2 ( s c ) ⋀ ψ ( v n ) = ψ ( u n ) } R_2(s_c,\psi(u_n))=\{v_n \in V_2:v_n\notin M_2(s_c)\bigwedge \psi(v_n)=\psi(u_n)\} R2(sc,ψ(un))={vn∈V2:vn∈/M2(sc)⋀ψ(vn)=ψ(un)} - 在不同的情况时,我们还可以进一步删减
R
2
R_2
R2。
(1)如果 u n u_n un是 N G 1 N_{G_1} NG1的第一个节点,其没有父节点,那么 v n v_n vn的候选集就是 R 2 R_2 R2;
(2)如果 u n u_n un有父节点,且是父节点的前继节点,则
R 2 P ( s c , ψ ( u n ) , v ~ ) = { v n ∈ V 2 : v n ∈ P 2 ( v ~ ) ⋂ R 2 ( s c , ψ ( u n ) } R_2^P(s_c,\psi(u_n),\widetilde{v})=\{v_n \in V_2:v_n\in P_2(\widetilde{v})\bigcap R_2(s_c,\psi(u_n)\} R2P(sc,ψ(un),v )={vn∈V2:vn∈P2(v )⋂R2(sc,ψ(un)}
(3)如果 u n u_n un有父节点,且是父节点的后继节点,则
R 2 S ( s c , ψ ( u n ) , v ~ ) = { v n ∈ V 2 : v n ∈ S 2 ( v ~ ) ⋂ R 2 ( s c , ψ ( u n ) } R_2^S(s_c,\psi(u_n),\widetilde{v})=\{v_n \in V_2:v_n\in S_2(\widetilde{v})\bigcap R_2(s_c,\psi(u_n)\} R2S(sc,ψ(un),v )={vn∈V2:vn∈S2(v )⋂R2(sc,ψ(un)}
其中 v ~ \widetilde{v} v 表示 G 2 G_2 G2中与 u n u_n un的父节点匹配的节点。具体算法如下图所示
2.6 IsFeasible
- 判断能够将匹配对
(
u
n
,
v
n
)
(u_n,v_n)
(un,vn)将入到当前状态
s
c
s_c
sc中,
I s F e a s i b l e ( s c , u n , v n ) = F s ( s c , u n , v n ) ∧ F t ( s c , u n , v n ) IsFeasible(s_c,u_n,v_n)=F_s(s_c,u_n,v_n)\wedge F_t(s_c,u_n,v_n) IsFeasible(sc,un,vn)=Fs(sc,un,vn)∧Ft(sc,un,vn)
F s F_s Fs判断标签是否一致, F t F_t Ft判断拓扑结构是否一致。 - F t ( s c , u n , v n ) = F c ( s c , u n , v n ) ∧ F l a 1 ( s c , u n , v n ) ∧ F l a 2 ( s c , u n , v n ) F_t(s_c,u_n,v_n)=F_c(s_c,u_n,v_n)\wedge F_{la1}(s_c,u_n,v_n)\wedge F_{la2}(s_c,u_n,v_n) Ft(sc,un,vn)=Fc(sc,un,vn)∧Fla1(sc,un,vn)∧Fla2(sc,un,vn), F c F_c Fc检查是否满足一致性,后面会介绍。 F l a 1 F_{la1} Fla1和 F l a 2 F_{la2} Fla2是为了进一步删减搜索空间,代表1-lookahead和2-lookahead。只使用 F c F_c Fc便能保证结果的正确性,其余两个是为了加速的。
-
F
c
(
s
c
,
u
n
,
v
n
)
⇔
F_c(s_c,u_n,v_n)\Leftrightarrow
Fc(sc,un,vn)⇔
∀ u ′ ∈ S 1 ( u n ) ∩ M 1 ( s c ) ∃ v ′ = μ ~ ( s c , u ′ ) ∈ S 2 ( v n ) \forall u'\in S_1(u_n) \cap M_1(s_c) \exists v'=\widetilde{\mu}(s_c,u')\in S_2(v_n) ∀u′∈S1(un)∩M1(sc)∃v′=μ (sc,u′)∈S2(vn)
∧ ∀ u ′ ∈ P 1 ( u n ) ∩ M 1 ( s c ) ∃ v ′ = μ ~ ( s c , u ′ ) ∈ P 2 ( v n ) \wedge\forall u'\in P_1(u_n) \cap M_1(s_c) \exists v'=\widetilde{\mu}(s_c,u')\in P_2(v_n) ∧∀u′∈P1(un)∩M1(sc)∃v′=μ (sc,u′)∈P2(vn)
∧ ∀ v ′ ∈ S 2 ( v n ) ∩ M 2 ( s c ) ∃ u ′ = μ ~ − 1 ( s c , v ′ ) ∈ S 1 ( u n ) \wedge\forall v'\in S_2(v_n) \cap M_2(s_c) \exists u'=\widetilde{\mu}^{-1}(s_c,v')\in S_1(u_n) ∧∀v′∈S2(vn)∩M2(sc)∃u′=μ −1(sc,v′)∈S1(un)
∧ ∀ v ′ ∈ P 2 ( v n ) ∩ M 2 ( s c ) ∃ u ′ = μ ~ − 1 ( s c , v ′ ) ∈ P 1 ( u n ) \wedge\forall v'\in P_2(v_n) \cap M_2(s_c) \exists u'=\widetilde{\mu}^{-1}(s_c,v')\in P_1(u_n) ∧∀v′∈P2(vn)∩M2(sc)∃u′=μ −1(sc,v′)∈P1(un)
其中, S 1 ( u n ) S_1(u_n) S1(un)表示 u n u_n un在 G 1 G_1 G1中的后继节点, P 1 ( u n ) P_1(u_n) P1(un)表示 u n u_n un在 G 1 G_1 G1中的前继节点,同理其它集合。 μ ~ ( s c , u ′ ) \widetilde{\mu}(s_c,u') μ (sc,u′)表示在 M ( s c ) M(s_c) M(sc)中与 u ′ u' u′对应的点, μ ~ − 1 ( s c , v ′ ) \widetilde{\mu}^{-1}(s_c,v') μ −1(sc,v′)表示在 M ( s c ) M(s_c) M(sc)中与 v ′ v' v′对应的点。 - F l a 1 F_{la1} Fla1检查 u n u_n un的邻居分散在各个terminal集中每个类别中的个数是否小于等于 v n v_n vn的同样的个数。 F l a 2 F_{la2} Fla2进行同样的操作,不过是在 V ~ 1 ( s c ) \widetilde{V}_1(s_c) V 1(sc)和 V ~ 2 ( s c ) \widetilde{V}_2(s_c) V 2(sc)上。
-
图说子图同构算法——VF2算法(一)
2016-10-09 01:53:47给我依赖的天使1.Let us play VF2 algorithm1.1一些声明 1.2你必需知道的事情虽然子图的同构问题没有图的同构问题要求这么严,图的同构必须要求结点的度必须相同,否则不同构。如果在一个图中某个节点的度大于...写在前面的话
谨以此系列献给 my grandpa~
体验过人间的无常,才知道爱才是宝藏
像孩子依赖着肩膀
像诗人依赖月亮
像眼泪依赖脸庞你就是我的天使
给我依赖的天使1.Let us play VF2 algorithm
1.1一些声明
1.2你必需知道的事情
虽然子图的同构问题没有图的同构问题要求这么严,图的同构必须要求结点的度必须相同,否则不同构。
如果在一个图中某个节点的度大于要匹配的图形的或者是它的子图,这样是不可能找到子图和它同构的,因为它本身根本构建不出这么大的度。
子图同构是图的同构中的一种,至少有些该满足的还是要满足。
1.3 算法处理
如果点已经全部用完,那么匹配的部分就是我们的子图同构的部分。
写在后面的话
喜欢的朋友们,支持一下,哈哈哈
写在最后的话
无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程 https://www.captainbed.net/chichoxian
-
vf3lib:VF3算法-解决大型图和密集图上子图同构的最快算法
2021-01-28 19:11:32vf3lib:VF3算法-解决大型图和密集图上子图同构的最快算法 -
子图同构VF2代码实现、论文、测试数据(Graph isomorphism)
2018-01-07 12:40:13关于子图同构算法VF2的论文,实现和测试数据。用于学习子图同构算法,用作借鉴。 -
子图同构算法-VF2(java实现)
2020-03-30 09:39:36最近在项目中用到了子图同构算法VF2在这里记录一下。内容主要来自一篇论文(A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs)子图同构算法-VF2(java实现)
最近在项目中用到了子图同构算法VF2,自己查找的时候发现csdn上没有太详细的博客,所以在这里记录一下。内容主要来自一篇论文(A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs)
一、什么是VF2算法
VF2算法是一种子图同构算法,而子图同构我们可以这样定义:
假设有两个图 H=(VH,EH) H=(VH,EH) 和图 G=(V,E) G=(V,E) 子图同构即从H到G存在这样一个函数 f:VH→V 并且 (u,v)∈EH(u,v)∈EH同样成立 f 叫做子图同构的一个映射。在VF2算法中,可以将查询图表示为 queryGraph,把数据图表示为 targetGraph,引入一个中间状态 state,用于记录我们当前子图同构进行到的状态。 在每一个中间状态中,VF2算法计算要添加到当前状态state的候选节点对 P(S)(分别由查询图和数据图中的一个节点组成),是否满足 可行性规则,如果该节点对可以满足 可行性规则那么就将其添加到当前状态state中并更新当前状态,整个过程递归的进行。
其中 可行性规则一共包含五条,这五条规则确保了子图同构过程的正确性。为了方便代码实现,本文将这五条规则归结为三条,每条通过一个java方法实现。分别是:-
前驱和后继规则:对于查询节点的每一个前驱节点和后继节点,目标节点一定存在与之对应的。
-
1-look-ahead规则:在查询图中与查询节点邻接,以查询节点为起点/终点并且属于以已经匹配顶点为起点/终点的部分的数量一定小于等于在数据图中对应部分的数量。
-
2-look-ahead 规则:在查询图中查询节点与既不是已匹配顶点也不和以匹配顶点相邻的点也应该与数据图中的对应部分满足第二条规则。
根据这几条规则递归的进行匹配最终可以判断在数据图中是否包含查询图。
二、VF2算法的代码实现(java)
本文递归的判断目标图是否包含查询图:
private boolean matchRecursive(State state, Graph targetGraph, Graph queryGraph){ if (state.depth == queryGraph.nodes.size()){ // Found a match state.matched = true; return true; } else { // Extend the state ArrayList<Pair<Integer,Integer>> candidatePairs = genCandidatePairs(state, targetGraph, queryGraph); for (Pair<Integer, Integer> entry : candidatePairs){ if (checkFeasibility(state, entry.getKey(), entry.getValue())){ state.extendMatch(entry.getKey(), entry.getValue()); // extend mapping if (matchRecursive(state, targetGraph, queryGraph)){ // Found a match return true; } state.backtrack(entry.getKey(), entry.getValue()); // remove the match added before } } } return false; }
其中 genCandidatePairs方法用于生成当前状态的所有候选对,checkFeasibility方法用于检查添加词匹配项的可行性。
以下是我们编写的方法用于判断 可行性规则是否满足:
验证规则一
的方法:private Boolean checkPredAndSucc(State state, int targetNodeIndex , int queryNodeIndex) { Node targetNode = state.targetGraph.nodes.get(targetNodeIndex); Node queryNode = state.queryGraph.nodes.get(queryNodeIndex); int[][] targetAdjacency = state.targetGraph.getAdjacencyMatrix(); int[][] queryAdjacency = state.queryGraph.getAdjacencyMatrix(); for (Edge e : queryNode.inEdges) { if (state.core_2[e.source.id] > -1) { if (targetAdjacency[state.core_2[e.source.id]][targetNodeIndex] == -1){ return false; // not such edge in target graph } else if (targetAdjacency[state.core_2[e.source.id]][targetNodeIndex] != e.label){ return false; // label doesn't match } } } for (Edge e : queryNode.outEdges) { if (state.core_2[e.target.id] > -1) { if (targetAdjacency[targetNodeIndex][state.core_2[e.target.id]] == -1){ return false; // not such edge in target graph } else if (targetAdjacency[targetNodeIndex][state.core_2[e.target.id]] != e.label) { return false; // label doesn't match } } } return true; }
验证
规则二
的方法:private boolean checkInAndOut(State state, int targetNodeIndex , int queryNodeIndex) { Node targetNode = state.targetGraph.nodes.get(targetNodeIndex); Node queryNode = state.queryGraph.nodes.get(queryNodeIndex); int targetPredCnt = 0, targetSucCnt = 0; int queryPredCnt = 0, querySucCnt = 0; //入度规则 //目标节点在T1in中的前驱/后继节点数必须大于或者等于查询节点在T2in中的前驱/后继节点数 for (Edge e : targetNode.inEdges){ if (state.inT1in(e.source.id)){ targetPredCnt++; } } for (Edge e : targetNode.outEdges){ if (state.inT1in(e.target.id)){ targetSucCnt++; } } for (Edge e : queryNode.inEdges){ if (state.inT2in(e.source.id)){ queryPredCnt++; } } for (Edge e : queryNode.outEdges){ if (state.inT2in(e.target.id)){ querySucCnt++; } } if (targetPredCnt < queryPredCnt || targetSucCnt < querySucCnt){ return false; } // T1out中的目标节点的前驱/后继数必须大于或者等于处于T2out中的查询节点的前驱/后继数 for (Edge e : targetNode.inEdges){ if (state.inT1out(e.source.id)){ targetPredCnt++; } } for (Edge e : targetNode.outEdges){ if (state.inT1out(e.target.id)){ targetSucCnt++; } } for (Edge e : queryNode.inEdges){ if (state.inT2out(e.source.id)){ queryPredCnt++; } } for (Edge e : queryNode.outEdges){ if (state.inT2out(e.target.id)){ querySucCnt++; } } if (targetPredCnt < queryPredCnt || targetSucCnt < querySucCnt){ return false; } return true; }
验证
规则三
的方法:private boolean checkNew(State state, int targetNodeIndex , int queryNodeIndex){ Node targetNode = state.targetGraph.nodes.get(targetNodeIndex); Node queryNode = state.queryGraph.nodes.get(queryNodeIndex); int targetPredCnt = 0, targetSucCnt = 0; int queryPredCnt = 0, querySucCnt = 0; for (Edge e : targetNode.inEdges){ if (state.inN1Tilde(e.source.id)){ targetPredCnt++; } } for (Edge e : targetNode.outEdges){ if (state.inN1Tilde(e.target.id)){ targetSucCnt++; } } for (Edge e : queryNode.inEdges){ if (state.inN2Tilde(e.source.id)){ queryPredCnt++; } } for (Edge e : queryNode.outEdges){ if (state.inN2Tilde(e.target.id)){ querySucCnt++; } } if (targetPredCnt < queryPredCnt || targetSucCnt < querySucCnt){ return false; } return true; } }
下面定义了state状态类,以上的验证可行性方法以及VF2算法的整个流程基于该类编写:
package wip.VF2.core; import java.io.PrintWriter; import java.util.HashSet; import java.util.Scanner; import wip.VF2.graph.Edge; import wip.VF2.graph.Graph; import wip.VF2.graph.Node; public class State { public int[] core_1; // stores for each target graph node to which query graph node it maps ("-1" indicates no mapping) public int[] core_2; // stores for each query graph node to which target graph node it maps ("-1" indicates no mapping) public int[] in_1; // stores for each target graph node the depth in the search tree at which it entered "T_1 in" or the mapping ("-1" indicates that the node is not part of the set) public int[] in_2; // stores for each query graph node the depth in the search tree at which it entered "T_2 in" or the mapping ("-1" indicates that the node is not part of the set) public int[] out_1; // stores for each target graph node the depth in the search tree at which it entered "T_1 out" or the mapping ("-1" indicates that the node is not part of the set) public int[] out_2; // stores for each query graph node the depth in the search tree at which it entered "T_2 out" or the mapping ("-1" indicates that the node is not part of the set) public HashSet<Integer> T1in; // nodes that not yet in the partial mapping, that are the destination of branches start from target graph public HashSet<Integer> T1out; // nodes that not yet in the partial mapping, that are the origin of branches end into target graph public HashSet<Integer> T2in; // nodes that not yet in the partial mapping, that are the destination of branches start from query graph public HashSet<Integer> T2out; // nodes that not yet in the partial mapping, that are the origin of branches end into query graph public HashSet<Integer> unmapped1; // unmapped nodes in target graph public HashSet<Integer> unmapped2; // unmapped nodes in query graph public int depth = 0; // current depth of the search tree public boolean matched = false; public Graph targetGraph; public Graph queryGraph; /** * Initialize a State * @param targetGraph The big graph * @param queryGraph The small graph */ public State(Graph targetGraph, Graph queryGraph) { this.targetGraph = targetGraph; this.queryGraph = queryGraph; int targetSize = targetGraph.nodes.size(); int querySize = queryGraph.nodes.size(); T1in = new HashSet<Integer>(targetSize * 2); T1out = new HashSet<Integer>(targetSize * 2); T2in = new HashSet<Integer>(querySize * 2); T2out = new HashSet<Integer>(querySize * 2); unmapped1 = new HashSet<Integer>(targetSize * 2); unmapped2 = new HashSet<Integer>(querySize * 2); core_1 = new int[targetSize]; core_2 = new int[querySize]; in_1 = new int[targetSize]; in_2 = new int[querySize]; out_1 = new int[targetSize]; out_2 = new int[querySize]; // initialize values ("-1" means no mapping / not contained in the set) // initially, all sets are empty and no nodes are mapped for (int i = 0 ; i < targetSize ; i++) { core_1[i] = -1; in_1[i] = -1; out_1[i] = -1; unmapped1.add(i); } for (int i = 0 ; i < querySize ; i++) { core_2[i] = -1; in_2[i] = -1; out_2[i] = -1; unmapped2.add(i); } } public Boolean inM1(int nodeId) { return (core_1[nodeId] > -1); } public Boolean inM2(int nodeId) { return (core_2[nodeId] > -1); } public Boolean inT1in(int nodeId) { return ((core_1[nodeId] == -1) && (in_1[nodeId] > -1)); } public Boolean inT2in(int nodeId) { return ((core_2[nodeId] == -1) && (in_2[nodeId] > -1)); } public Boolean inT1out(int nodeId) { return ((core_1[nodeId] == -1) && (out_1[nodeId] > -1)); } public Boolean inT2out(int nodeId) { return ((core_2[nodeId] == -1) && (out_2[nodeId] > -1)); } public Boolean inT1(int nodeId) { return (this.inT1in(nodeId) || this.inT1out(nodeId)); } public Boolean inT2(int nodeId) { return (this.inT2in(nodeId) || this.inT2out(nodeId)); } public Boolean inN1Tilde(int nodeId) { return ((core_1[nodeId] == -1) && (in_1[nodeId] == -1) && (out_1[nodeId] == -1)); } public Boolean inN2Tilde(int nodeId) { return ((core_2[nodeId] == -1) && (in_2[nodeId] == -1) && (out_2[nodeId] == -1)); } /** * Add a new match (targetIndex, queryIndex) to the state * @param targetIndex Index of the node in target graph * @param queryIndex Index of the node in query graph */ public void extendMatch(int targetIndex, int queryIndex) { core_1[targetIndex] = queryIndex; core_2[queryIndex] = targetIndex; unmapped1.remove(targetIndex); unmapped2.remove(queryIndex); T1in.remove(targetIndex); T1out.remove(targetIndex); T2in.remove(queryIndex); T2out.remove(queryIndex); depth++; // move down one level in the search tree Node targetNode = targetGraph.nodes.get(targetIndex); Node queryNode = queryGraph.nodes.get(queryIndex); for (Edge e : targetNode.inEdges) { if (in_1[e.source.id] == -1){ // if the note is not in T1in or mapping in_1[e.source.id] = depth; if (!inM1(e.source.id)) // if not in M1, add into T1in T1in.add(e.source.id); } } for (Edge e : targetNode.outEdges) { if (out_1[e.target.id] == -1){ // if the note is not in T1out or mapping out_1[e.target.id] = depth; if (!inM1(e.target.id)) // if not in M1, add into T1out T1out.add(e.target.id); } } for (Edge e : queryNode.inEdges) { if (in_2[e.source.id] == -1){ // if the note is not in T2in or mapping in_2[e.source.id] = depth; if (!inM2(e.source.id)) // if not in M1, add into T2in T2in.add(e.source.id); } } for (Edge e : queryNode.outEdges) { if (out_2[e.target.id] == -1){ // if the note is not in T2out or mapping out_2[e.target.id] = depth; if (!inM2(e.target.id)) // if not in M1, add into T2out T2out.add(e.target.id); } } } /** * Remove the match of (targetNodeIndex, queryNodeIndex) for backtrack * @param targetNodeIndex * @param queryNodeIndex */ public void backtrack(int targetNodeIndex, int queryNodeIndex) { core_1[targetNodeIndex] = -1; core_2[queryNodeIndex] = -1; unmapped1.add(targetNodeIndex); unmapped2.add(queryNodeIndex); for (int i = 0 ; i < core_1.length ; i++) { if (in_1[i] == depth) { in_1[i] = -1; T1in.remove(i); } if (out_1[i] == depth) { out_1[i] = -1; T1out.remove(i); } } for (int i = 0 ; i < core_2.length ; i++) { if (in_2[i] == depth) { in_2[i] = -1; T2in.remove(i); } if (out_2[i] == depth) { out_2[i] = -1; T2out.remove(i); } } // put targetNodeIndex and queryNodeIndex back into Tin and Tout sets if necessary if (inT1in(targetNodeIndex)) T1in.add(targetNodeIndex); if (inT1out(targetNodeIndex)) T1out.add(targetNodeIndex); if (inT2in(queryNodeIndex)) T2in.add(queryNodeIndex); if (inT2out(queryNodeIndex)) T2out.add(queryNodeIndex); depth--; } /** * Print the current mapping */ public void printMapping() { for (int i = 0 ; i < core_2.length ; i++) { System.out.print("(" + core_2[i] + "-" + i + ") "); } System.out.println(); } /** * Write state to file */ public void writeMapping(PrintWriter writer){ for (int i = 0 ; i < core_2.length ; i++) { writer.write("(" + core_2[i] + "-" + i + ") "); } writer.write("\n"); } }
三、VF2算法的改进思路
- 查询图的边的匹配顺序可以改进为按照边在数据图上出现的次数从小到大的匹配。(提高边的过滤能力)
- 顶点的匹配顺序可以改进为顶点出现次数少,度数大的优先匹配。(提高顶点的过滤能力)
-
-
(子)图同构算法VF2实现(1)
2015-11-11 09:00:54子图同构问题本质上就是一种匹配,VF2算法加了很多feasibility rules,保证了算法的高效性。这里只是实现最基本的判断子图同构的算法: 参考文献有(其实google一把就能出来这些): ... -
Ullmann算法原论文
2019-03-15 09:05:25Ullmann算法可谓是子图同构领域的开篇经典之作,是很多人学习图匹配算法的入学论文。 -
(子)图同构算法VF2实现(2)——源代码与具体实现
2015-11-14 09:47:06上一篇关于VF2的博客写的太水了,http://blog.csdn.net/mmc2015/article/details/49777447。而且错误百出。 这里给出真正需要注意的地方 1)数据结构: //每个邻接边的结构,注意,该结构不单独使用,必须和某个点... -
PMSM_VF控制_PMSMVF_PMSM电机驱动算法_pmsm_stm32vf控制_传感器
2021-09-10 21:51:43VF算法控制三相无刷电机,开环控制,无传感器 -
VF程序设计语句和算法
2019-04-18 10:43:13本文介绍了VF程序设计语句和算法,从入门级别课程到案例讲解。 -
图匹配和子图同构检测的并行网络组织算法
2021-06-29 17:30:45图匹配和子图同构检测的并行网络组织算法 图匹配和子图同构检测的并行网络组织算法 Keita Maehara 计算机与系统工程系,神户大学,神户,日本 657-8501 Kuniaki Uehara 城市研究中心安全与安保,神户大学,神户,... -
VF常用算法集
2014-12-08 22:09:02Fortran经典算法集 -
图论算法
2019-05-06 16:54:00五一时候随便翻书看到了一些关于离散数学图论的模板和算法,大概总结了一下,图论要比数论稍简单一点点。。。 一、 点用边连起来就叫做图,严格意义上讲,图是一种数据结构,定义为:graph=(V,E)。V是一个非空... -
基于VF-CS的移动传感器网络覆盖优化算法
2021-01-14 06:52:47为此提出一种基于虚拟力(virtual force)扰动和布谷鸟搜索(CS,Cuckoo search)的移动传感器网络覆盖优化算法(VF-CS)。首先,对传感器节点进行Voronoi图划分,形成独立的泰森多边形(Thiessen polygon)。其次,... -
《A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs》论文总结
2017-11-19 10:36:57解决子图同构的VF2算法论文的理解 -
子图同构算法——Ullmann算法(1)不包含refine procedure的简单穷举算法。
2013-08-11 11:24:03最近在学习子图同构算法。什么是子图同构,看这里-->图论。在图论的维基百科中有子图同构的描述。 子图同构一直是图论中比较重要的一个问题,经过各位大牛长时间的学习和研究,发现求解子图同构是一个NP完全问题。... -
Hadoop MapReduce二次排序算法与实现之实现
2018-10-08 16:16:59转自:一起学Hadoop——二次排序算法的实现 二次排序,从字面上可以理解为在对key排序的基础上对key所对应的值value排序,也叫辅助排序。一般情况下,MapReduce框架只对key排序,而不对key所对应的值排序,因此... -
PMSM_VF控制,PMF技术,C,C++
2021-09-10 21:51:43VF算法控制三相无刷电机,开环控制,无传感器 -
PMSM FOC VF 电压频率开环控制
2020-07-15 15:17:21转 VF算法控制三相无刷电机,开环控制,无传感器(VF controlled three-phase brushless motor) 包含FOC 核心算 clark park ipark svpwm IQ格式的计算,值得参考。只要设定电压与频率比就能让电机,属于开环控制,很... -
SST39VF1601 PCB封装
2017-10-09 19:30:35SST39VF1601C SST39VF1681 PCB封装 SST39VF1601C SST39VF1681 PCB封装 -
重庆市专升本考试VF程序汇总
2017-10-15 09:16:11重庆市专升本考试VF程序汇总 常用算法 历年真题大全 -
Stable Baselines/RL算法/A2C
2019-08-13 11:35:392tensorflow调试 tensorboard_log str tensorboard的日志位置(如果时None,没有日志) _init_setup_model bool 实例化创建过程中是否建立网络(只用于载入) policy_kwargs dict ... -
详解MapReduce的模式、算法和用例
2014-04-06 19:23:44这个算法由Google提出,使用权威的PageRank算法,通过连接到一个网页的其他网页来计算网页的相关性。真实算法是相当复杂的,但是核心思想是权重可以传播,也即通过一个节点的各联接节点的权重的均值来计算节点自身... -
基于C-V2X前向碰撞预警算法的实现
2021-11-30 11:06:20关于C-V2X前向碰撞预警的算法实现,首先需要了解一下前向碰撞预警算法流程图,如下图所示: 背景技术: 本算法主要是基于V2X通信技术,在道路交通中,车辆与车辆之间通过该项技术进行数据交互,将自身的各项基本... -
PMSM_FOC_VF 电压频率比开环控制_PMSMVF_pmsm_电压频率比开环控制_FOC控制_无传感器foc
2020-07-15 15:17:21转 VF算法控制三相无刷电机,开环控制,无传感器(VF controlled three-phase brushless motor) 包含FOC 核心算 clark park ipark svpwm IQ格式的计算,值得参考。只要设定电压与频率比就能让电机,属于开环控制,很... -
无感Foc电机控制,算法采用滑膜观测器,启动采用Vf,全开源c代码,全开源,启动顺滑,很有参考价值。
2021-07-05 11:49:23无感Foc电机控制 算法采用滑膜观测器,启动采用Vf,全开源c代码,全开源,启动顺滑,很有参考价值。 带原理图,笔记仅仅展示一部分 -
微电网VF控制
2020-10-15 11:26:26且在1s时突然并入负载2,而输出电压不变,因此验证了VF控制的正确性。本仿真有配套论文,而且几乎就是根据论文搭建的,有意请加扣扣:3223787740,只挣一个搭仿真的钱,学生党也能承受。(主页还有更多的关于PQ,VF,... -
AGM算法
2020-05-29 21:36:23令G(V,E,LV,LE,φ)G(V,E,L_V,L_E,\varphi)G(V,E,LV,LE...FSM算法根据操作的数据不同,可以分为针对图数据库的和针对一个大图的(现在只讨论exact match方法)。 根据每个顶点标签的id对顶点进行排序,然后根据该顺序