子图同构之VF3
1.子图同构问题描述
首先描述一下子图同构是什么意思,一个图由一系列节点和一系列连接节点的边构成,节点和边都可以有标签,也就是可以给他们按属性分类。
精确点:一个图 G = ( V , E ) G=(V,E) G=(V,E)由集合 V V V和集合 E E E组成, V V V是节点(node)集合, E E E是边(edge)集合,且 E ⊂ V × V E\subset V\times V E⊂V×V ,用 L v L_v Lv表示节点上的标签集合, L e L_e Le表示边上的标签集合,那么每个节点对应的标签由函数(或者说映射) λ 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 确定。
现在给定两个图 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) ,其中 G 1 G_1 G1是比较小的图(我们把它叫做pattern graph), G 2 G_2 G2是比较大的图(我们把它叫做target graph),用集合 M ⊂ V 1 × V 2 M\sub V_1\times V_2 M⊂V1×V2 表示两个图中节点的对应关系。如果节点 u ∈ V 1 u\in V_1 u∈V1,则 μ ( u ) ∈ V 2 \mu(u)\in V_2 μ(u)∈V2表示与节点 u u u对应的 G 2 G_2 G2中的节点;如果节点 v ∈ V 2 v\in V_2 v∈V2,则 ν ( v ) ∈ V 1 \nu(v)\in V_1 ν(v)∈V1表示与节点 v v v对应的 G 1 G_1 G1中的节点。
G 1 G_1 G1 | G 2 G_2 G2 | |
---|---|---|
对应关系 | u u u | μ ( u ) \mu(u) μ(u) |
对应关系 | μ − 1 ( v ) \mu^{-1} (v) μ−1(v) | v v v |
如果以下6个条件成立,则这两个图是子图同构的。
- ∀ u ∈ V 1 ∃ μ ( u ) ∈ V 2 : ( u , μ ( u ) ) ∈ M \forall u\in V_1 \quad \exist \mu(u)\in V_2:(u,\mu(u))\in M ∀u∈V1∃μ(u)∈V2:(u,μ(u))∈M
- ∀ u , u ′ ∈ V 1 u ≠ u ′ ⇒ μ ( u ) ≠ μ ( u ′ ) \forall u,u^{'}\in V_1 \quad u\neq u^{'}\Rightarrow \mu(u)\neq \mu(u^{'}) ∀u,u′∈V1u=u′⇒μ(u)=μ(u′)
- ∀ ( u , u ′ ) ∈ E 1 ∃ ( μ ( u ) , μ ( u ′ ) ) ∈ E 2 ) \forall (u,u^{'})\in E_1 \quad \exist (\mu(u),\mu(u^{'}))\in E_2) ∀(u,u′)∈E1∃(μ(u),μ(u′))∈E2)
- ∀ u , u ′ ∈ V 1 ( μ ( u ) , μ ( u ′ ) ) ∈ E 2 ⇒ ( u , u ′ ) ∈ E 1 \forall u,u^{'} \in V_1 \quad (\mu(u),\mu(u^{'})) \in E_2 \Rightarrow (u,u^{'}) \in E_1 ∀u,u′∈V1(μ(u),μ(u′))∈E2⇒(u,u′)∈E1
- ∀ u ∈ V 1 λ V 1 ( u ) = λ V 2 ( μ ( u ) ) \forall u \in V_1 \quad \lambda_{V_1}(u)=\lambda_{V_2}(\mu(u)) ∀u∈V1λV1(u)=λV2(μ(u))
- ∀ ( u , u ′ ) ∈ E 1 λ e 1 ( u , u ′ ) = λ e 2 ( μ ( u ) , μ ( u ′ ) ) \forall (u,u^{'}) \in E_1 \quad \lambda_{e_1}(u,u^{'})=\lambda_{e_2}(\mu(u),\mu(u^{'})) ∀(u,u′)∈E1λe1(u,u′)=λe2(μ(u),μ(u′))
用人话来解释一下:
-
对于小图中每个节点,大图中都要有一个对应的节点与之对应,并且这样一对一对的节点构成了集合 M M M;
-
小图中任意两个不一样的节点,他们对应的大图中的节点不能是同一个;
-
小图中每条边,大图中都有一条边与之对应,并且他们两端的节点一一对应;
-
小图中任意两个节点,如果他们对应的大图中的节点之间有一条边,那么小图中这两个节点之间也得有条边;
-
每对对应节点的label要相同,也就是这俩节点类型或属性相同;
-
每对对应边的label要相同,也就是说这俩边的类型或属性相同。
综上所述,子图同构简单来说就是,大图中有一个子结构,长得跟小图一模一样,而且这个子结构的节点之间不能多出小图中不存在的边来,如果要去掉最后这个而且,就把上面第4个条件去掉~
2.VF3算法
VF3算法的目标是,给定一个小图和一个大图,找出大图中所有与小图同构的子图,这是一个NP-hard问题。整体上,VF3算法是一个树搜索算法,并且想办法去尽可能剪枝。搜索树上的每个节点都是一个状态(state),记为 s s s,每个 s s s中包含一系列节点的映射关系,可以想象成一系列key-value对,key都是小图中的节点,value都是大图中的节点。搜索最开始的时候(也就是树的根节点) s s s中什么都没有,随着搜索的进行(树高度的增加), s s s中的key-value对会一对对增加,假如这个状态中所有的节点对都满足第一节中的6条约束,我们称这个状态为一致状态(consistent state);如果一个一致状态包含的小图中所有的节点,那意味着我们找到了一个大图的子结构与小图同构,称之为目标状态(goal state);如果一个状态是不可能再派生出一致状态的,我们称之为死亡状态(dead state)。
那么现在最大的问题就是两个:
- 这棵搜索树怎么组织?
- 怎么设计剪枝规则?
下面逐个展开~
A. 总体流程介绍
先来介绍一些符号:
M ~ ( s ) \tilde{M}(s) M~(s)表示状态 s s s中的节点映射集合,则 M ~ ( s ) ⊆ M \tilde{M}(s) \subseteq M M~(s)⊆M;
M 1 ~ ( s ) \tilde{M_1}(s) M1~(s)表示 M ~ ( s ) \tilde{M}(s) M~(s)中属于 G 1 G_1 G1的节点,也就是所有的key,严格点: M 1 ~ ( s ) = { u ∈ V 1 : ∃ ( u , v ) ∈ M ~ ( s ) , v ∈ V 2 } \tilde{M_1}(s)=\{u\in V_1:\exist (u,v)\in \tilde{M}(s),v\in V_2\} M1~(s)={u∈V1:∃(u,v)∈M~(s),v∈V2};
M 2 ~ ( s ) \tilde{M_2}(s) M2~(s)表示 M ~ ( s ) \tilde{M}(s) M~(s)中属于 G 2 G_2 G2的节点,也就是所有的value,严格点: M 2 ~ ( s ) = { v ∈ V 2 : ∃ ( u , v ) ∈ M ~ ( s ) , u ∈ V 1 } \tilde{M_2}(s)=\{v\in V_2:\exist (u,v)\in \tilde{M}(s),u\in V_1\} M2~(s)={v∈V2:∃(u,v)∈M~(s),u∈V1};
G ~ 1 ( s ) \tilde{G}_1(s) G~1(s)表示仅包含节点在 M 1 ~ ( s ) \tilde{M_1}(s) M1~(s)中的 G 1 G_1 G1的子图,也就是比 M 1 ~ ( s ) \tilde{M_1}(s) M1~(s)加上了边;
G ~ 2 ( s ) \tilde{G}_2(s) G~2(s)表示仅包含节点在 M 2 ~ ( s ) \tilde{M_2}(s) M2~(s)中的 G 2 G_2 G2的子图,也就是比 M 2 ~ ( s ) \tilde{M_2}(s) M2~(s)加上了边;
μ ~ ( s , u ) \tilde{\mu}(s,u) μ~(s,u)表示 M ~ ( s ) \tilde{M}(s) M~(s)中与 u u u对应的节点,其中 u ∈ V 1 u\in V_1 u∈V1, μ ~ ( s , u ) ∈ V 2 \tilde{\mu}(s,u)\in V_2 μ~(s,u)∈V2;
μ ~ − 1 ( s , v ) \tilde{\mu}^{-1}(s,v) μ~−1(s,v)表示 M ~ ( s ) \tilde{M}(s) M~(s)中与 v v v对应的节点,其中 μ ~ − 1 ( s , v ) ∈ V 1 \tilde{\mu}^{-1}(s,v)\in V_1 μ~−1(s,v)∈V1, v ∈ V 2 v\in V_2 v∈V2。
V 1 V_1 V1 | V 2 V_2 V2 | |
---|---|---|
对应关系 | u u u | μ ( s , u ) \mu(s,u) μ(s,u) |
对应关系 | μ − 1 ( s , v ) \mu^{-1} (s,v) μ−1(s,v) | v v v |
大致看一下整个流程,上伪代码~
VF3总流程
1: f u n c t i o n V F 3 ( G 1 , G 2 ) function \ \ VF3(G_1,G_2) function VF3(G1,G2)
2: S o l u t i o n s ← ∅ \ \ \ \ \ \ Solutions\leftarrow \empty Solutions←∅
3: P f = C o m p u t e P r o b a b i l i t y s ( G 1 , G 2 ) \ \ \ \ \ \ P_f=ComputeProbabilitys(G_1,G_2) Pf=ComputeProbabilitys(G1,G2)
4: N G 1 = G e n e r a t e N o d e S e q u e n c e ( G 1 , P f e a s ) \ \ \ \ \ \ N_{G_1}=GenerateNodeSequence(G_1,P_{feas}) NG1=GenerateNodeSequence(G1,Pfeas)
5: C l a s s i f y N o d e s ( G 1 , G 2 ) \ \ \ \ \ \ ClassifyNodes(G_1,G_2) ClassifyNodes(G1,G2)
6: ( s 0 , P a r e n t ) = P r e P r o c e s s P a t t e r n G r a p h ( G 1 , N G 1 ) \ \ \ \ \ \ (s_0,Parent)=PreProcessPatternGraph(G_1,N_{G_1}) (s0,Parent)=PreProcessPatternGraph(G1,NG1)
7: M a t c h ( s 0 , G 2 , G 1 , N G 1 , P a r e n t , S o l u t i o n s ) \ \ \ \ \ \ Match(s_0,G_2,G_1,N_{G_1},Parent,Solutions) Match(s0,G2,G1,NG1,Parent,Solutions)
8: r e t u r n S o l u t i o n s \ \ \ \ \ \ return \ \ Solutions return Solutions
匹配函数
1: f u n c t i o n M a t c h ( s 0 , G 2 , G 1 , N G 1 , P a r e n t , S o l u t i o n s ) function \ \ Match(s_0,G_2,G_1,N_{G_1},Parent,Solutions) function Match(s0,G2,G1,NG1,Parent,Solutions)
2: i f I s G o a l ( s c ) t h e n \ \ \ \ \ \ if \ \ IsGoal(s_c) \ \ then if IsGoal(sc) then
3: A p p e n d ( M ( s c ) , S o l u t i o n s ) \ \ \ \ \ \ \ \ \ \ \ \ Append(M(s_c),Solutions) Append(M(sc),Solutions)
4: r e t u r n T r u e \ \ \ \ \ \ \ \ \ \ \ \ return \ \ True return True
5: i f I s D e a d ( s c ) t h e n \ \ \ \ \ \ if \ \ IsDead(s_c) \ \ then if IsDead(sc) then
6: r e t u r n F a l s e \ \ \ \ \ \ \ \ \ \ \ \ return \ \ False return False
7: S e t ( u c , v c ) = ( ϵ , ϵ ) \ \ \ \ \ \ Set(u_c,v_c)=(\epsilon,\epsilon) Set(uc,vc)=(ϵ,ϵ)
8: ( u n , v n ) = G e t N e x t C a n d i d a t e ( s , ( u c , v c ) , N G 1 , P a r e n t , G 1 , G 2 ) \ \ \ \ \ \ (u_n,v_n) = GetNextCandidate(s,(u_c,v_c),N_{G_1},Parent,G_1,G_2) (un,vn)=GetNextCandidate(s,(uc,vc),NG1,Parent,G1,G2)
9: R e s u l t = F a l s e \ \ \ \ \ \ Result=False Result=False
10: w h i l e ( u c , v c ) ≠ ( ϵ , ϵ ) d o \ \ \ \ \ while \ \ (u_c,v_c) \neq (\epsilon,\epsilon) \ \ do while (uc,vc)=(ϵ,ϵ) do
11: i f I s F e a s i b l e ( s c , u n , v n ) t h e n \ \ \ \ \ \ \ \ \ \ \ \ if \ \ IsFeasible(s_c,u_n,v_n)\ \ then if IsFeasible(sc,un,vn) then
12: s n = s c ∪ ( u n , v n ) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ s_n=s_c\cup (u_n,v_n) sn=sc∪(un,vn)
13: i f M a t c h ( s n , G 1 , G 2 , N G 1 , P a r e n t ) i s T r u e t h e n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \ Match(s_n,G_1,G_2,N_{G_1},Parent) \ \ is\ \ True\ \ then if Match(sn,G1,G2,NG1,Parent) is True then
14: R e s u l t = T r u e \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Result=True Result=True
15: ( u n , v n ) = G e t N e x t C a n d i d a t e ( s , ( u n , v n ) , N G 1 , P a r e n t , G 1 , G 2 ) \ \ \ \ \ \ \ \ \ \ \ \ (u_n,v_n)=GetNextCandidate(s,(u_n,v_n),N_{G_1},Parent,G_1,G_2) (un,vn)=GetNextCandidate(s,(un,vn),NG1,Parent,G1,G2)
16: r e t u r n R e s u l t \ \ \ \ \ \ return\ \ Result return Result
总流程中出现了一些函数, C o m p u t e P r o b a b i l i t y s ( G 1 , G 2 ) ComputeProbabilitys(G_1,G_2) ComputeProbabilitys(G1,G2)和 G e n e r a t e N o d e S e q u e n c e ( G 1 , P f e a s ) GenerateNodeSequence(G_1,P_{feas}) GenerateNodeSequence(G1,Pfeas)是为了组织搜索树,也就是搜索顺序,其中 C o m p u t e P r o b a b i l i t y s ( G 1 , G 2 ) ComputeProbabilitys(G_1,G_2) ComputeProbabilitys(G1,G2)的计算结果又是为 G e n e r a t e N o d e S e q u e n c e ( G 1 , P f e a s ) GenerateNodeSequence(G_1,P_{feas}) GenerateNodeSequence(G1,Pfeas)服务的,最终算出的 N G 1 N_{G_1} NG1是小图的节点进入 s s s的顺序优先级,而大图中的节点进入顺序是由 M a t c h Match Match流程中的 G e t C a n d i d a t e GetCandidate GetCandidate函数决定的。 C l a s s i f y N o d e s ( G 1 , G 2 ) ClassifyNodes(G_1,G_2) ClassifyNodes(G1,G2)函数是对节点进行分类,是为了更好地剪枝同时也为后面的预处理做了一些准备。 P r e P r o c e s s P a t t e r n G r a p h ( G 1 , N G 1 ) PreProcessPatternGraph(G_1,N_{G_1}) PreProcessPatternGraph(G1,NG1)为每个小图中的节点提供一个父节点,为 M a t c h Match Match做好准备,细节后面一一展开。
搜索过程从 s 0 s_0 s0开始,这时 M ~ ( s 0 ) = ∅ \tilde{M}(s_0)=\empty M~(s0)=∅,把一对对的 ( u n , v n ) (u_n,v_n) (un,vn)往里面加, ( u n , v n ) (u_n,v_n) (un,vn)由 G e t C a n d i d a t e GetCandidate GetCandidate函数决定,每加一对都会判断加了这对节点后一步(1-lookahead)和两步(2-lookahead)是否可行,如果不可行就剪枝,不再从这个状态往下搜。为什么没有3-lookahead,n-lookahead?因为往后考虑n步本身就需要计算量,这是判断一枝的效率和剪枝质量之间的博弈。
B 可行集合
这一节主要是介绍一些集合的概念,为理解后面的算法做铺垫。
首先集合粗略地分为 P ~ 1 \tilde P_1 P~1, S ~ 1 \tilde S_1 S~1, V ~ 1 \tilde V_1 V~1 P ~ 2 \tilde P_2 P~2, S ~ 2 \tilde S_2 S~2, V ~ 2 \tilde V_2 V~2 ,其中下标1代表是属于小图 G 1 G_1 G1的节点子集,下标2代表是属于大图 G 2 G_2 G2的节点子集, P P P是predecessor,意思是前驱节点,具体讲是至少有一条边连接到已经匹配的 M ~ ( s ) \tilde M(s) M~(s)集合中节点的所有节点构成的集合,好拗口…重来:一个predecessor节点就是有边指向 M ~ ( s ) \tilde M(s) M~(s)里面的节点; S S S与 P P P相对,是successor,即后继节点,是从 M ~ ( s ) \tilde M(s) M~(s)中出来被指向的节点构成的集合;最后 V V V是这个图中既不在 M M M中,又不在 P P P和 S S S中的节点构成的集合。综上, M M M是已经匹配的节点集合, P P P和 S S S是直接和已经匹配的节点相连的节点集合,即1-lookahead要用到的集合,V是更外圈的节点集合,至少得是2-lookahead才够得到,下面严格定义一下~
- P ~ 1 ( s ) = { u ∈ V 1 − M ~ 1 ( s ) : ∃ u ′ ∈ M ~ 1 ( s ) : ( u , u ′ ) ∈ E 1 } \tilde P_1(s)=\{u\in V_1-\tilde M_1(s):\exist u^{'}\in \tilde M_1(s):(u,u^{'})\in E_1 \} P~1(s)={u∈V1−M~1(s):∃u′∈M~1(s):(u,u′)∈E1}
- S ~ 1 ( s ) = { u ∈ V 1 − M ~ 1 ( s ) : ∃ u ′ ∈ M ~ 1 ( s ) : ( u ′ , u ) ∈ E 1 } \tilde S_1(s)=\{u\in V_1-\tilde M_1(s):\exist u^{'}\in \tilde M_1(s):(u^{'},u)\in E_1 \} S~1(s)={u∈V1−M~1(s):∃u′∈M~1(s):(u′,u)∈E1}
- V ~ 1 ( s ) = V 1 − M ~ 1 ( s ) − P ~ 1 ( s ) − S ~ 1 ( s ) \tilde V_1(s)=V_1-\tilde M_1(s)-\tilde P_1(s)-\tilde S_1(s) V~1(s)=V1−M~1(s)−P~1(s)−S~1(s)
- P ~ 2 ( s ) = { v ∈ V 2 − M ~ 2 ( s ) : ∃ v ′ ∈ M ~ 2 ( s ) : ( v , v ′ ) ∈ E 2 } \tilde P_2(s)=\{v\in V_2-\tilde M_2(s):\exist v^{'}\in \tilde M_2(s):(v,v^{'})\in E_2 \} P~2(s)={v∈V2−M~2(s):∃v′∈M~2(s):(v,v′)∈E2}
- S ~ 2 ( s ) = { v ∈ V 2 − M ~ 2 ( s ) : ∃ v ′ ∈ M ~ 2 ( s ) : ( v ′ , v ) ∈ E 2 } \tilde S_2(s)=\{v\in V_2-\tilde M_2(s):\exist v^{'}\in \tilde M_2(s):(v^{'},v)\in E_2 \} S~2(s)={v∈V2−M~2(s):∃v′∈M~2(s):(v′,v)∈E2}
- V ~ 2 ( s ) = V 2 − M ~ 2 ( s ) − P ~ 2 ( s ) − S ~ 2 ( s ) \tilde V_2(s)=V_2-\tilde M_2(s)-\tilde P_2(s)-\tilde S_2(s) V~2(s)=V2−M~2(s)−P~2(s)−S~2(s)
几个集合的关系如下图所示~~~
这样还不够,在这基础上,我们还要为节点分组,最常见的就是按节点的类型或属性分组,如果分别属于小图和大图的两个节点属于不同组,即类型不同,那他们不可能对应上,也就不可能作为一对节点进入一致状态 s s s。
于是我们把两个图中所有的节点合起来,一起按照类型分组,分组函数: ψ : V 1 ∪ V 2 → C \psi :V_1\cup V_2 \rightarrow C ψ:V1∪V2→C,给每个节点分一个组别 c i ∈ C = { c 1 , c 2 , . . . c q } c_i\in C=\{c_1,c_2,...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)。
在给节点分组之后,我们又可以将上面6个集合分得更细,分成6q个,q是分组的数量。具体如下:
- P ~ 1 c i ( s ) = { u ∈ P ~ 1 ( s ) : ψ ( u ) = c i } \tilde P_1^{c_i}(s)=\{u\in \tilde 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 } \tilde S_1^{c_i}(s)=\{u\in \tilde 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 } \tilde V_1^{c_i}(s)=\{u\in \tilde V_1(s):\psi(u)=c_i \} V~1ci(s)={u∈V~1(s):ψ(u)=ci}
- P ~ 2 c i ( s ) = { v ∈ P ~ 2 ( s ) : ψ ( v ) = c i } \tilde P_2^{c_i}(s)=\{v\in \tilde P_2(s):\psi(v)=c_i \} P~2ci(s)={v∈P~2(s):ψ(v)=ci}
- S ~ 2 c i ( s ) = { v ∈ S ~ 2 ( s ) : ψ ( v ) = c i } \tilde S_2^{c_i}(s)=\{v\in \tilde S_2(s):\psi(v)=c_i \} S~2ci(s)={v∈S~2(s):ψ(v)=ci}
- V ~ 2 c i ( s ) = { v ∈ V ~ 2 ( s ) : ψ ( v ) = c i } \tilde V_2^{c_i}(s)=\{v\in \tilde V_2(s):\psi(v)=c_i \} V~2ci(s)={v∈V~2(s):ψ(v)=ci}
C 组织小图中节点的匹配顺序
简单计算,假如一个状态 s s s对应的匹配集合 M ( s ) M(s) M(s)中已经有k对节点,那么到达这个状态共有 k ! k! k!种不同的路径,为了避免如此繁重的计算,我们将状态空间组织成一颗树,而不是原来的图,实则是避免同样的状态 s s s在一次搜索中访问多次。
在这一步中,我们要组织的是小图中的节点进入匹配的顺序,大图中节点的进入顺序在后面,为了给小图中节点排一个进入匹配的优先级序列(也就是 N G 1 N_{G_1} NG1),要同时用到小图和大图的一些结构信息,原则是优先考虑有更多约束的节点,具体讲有两点:一是在大图中找到对应节点的可能性比较小的应该优先,二是跟已经匹配上的节点的连接比较多的应该优先。这样可以更早地剪掉大分支~
实现上述目的的是总流程中3、4行的两个函数:
-
P f = C o m p u t e P r o b a b i l i t y s ( G 1 , G 2 ) P_f=ComputeProbabilitys(G_1,G_2) Pf=ComputeProbabilitys(G1,G2)
-
N G 1 = G e n e r a t e N o d e S e q u e n c e ( G 1 , P f e a s ) N_{G_1}=GenerateNodeSequence(G_1,P_{feas}) NG1=GenerateNodeSequence(G1,Pfeas)
首先, P f ( u ) P_f(u) Pf(u)是在大图中找到一个节点 v v v可以跟小图节点 u u u匹配上的概率,当然这个概率是估算,因为精确计算过于困难而且需要花很大的计算量,这里没必要这么精确。
我们这样估计这个概率:
P f ( u ) = P r ( λ v 2 ( v ) = λ v 1 ( u ) , d i n ( v ) ≥ d i n ( u ) , d o u t ( v ) ≥ d o u t ( u ) ) P_f(u)=P_r(\lambda_{v_2}(v)=\lambda_{v_1}(u),d^{in}(v)\geq d^{in}(u),d^{out}(v)\geq d^{out}(u)) Pf(u)=Pr(λv2(v)=λv1(u),din(v)≥din(u),dout(v)≥dout(u)),其中 d i n d^{in} din是入度, d o u t d^{out} dout是出度。
很自然,想要在大图中找到一个节点 v v v跟小图节点 u u u匹配,必须满足三个条件:
1.两个节点的label相同;
2.大图节点 v v v的入度不小于小图节点 u u u;
3.大图节点 v v v的出度不小于小图节点 u u u。
我们用同时满足这三个条件的节点概率来估计 P f ( u ) P_f(u) Pf(u)~但是在最差的情况下,计算这个概率的时间复杂度为 O ( N 3 ) O(N^3) O(N3),于是我们进一步假设这三个条件是独立的,于是可以这样估算这个概率:
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_{v_1}(u)).\sum_{d'\geq d^{in}(u)}P_d^{in}(d').\sum_{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的label是 l l l的概率。
用人话解释一下,因为假设三个条件独立,因此可以把三个条件各自满足的可能性相乘来估算同时满足三个条件的概率。第一个概率是节点 v v v拥有跟节点 u u u相同标签的概率;第二个概率:假设 u u u节点的入度为3,那么 d ′ d' d′是3,4,5,6…把 v v v的入度为这些值的概率全部加起来;第三个概率:假设 u u u节点的出度为4,那么 d ′ d' d′是4,5,6,7…把 v v v的出度为这些值的概率全部加起来。这个计算的复杂度仅为 O ( N ) O(N) O(N).
除了考虑以上概率以外,我们还要考虑已经在 N G 1 N_{G_1} NG1里的节点的结构,定义 d M d_M dM为一个节点与已经在 N G 1 N_{G_1} NG1里的节点相连的数量,即节点度的一部分。
总体上, G e n e r a t e N o d e S e q u e n c e ( G 1 , P f e a s ) GenerateNodeSequence(G_1,P_{feas}) GenerateNodeSequence(G1,Pfeas)函数首先考虑 d M d_M dM, d M d_M dM大的节点与已经匹配上的节点的连接比较多,优先级高;如果几个节点的 d M d_M dM相同,则按上面计算出的 P f P_f Pf排序, P f P_f Pf小的节点优先级高;如果 P f P_f Pf也相同,则再考虑节点的度,度大的节点优先级高;最后如果前面三者全部相等,那就随机排~这样我们就把小图的节点匹配顺序排好了,得到了 N G 1 N_{G_1} NG1。
D 预处理
每一个状态
s
s
s在搜索树中的高度与它包含的节点对数相同,而小图节点进入
s
s
s的优先级由
N
G
1
N_{G_1}
NG1确定,树中高度相同的状态
s
s
s的小图节点
u
u
u都相同,而大图节点
v
v
v是动态选择的,每一种
v
v
v的可能都造成了树在这个高度上的一个分支。上图助理解~
作为正式匹配前的最后一步,预处理函数 P r e P r o c e s s P a t t e r n G r a p h ( G 1 , N G 1 ) PreProcessPatternGraph(G_1,N_{G_1}) PreProcessPatternGraph(G1,NG1)为每个小图节点提供一个父节点,这是为了后面真正选候选节点对做准备的,而节点对的选择也就是选择上图中 ? ? ? ??? ???位置的 v v v节点,因为 u u u已经被确定了。这里的父节点并不是一个指向它的节点,而是在它之前进入 s s s的且与它有连接的节点中优先级最高的那个,第一个 u u u没有父节点。
伪代码如下:
1: f u n c t i o n P r e P r o c e s s P a t t e r n G r a p h ( G 1 , N G 1 , T 1 ) function \ \ PreProcessPatternGraph(G_1,N_{G_1},T_1) function PreProcessPatternGraph(G1,NG1,T1)
2: i = 0 \ \ \ \ \ \ i=0 i=0
3: f o r a l l u ∈ N G 1 d o \ \ \ \ \ \ for\ \ all\ \ u\in N_{G_1} \ \ do for all u∈NG1 do
4: f o r a l l u ′ ∈ P 1 ( u ) ∪ S 1 ( u ) d o \ \ \ \ \ \ \ \ \ \ \ \ for \ \ all\ \ u'\in P_1(u)\cup S_1(u)\ \ do for all u′∈P1(u)∪S1(u) do
5: c i = ψ ( u ′ ) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ c_i=\psi(u') ci=ψ(u′)
6: i f u ′ ∈ P 1 ( u ) ∧ u ′ ∉ P ~ 1 c i t h e n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \ u'\in P_1(u) \land u'\notin \tilde P_1^{c_i}\ \ then if u′∈P1(u)∧u′∈/P~1ci then
7: P u t u ′ i n P ~ 1 a t l e v e l i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Put\ \ u'\ \ in\ \ \tilde P_1\ \ at\ \ level\ \ i Put u′ in P~1 at level i
8: P a r e n t ( u ′ ) = u \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Parent(u')=u Parent(u′)=u
9: i f u ′ ∈ S 1 ( u ) ∧ u ′ ∉ S ~ 1 c i t h e n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \ u'\in S_1(u) \land u'\notin \tilde S_1^{c_i}\ \ then if u′∈S1(u)∧u′∈/S~1ci then
10: P u t u ′ i n S ~ 1 a t l e v e l i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Put\ \ u'\ \ in\ \ \tilde S_1\ \ at\ \ level\ \ i Put u′ in S~1 at level i
11: P a r e n t ( u ′ ) = u \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Parent(u')=u Parent(u′)=u
12: i = i + 1 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=i+1 i=i+1
13: r e t u r n P a r e n t \ \ \ \ \ \ return\ \ Parent return Parent
E 选择候选节点对
前面做了那么多准备,可以说大多数都是为了给这一步做准备的。
( u n , v n ) = G e t N e x t C a n d i d a t e ( s , ( u c , v c ) , N G 1 , P a r e n t , G 1 , G 2 ) (u_n,v_n)=GetNextCandidate(s,(u_c,v_c),N_{G_1},Parent,G_1,G_2) (un,vn)=GetNextCandidate(s,(uc,vc),NG1,Parent,G1,G2)这个函数在匹配过程中为每一个状态 s s s选择下一对候选节点, ( u c , v c ) (u_c,v_c) (uc,vc)是当前状态 s s s中最后进入的一对节点。对于小图而言, u n u_n un节点是 N G 1 N_{G_1} NG1序列中,跟在 u c u_c uc后面的节点;所以接下来的关键工作就是选择大图中的节点 v n v_n vn来与 u n u_n un对应。
首先, v n v_n vn必须与 u n u_n un的组别相同,我们把与 u n u_n un同组的还没匹配上的节点集合记为 R 2 ( s c , ψ ( u n ) ) R_2(s_c,\psi (u_n)) R2(sc,ψ(un))。
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 \tilde M_2(s_c)\land \psi(v_n)=\psi (u_n) \} R2(sc,ψ(un))={vn∈V2:vn∈/M~2(sc)∧ψ(vn)=ψ(un)}
更进一步, v n v_n vn与它的父节点 v ~ n \tilde v_n v~n之间的关系得和 u n u_n un与它的父节点 u ~ n \tilde u_n u~n之间的关系相同。具体来说,如果 u n u_n un是 u ~ n \tilde u_n u~n的后继节点,那么 v n v_n vn也得是 v ~ n \tilde v_n v~n的后继节点;如果 u n u_n un是 u ~ n \tilde u_n u~n的前驱节点,那么 v n v_n vn也得是 v ~ n \tilde v_n v~n的前驱节点。
因此最终 v n v_n vn的候选集如下:
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),\tilde v)=\{v_n\in V_2:v_n\in S_2(\tilde v)\cap R_2(s_c,u_n) \} R2S(sc,ψ(un),v~)={vn∈V2:vn∈S2(v~)∩R2(sc,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),\tilde v)=\{v_n\in V_2:v_n\in P_2(\tilde v)\cap R_2(s_c,u_n) \} R2P(sc,ψ(un),v~)={vn∈V2:vn∈P2(v~)∩R2(sc,un)}
上面两条规则很大程度地缩小了可以考虑的 v n v_n vn的范围,根据 u n u_n un与它的父节点 u ~ n \tilde u_n u~n之间的关系,可以知道该从上面哪个集合中选取 v n v_n vn,然后就取这个集合中的第一个节点当作 v n v_n vn,与 u n u_n un组成节点对准备进入状态 s s s,当然是否真的能进入还要看是否满足可行规则,很有可能被剪枝掉~
选取候选节点对伪代码如下:
1: f u n c t i o n G e t N e x t C a n d i d a t e ( s , ( u c , v c ) , N G 1 , P a r e n t , G 1 , G 2 ) function\ \ GetNextCandidate(s,(u_c,v_c),N_{G_1},Parent,G_1,G_2) function GetNextCandidate(s,(uc,vc),NG1,Parent,G1,G2)
2: i f u c = ϵ t h e n \ \ \ \ \ \ if\ \ u_c=\epsilon \ \ then if uc=ϵ then
3: u n = G e t N e x t I n S e q u e n c e ( N G 1 , s c ) \ \ \ \ \ \ \ \ \ \ \ \ u_n=GetNextInSequence(N_{G_1},s_c) un=GetNextInSequence(NG1,sc)
4: i f u n = ϵ t h e n \ \ \ \ \ \ \ \ \ \ \ \ if \ \ u_n=\epsilon\ \ then if un=ϵ then //这个序列结束
5: r e t u r n ( ϵ , ϵ ) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return\ \ (\epsilon,\epsilon) return (ϵ,ϵ)
6: e l s e \ \ \ \ \ \ else else
7: u n = u c \ \ \ \ \ \ \ \ \ \ \ \ u_n=u_c un=uc
8: i f P a r e n t ( u n ) = ϵ t h e n \ \ \ \ \ \ if\ \ Parent(u_n)=\epsilon\ \ then if Parent(un)=ϵ then // u n u_n un没有父节点
9: v n = G e t N e x t N o d e ( v c , R 2 ( s c , ψ ( u n ) ) ) \ \ \ \ \ \ \ \ \ \ \ \ v_n=GetNextNode(v_c,R_2(s_c,\psi(u_n))) vn=GetNextNode(vc,R2(sc,ψ(un)))
10: r e t u r n ( u n , v n ) \ \ \ \ \ \ \ \ \ \ \ \ return \ \ (u_n,v_n) return (un,vn)
11: e l s e \ \ \ \ \ \ else else
12: v ~ = u ~ ( s c , P a r e n t ( u n ) ) \ \ \ \ \ \ \tilde v = \tilde u(s_c,Parent(u_n)) v~=u~(sc,Parent(un)) //获取跟 u n u_n un的父节点对应的节点
13: i f u n i n P 1 ( P a r e n t ( u n ) ) \ \ \ \ \ \ if\ \ u_n\ \ in\ \ P_1(Parent(u_n)) if un in P1(Parent(un)) // u n u_n un是其父节点的前驱节点
14: v n = G e t N e x t N o d e ( v c , R 2 P ( s c , ψ ( u n ) , v ~ ) \ \ \ \ \ \ \ \ \ \ \ \ v_n=GetNextNode(v_c,R_2^P(s_c,\psi(u_n),\tilde v) vn=GetNextNode(vc,R2P(sc,ψ(un),v~)
15: r e t u r n ( u n , v n ) \ \ \ \ \ \ \ \ \ \ \ \ return\ \ (u_n,v_n) return (un,vn)
16: i f u n i n S 1 ( P a r e n t ( u n ) ) \ \ \ \ \ \ if\ \ u_n\ \ in\ \ S_1(Parent(u_n)) if un in S1(Parent(un)) // u n u_n un是其父节点的后继节点
17: v n = G e t N e x t N o d e ( v c , R 2 S ( s c , ψ ( u n ) , v ~ ) \ \ \ \ \ \ \ \ \ \ \ \ v_n=GetNextNode(v_c,R_2^S(s_c,\psi(u_n),\tilde v) vn=GetNextNode(vc,R2S(sc,ψ(un),v~)
18: r e t u r n ( u n , v n ) \ \ \ \ \ \ \ \ \ \ \ \ return\ \ (u_n,v_n) return (un,vn)
19: r e t u r n ( ϵ , ϵ ) \ \ \ \ \ \ return\ \ (\epsilon,\epsilon) return (ϵ,ϵ) // u n u_n un没有对应的候选节点
F 可行规则
在匹配过程中,每一步选取的节点对 ( u n , v n ) (u_n,v_n) (un,vn)加入到状态 s s s不一定合适,要么加入后新的状态 s ′ s' s′不是一致状态(consistent state),要么加入后未来几步不可能达到一致状态,因此这一搜索分支就可以被砍掉,不再往下搜索,并且回溯到上一状态 s s s并选取下一节点对。我们要用一系列的可行规则来判断当前选出的候选节点对是否可以加入状态 s s s达到 s ′ s' s′。
我们用可行函数 I s F e a s i b l e ( s c , u n , v n ) IsFeasible(s_c,u_n,v_n) IsFeasible(sc,un,vn)来判断在当前状态 s c s_c sc下,加入节点对 ( u n , v n ) (u_n,v_n) (un,vn)是否可行。判断规则主要分为两类,一类是基于节点和边的标签(label),记作 F s F_s Fs,另一类基于图的拓扑结构,记作 F t F_t Ft。两类规则都得满足,因此有:
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)\land 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的规则很简单,就是要保证新加入节点对后,对应节点和边的label要全部相同:
- ∀ u ∈ V 1 λ V 1 ( u ) = λ V 2 ( μ ( u ) ) \forall u \in V_1 \quad \lambda_{V_1}(u)=\lambda_{V_2}(\mu(u)) ∀u∈V1λV1(u)=λV2(μ(u))
- ∀ ( u , u ′ ) ∈ E 1 λ e 1 ( u , u ′ ) = λ e 2 ( μ ( u ) , μ ( u ′ ) ) \forall (u,u^{'}) \in E_1 \quad \lambda_{e_1}(u,u^{'})=\lambda_{e_2}(\mu(u),\mu(u^{'})) ∀(u,u′)∈E1λe1(u,u′)=λe2(μ(u),μ(u′))
而 F t F_t Ft的规则就复杂一些,当然这也是本算法剪枝的精髓所在~这部分规则又可以分为三个小部分,第一:将新节点对加入状态 s s s后,检查新的状态 s ′ s' s′还是一致状态,记作 F c F_c Fc;第二,考虑1-lookahead,判断未来一步是否可能构成一致状态 s ′ ′ s'' s′′记作 F l a 1 F_{la1} Fla1;第三,考虑2-lookahead,判断未来两步是否可能构成一致状态 s ′ ′ ′ s''' s′′′,记作 F l a 2 F_{la2} Fla2。
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)\land F_{la1}(s_c,u_n,v_n)\land 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:
当 s c s_c sc已经是一个一致状态,判断要新加入的候选节点对 ( u n , v n ) (u_n,v_n) (un,vn)是否可行,需要满足 s c ∪ ( u n , v n ) s_c\cup (u_n,v_n) sc∪(un,vn)依然是一致状态。
F c ( S c , u n , v n ) = ∀ u ′ ∈ S 1 ( u n ) ∩ M ~ 1 ( s c ) ∃ v ′ = μ ~ ( s c , u ′ ) ∈ S 2 ( v n ) ∧ ∀ u ′ ∈ P 1 ( u n ) ∩ M ~ 1 ( s c ) ∃ v ′ = μ ~ ( s c , u ′ ) ∈ P 2 ( v n ) ∧ ∀ v ′ ∈ S 2 ( v n ) ∩ M ~ 2 ( s c ) ∃ u ′ = μ ~ − 1 ( s c , v ′ ) ∈ S 1 ( u n ) ∧ ∀ v ′ ∈ P 2 ( v n ) ∩ M ~ 2 ( s c ) ∃ u ′ = μ ~ − 1 ( s c , v ′ ) ∈ P 1 ( u n ) F_c(S_c,u_n,v_n)= \\ \forall u'\in S_1(u_n)\cap \tilde M_1(s_c)\ \ \exist v'=\tilde \mu(s_c,u')\in S_2(v_n) \\ \land \forall u'\in P_1(u_n)\cap \tilde M_1(s_c)\ \ \exist v'=\tilde \mu(s_c,u')\in P_2(v_n) \\ \land \forall v'\in S_2(v_n)\cap \tilde M_2(s_c)\ \ \exist u'=\tilde \mu ^{-1}(s_c,v')\in S_1(u_n) \\ \land \forall v'\in P_2(v_n)\cap \tilde M_2(s_c)\ \ \exist u'=\tilde \mu ^{-1}(s_c,v')\in P_1(u_n) Fc(Sc,un,vn)=∀u′∈S1(un)∩M~1(sc) ∃v′=μ~(sc,u′)∈S2(vn)∧∀u′∈P1(un)∩M~1(sc) ∃v′=μ~(sc,u′)∈P2(vn)∧∀v′∈S2(vn)∩M~2(sc) ∃u′=μ~−1(sc,v′)∈S1(un)∧∀v′∈P2(vn)∩M~2(sc) ∃u′=μ~−1(sc,v′)∈P1(un)
人话解释一下:
S 1 ( u n ) S_1(u_n) S1(un)是 u n u_n un节点的所有后继节点构成的集合; P 1 ( u n ) P_1(u_n) P1(un)是 u n u_n un节点的所有前驱节点构成的集合;
S 2 ( v n ) S_2(v_n) S2(vn)是 v n v_n vn节点的所有后继节点构成的集合; P 2 ( v n ) P_2(v_n) P2(vn)是 v n v_n vn节点的所有前驱节点构成的集合。
4条规则就说了一件事:新的节点 u n u_n un与已经匹配上的 M ~ 1 ( s c ) \tilde M_1(s_c) M~1(sc)中节点的关系 和 新的节点 v n v_n vn与已经匹配上的 M ~ 2 ( s c ) \tilde M_2(s_c) M~2(sc)中节点的关系 要一模一样!不能左边有右边没有或右边有左边没有,也不能乱了顺序和边的方向。
补充:有的问题需求是,小图中有的边大图中必须有,而大图中有的边小图可以没有,也就是小于等于的关系,那么就把后两条规则删除,也就是最右边的两个图的情况可以被容许。
第二类规则 F l a 1 F_{la1} Fla1:
F l a 1 ( s c , u n , v n ) = F l a 1 1 ( s c , u n , v n ) ∧ F l a 1 2 ( s c , u n , v n ) ∧ . . . ∧ F l a 1 q ( s c , u n , v n ) F l a 1 i ( s c , u n , v n ) = ∣ P 1 ( u n ) ∩ P ~ 1 c i ( s c ) ∣ ≤ ∣ P 2 ( v n ) ∩ P ~ 2 c i ( s c ) ∣ ∧ ∣ P 1 ( u n ) ∩ S ~ 1 c i ( s c ) ∣ ≤ ∣ P 2 ( v n ) ∩ S ~ 2 c i ( s c ) ∣ ∧ ∣ S 1 ( u n ) ∩ P ~ 1 c i ( s c ) ∣ ≤ ∣ S 2 ( v n ) ∩ P ~ 2 c i ( s c ) ∣ ∧ ∣ S 1 ( u n ) ∩ S ~ 1 c i ( s c ) ∣ ≤ ∣ S 2 ( v n ) ∩ S ~ 2 c i ( s c ) ∣ F_{la1}(s_c,u_n,v_n)=F_{la1}^1(s_c,u_n,v_n)\land F_{la1}^2(s_c,u_n,v_n)\land ...\land F_{la1}^q(s_c,u_n,v_n)\\ F_{la1}^i(s_c,u_n,v_n)=\\ |P_1(u_n)\cap \tilde P_1^{c_i}(s_c)|\le |P_2(v_n)\cap \tilde P_2^{c_i}(s_c)| \\ \land |P_1(u_n)\cap \tilde S_1^{c_i}(s_c)|\le |P_2(v_n)\cap \tilde S_2^{c_i}(s_c)| \\ \land |S_1(u_n)\cap \tilde P_1^{c_i}(s_c)|\le |S_2(v_n)\cap \tilde P_2^{c_i}(s_c)| \\ \land |S_1(u_n)\cap \tilde S_1^{c_i}(s_c)|\le |S_2(v_n)\cap \tilde S_2^{c_i}(s_c)| Fla1(sc,un,vn)=Fla11(sc,un,vn)∧Fla12(sc,un,vn)∧...∧Fla1q(sc,un,vn)Fla1i(sc,un,vn)=∣P1(un)∩P~1ci(sc)∣≤∣P2(vn)∩P~2ci(sc)∣∧∣P1(un)∩S~1ci(sc)∣≤∣P2(vn)∩S~2ci(sc)∣∧∣S1(un)∩P~1ci(sc)∣≤∣S2(vn)∩P~2ci(sc)∣∧∣S1(un)∩S~1ci(sc)∣≤∣S2(vn)∩S~2ci(sc)∣
这一类约束主要考虑与当前状态 s c s_c sc中的节点直接相连的节点( P , S P,S P,S集合)与新的节点对 ( u n , v n ) (u_n,v_n) (un,vn)间的关系。现在考虑的是3部分节点,1是已经在 M ( s c ) M(s_c) M(sc)中的节点,即已经匹配了的;2是新的候选节点;3是 P , S P,S P,S集合中的节点。3的节点中,与1和2的关系可以分成四类,分别对应上述4条规则,然后每一大类还可以按之前的分组分开,一共4q类关系,每一类关系的节点数量都要满足:小图节点数量不大于大图节点数量,若小图某一类的节点数大于大图了,说明未来不可能形成一致状态,因为未来必然会有小图中边,在大图中找不到。
第三类规则 F l a 2 F_{la2} Fla2:
F l a 2 ( s c , u n , v n ) = F l a 2 1 ( s c , u n , v n ) ∧ F l a 2 2 ( s c , u n , v n ) ∧ . . . ∧ F l a 2 q ( s c , u n , v n ) F l a 2 i ( s c , u n , v n ) = ∣ P 1 ( u n ) ∩ V ~ 1 c i ( s c ) ∣ ≤ ∣ P 2 ( v n ) ∩ V ~ 2 c i ( s c ) ∣ ∧ ∣ S 1 ( u n ) ∩ V ~ 1 c i ( s c ) ∣ ≤ ∣ S 2 ( v n ) ∩ V ~ 2 c i ( s c ) ∣ F_{la2}(s_c,u_n,v_n)=F_{la2}^1(s_c,u_n,v_n)\land F_{la2}^2(s_c,u_n,v_n)\land ...\land F_{la2}^q(s_c,u_n,v_n)\\ F_{la2}^i(s_c,u_n,v_n)= \\ |P_1(u_n)\cap \tilde V_1^{c_i}(s_c)|\le |P_2(v_n)\cap \tilde V_2^{c_i}(s_c)| \\ \land |S_1(u_n)\cap \tilde V_1^{c_i}(s_c)|\le |S_2(v_n)\cap \tilde V_2^{c_i}(s_c)| Fla2(sc,un,vn)=Fla21(sc,un,vn)∧Fla22(sc,un,vn)∧...∧Fla2q(sc,un,vn)Fla2i(sc,un,vn)=∣P1(un)∩V~1ci(sc)∣≤∣P2(vn)∩V~2ci(sc)∣∧∣S1(un)∩V~1ci(sc)∣≤∣S2(vn)∩V~2ci(sc)∣
这一类约束主要考虑与当前状态 s c s_c sc中的节点不直接相连的节点( V V V集合)与新的节点对 ( u n , v n ) (u_n,v_n) (un,vn)间的关系。现在考虑的是3部分节点,1是已经在 M ( s c ) M(s_c) M(sc)中的节点,即已经匹配了的;2是新的候选节点;3是 V V V集合中的节点。3的节点中,与1和2的关系可以分成2类,分别对应上述2条规则,然后每一大类还可以按之前的分组分开,一共2q类关系,每一类关系的节点数量都要满足:小图节点数量不大于大图节点数量,若小图某一类的节点数大于大图了,说明未来不可能形成一致状态,因为未来必然会有小图中边,在大图中找不到。
上述三类规则其实是把新节点对 ( u n , v n ) (u_n,v_n) (un,vn)的度分成三部分,第一部分是与 M ( s ) M(s) M(s)中节点的连接;第二部分是与 P ( s ) 、 V ( s ) P(s)、V(s) P(s)、V(s)中节点的连接,即1-lookahead;第三部分是与 V ( s ) V(s) V(s)中节点的连接,即2-lookahead。这三类连接又可以分为入度和出度,与其连接的节点又可以按分组分开,其中第一部分 M ( s ) M(s) M(s)中已经匹配上的节点分组一定相同,因此可以不考虑分组。这样分类后,每一类度的数量都要满足,小图不大于大图,否则将不可能同构,其中第一类比较特殊,必须完全相同才能同构,当然也有只需要小于等于的需求,这类需求严格意义上已经不是子图同构了。
3 结语
终于写完了,太不好描述了~~~这是近期做的一个复杂网络相关项目中用到的算法,学习实现后记录一下~如果有不对的地方望大佬们指正!