计算群论基础算法:Schreier-Sims 算法

Schreier-Sims 算法是一种寻找置换群的强生成元(SGS)的有效算法,它在计算群论中非常有用,如果找到一组 SGS,可以很容易判断任意置换是否在置换群中,如果在群中,还可以求出该置换具体如何由生成元表示。

本人了解这个算法的动机是听说它可以用来破解魔方……众所周知,普通的三阶魔方有 48 个贴纸(中心块不算),因此魔方的任意操作都可以看做对 48 个贴纸做置换,从而魔方的操作构成了对称群 S 48 S_{48} S48 的子群,其生成元就是六个面各自顺时针旋转 90 度的操作(称为基本操作),将任意置换表示成基本操作的复合就算是解了魔方。因此使用 Schreier-Sims 算法来解魔方可以说是小菜一碟,并且可以解任意可以用群来描述的魔方。(然而一些带有捆绑特性的魔方不容易用群描述,因为两次合法旋转的复合未必就合法。这就超出本文的范围了。)

群论前置知识

阅读本文前需要先了解群论的一些最基本的概念:群(子群,生成子群,正规子群),置换群(对称群,交错群),同态,群作用,轨道和稳定子。详细的讲解可以在很多抽象代数教材中找到,在此不多赘述。

群作用一般有两种不同的记法,本文采用的是右作用的记法:群 G G G 在集合 Ω \Omega Ω 上的作用记为 ( α , g ) ↦ α g , (\alpha,g)\mapsto\alpha^g, (α,g)αg,其中 α ∈ Ω , g ∈ G \alpha\in\Omega,g\in G αΩ,gG。群作用需要满足的两个条件写为 α 1 = α , ( α g ) h = α g h ( ∀ α ∈ Ω , g , h ∈ G ) . \alpha^1=\alpha,(\alpha^g)^h=\alpha^{gh}(\forall \alpha\in\Omega,g,h\in G). α1=α,(αg)h=αgh(αΩ,g,hG).其中 1 1 1 是群 G G G 的单位元,以后乘法群的单位元一律记作 1 1 1,仅含一个单位元的群(平凡群)也记作 1 1 1。特别需要注意的是第二个等式,群作用的复合是先算左边再算右边,与通常的函数复合( ( g ∘ f ) ( x ) = g ( f ( x ) ) (g\circ f)(x)=g(f(x)) (gf)(x)=g(f(x)))顺序恰好相反。

G G G 在集合 Ω \Omega Ω 上的作用与形如 G → Sym ⁡ ( Ω ) G\rightarrow\operatorname{Sym}(\Omega) GSym(Ω) 的同态是对应的(其中 Sym ⁡ ( Ω ) \operatorname{Sym}(\Omega) Sym(Ω) Ω \Omega Ω 的对称群)。特别地,如果 G ≤ Sym ⁡ ( Ω ) G\le\operatorname{Sym}(\Omega) GSym(Ω) 本身就是 Ω \Omega Ω 的置换群,则自然有 G → Sym ⁡ ( Ω ) G\rightarrow\operatorname{Sym}(\Omega) GSym(Ω) 的同态映射(该映射简单地把 G G G 中任意元素映到自身),它对应群 G G G 在集合 Ω \Omega Ω 上的自然作用,并且该作用是忠实的(即: G G G 中唯一一个将 Ω \Omega Ω 中所有元素映射到自身的元素是单位元 1 1 1),因为对应的同态是单同态。因此以后凡是假设 G G G Sym ⁡ ( Ω ) \operatorname{Sym}(\Omega) Sym(Ω) 的子群,同时也就等同于默认 G G G Ω \Omega Ω 上的忠实作用。

置换的复合也是先算左边再算右边。例如取 α = 1 , g = ( 1   2 ) , h = ( 1   3 ) , \alpha=1,g=(1\ 2),h=(1\ 3), α=1,g=(1 2),h=(1 3),则有 α g h = ( α g ) h = ( 1 g ) h = 2 h = 2. \alpha^{gh}=(\alpha^g)^h=(1^g)^h=2^h=2. αgh=(αg)h=(1g)h=2h=2.再取 α = 2 , 3 \alpha=2,3 α=2,3 计算可知 g h = ( 1   2   3 ) gh=(1\ 2\ 3) gh=(1 2 3)。置换复合的顺序不可随意颠倒,例如此处若遵守先右后左的规定, ( 1   2 ) ( 1   3 ) (1\ 2)(1\ 3) (1 2)(1 3) 的结果就是 ( 1   3   2 ) (1\ 3\ 2) (1 3 2) 了。

预备知识:Transversal

本文需要用到的唯一一个有必要在此说明的概念是 transversal,中文直译类似于“断面”“截面”。设 H H H 是群 G G G 的子群,考虑 H H H 的右陪集空间 { H g : g ∈ G } , \{Hg:g\in G\}, {Hg:gG},从每个陪集中任取一个元素(代表元),这些元素组成的集合就是 transversal。如果把每个陪集看做是一张纸,则所有陪集的并集 G G G 就是一摞叠起来的纸,而 transversal 相当于纵向切开这一摞纸得到的截面,这个截面是由每张纸上取一条线组成的。

H ≤ G H\le G HG 有 transversal R R R,通常规定从陪集 H 1 = H H1=H H1=H 中取出的代表元是单位元 1 1 1,即 1 ∈ R 1\in R 1R g ∈ G g\in G gG 所在陪集的代表元通常记为 g ‾ \overline{g} g,即 g ‾ = H g ∩ R . \overline{g}=Hg\cap R. g=HgR.

预备知识:群在计算机中的表示

表示一个群有很多种方法,其中最直观的就是乘法表。然而我们研究的群通常阶数都很大,少量的生成元就可以生成阶数巨大的群,例如三阶魔方的可行状态数有 ( 2 12 ⋅ 12 ! ⋅ 3 8 ⋅ 8 ! ) / ( 2 ⋅ 2 ⋅ 3 ) = 43 , 252 , 003 , 274 , 489 , 856 , 000 (2^{12}\cdot 12!\cdot 3^8\cdot 8!)/(2\cdot 2\cdot 3)=43,252,003,274,489,856,000 (21212!388!)/(223)=43,252,003,274,489,856,000种,这也是三阶魔方通过旋转得到的置换群的阶数,而这个置换群至多用 6 个元素(即 6 种基本操作)就可以生成了。显然,不可能在计算机中存储如此巨大的乘法表,即使仅仅存储或者遍历群中所有元素(因为置换的复合很容易计算,无需存储运算的结果)都不可能。

一般来说,群的表示可以分为三种情况,这些情况是关于我们可以对群中元素做什么操作的描述。
情况 A:群中的任意元素都可以在计算机中表示(一般是用某种数据结构表示),并且可以计算任意两个元素的乘积和任意元素的逆元。但无法判断两个元素是否相等。例如由群表示 ⟨ X ∣ R ⟩ \langle X|R\rangle XR 描述的群。
情况 B:群中的任意元素都可以在计算机中表示,可以计算任意两个元素的乘积和任意元素的逆元,也可以判断两个元素是否相等。例如由若干个置换生成的置换群,置换的复合、逆元以及判断相等都是很容易的。
情况 C:在满足情况 B 的同时,还知道该群有一组特殊的生成元 g 1 , ⋯   , g r g_1,\cdots,g_r g1,,gr,并且有一个通用的算法可以将群中任意元素分解成 g 1 , ⋯   , g r g_1,\cdots,g_r g1,,gr 的某些元素和/或逆元(这些元素不必不同)相乘的形式。

本文仅考虑置换群的情况,通常我们需要处理的是置换群 G = ⟨ X ⟩ ≤ Sym ⁡ ( Ω ) G=\langle X\rangle\le\operatorname{Sym}(\Omega) G=XSym(Ω),其中 X X X 是已知的生成元集合,在计算机中表示为置换的列表,作为程序的输入。(本文中 Ω \Omega Ω 总是有限集,并且通常就用整数 1 1 1 n n n 表示。)于是如同之前所说,此时我们处在情况 B。而 Schreier-Sims 算法可以找到一组满足情况 C 的生成元(称为强生成元,SGS),从而到达情况 C。一旦到达情况 C,很多关于置换群的算法就有了用武之地。
以后当我们说“已知置换群 G = ⟨ X ⟩ G=\langle X\rangle G=X” 时,实际上程序真正的输入是生成元的列表 X X X,不再逐一解释。

预备算法:计算置换群的轨道和稳定子

首先需要解决的问题是:给定置换群 G = ⟨ X ⟩ ≤ Sym ⁡ ( Ω ) G=\langle X\rangle\le\operatorname{Sym}(\Omega) G=XSym(Ω) α ∈ Ω \alpha\in\Omega αΩ,计算 α \alpha α 的轨道 α G \alpha^G αG 和稳定子 G α G_\alpha Gα。当然,我们不可能输出 G α G_\alpha Gα 的每个元素,只要求输出一组生成元即可。这是计算群论中少数几个不用强生成元就可以有效解决的问题之一。

轨道

先考虑如何计算轨道。当然,我们不可能直接用定义 α G = { α g : g ∈ G } \alpha^G=\{\alpha^g:g\in G\} αG={αg:gG} 计算,因为遍历群 G G G 所有元素的时间复杂度无法接受。注意到任意 g ∈ G g\in G gG 都可以表示成 g = x 1 x 2 ⋯ x r g=x_1x_2\cdots x_r g=x1x2xr 的形式(其中每个 x i x_i xi 都是 X X X 的元素;之所以不考虑 X X X 中元素的逆元,是因为有限群的元素总是有限阶,故 x i − 1 x_i^{-1} xi1 总等于 x i ∣ x i ∣ − 1 x_i^{|x_i|-1} xixi1),因此形如 α g \alpha^g αg 的元素都可以表示成 ( ( α x 1 ) ⋯   ) x r ((\alpha^{x_1})^\cdots)^{x_r} ((αx1))xr 的形式。自然,可以用图的遍历解决求轨道的问题:

Ω \Omega Ω 中元素为顶点构造有向图,若元素 β , γ ∈ Ω \beta,\gamma\in\Omega β,γΩ 满足存在 x ∈ X x\in X xX 使得 β x = γ \beta^x=\gamma βx=γ,则连一条从 β \beta β γ \gamma γ 的有向边。从 α \alpha α 出发能够到达的所有顶点(包括 α \alpha α 自身)就是轨道 α G \alpha^G αG

事实上不需显式建图也可以完成遍历过程。下面是 Python 风格的伪代码。

def orbit(alpha, X):
	## Calculate alpha^G where G=.
	Delta = [alpha]
	for beta in Delta:
		for x in X:
			if beta^x not in Delta:
				Delta.append(beta^x)
	return Delta

此处变量的命名为了区分大小写,稍微违反了一下命名规范;另外外层 for 循环的 Delta 会在循环内部改变,这种写法是不推荐的,这里只是为了简洁使用这种写法。运算符 ^ 表示群作用,变量 Delta(即 Δ \Delta Δ)表示已经搜索到的元素的集合,程序终止时 Δ \Delta Δ 就是所求的轨道。

通常应用该算法时不仅需要求出 α \alpha α 的轨道 α G \alpha^G αG,同时需要对每个 β ∈ α G \beta\in\alpha^G βαG 赋予一个代表元 u β u_\beta uβ,使得 α u β = β \alpha^{u_\beta}=\beta αuβ=β。事实上,因为满足 α g = β \alpha^g=\beta αg=β 的所有 g ∈ G g\in G gG 组成的集合就是稳定子 G α G_\alpha Gα 的右陪集,我们立刻可知代表元集 { u β : β ∈ α G } \{u_\beta:\beta\in\alpha^G\} {uβ:βαG}就是 G α G_\alpha Gα 的一个 transversal。计算代表元也不难,只需在遍历搜索的时候记录轨道中每个元素对应的代表元。若当前已知 β ∈ Δ \beta\in\Delta βΔ,继续向下搜索发现 β x = γ ∉ Δ \beta^x=\gamma\notin\Delta βx=γ/Δ,则令 u γ = u β x u_\gamma=u_\beta x uγ=uβx 即可,因为 α u γ = α u β x = β x = γ . \alpha^{u_\gamma}=\alpha^{u_\beta x}=\beta^x=\gamma. αuγ=αuβx=βx=γ.修改后的算法如下。

def orbit_u(alpha, X):
	## Calculate alpha^G and a transversal of G_alpha where G=.
	Delta = [(alpha, 1)]
	for beta, u_beta in Delta:
		for x in X:
			if beta^x not in [b for b,_ in Delta]:
				Delta.append((beta^x, u_beta*x))
	return Delta

该算法可以进一步优化,例如显然判断 β x ∈ Δ \beta^x\in\Delta βxΔ 的操作比较浪费时间,至于具体如何优化,后面再讲,这里只是先给出一个大体的框架。

稳定子

现在考虑如何计算稳定子,当然如之前所说,我们只要找到 G α G_\alpha Gα 的一组生成元就算完成任务。为此需要一个引理。

(Schreier 引理)设子群 H ≤ G = ⟨ X ⟩ H\le G=\langle X\rangle HG=X 有 transversal R R R,满足 1 ∈ R 1\in R 1R,则 Y = { r x ( r x ‾ ) − 1 : r ∈ R , x ∈ X } Y=\{rx(\overline{rx})^{-1}:r\in R,x\in X\} Y={rx(rx)1:rR,xX} H H H 的一组生成元。( Y Y Y 称为 Schreier 生成元。)
证明:首先由 H r x = H r x ‾ Hrx=H\overline{rx} Hrx=Hrx 可知 r s ( r x ‾ ) − 1 ∈ H rs(\overline{rx})^{-1}\in H rs(rx)1H,从而只需证明 H H H 中任意元素都可以表示为若干形如 r x ( r x ‾ ) − 1 rx(\overline{rx})^{-1} rx(rx)1 的元素的乘积。任取 h = x 1 ± x 2 ± ⋯ x k ± h=x_1^{\pm} x_2^{\pm}\cdots x_k^{\pm} h=x1±x2±xk±(其中 x i ∈ X x_i\in X xiX,上角标 ± \pm ± 表示可正可负, x + , x − x^+,x^- x+,x 分别表示 x , x − 1 x,x^{-1} x,x1),令 r i = x 1 ± x 2 ± ⋯ x i ± ‾ ( i = 1 , ⋯   , k ) , r 0 = 1 , r_i=\overline{x_1^\pm x_2^\pm\cdots x_i^\pm}(i=1,\cdots,k),r_0=1, ri=x1±x2±xi±(i=1,,k),r0=1,则有 h = ( r 0 x 1 ± r 1 − 1 ) ( r 1 x 2 ± r 2 − 1 ) ⋯ ( r k − 1 x k ± r k − 1 ) . h=(r_0x_1^\pm r_1^{-1})(r_1x_2^\pm r_2^{-1})\cdots(r_{k-1}x_k^\pm r_k^{-1}). h=(r0x1±r11)(r1x2±r21)(rk1xk±rk1).注意到 H r i = H x 1 ± x 2 ± ⋯ x i ± = H r i − 1 x i ± , H r i ∈ R , Hr_i=Hx_1^\pm x_2^\pm\cdots x_i^\pm=Hr_{i-1}x_i^\pm,Hr_i\in R, Hri=Hx1±x2±xi±=Hri1xi±,HriR, r i = r i − 1 x i ± ‾ r_i=\overline{r_{i-1}x_i^\pm} ri=ri1xi±,这说明上面的 h h h 的分拆中所有带正号的项都是 T T T 的元素。带负号的项可以变为 r i − 1 x i − 1 r i − 1 = ( r i x i r i − 1 − 1 ) − 1 , r_{i-1}x_i^{-1}r_i^{-1}=(r_ix_ir_{i-1}^{-1})^{-1}, ri1xi1ri1=(rixiri11)1,
而由 H r i x i = H ( x 1 ± x 2 ± ⋯ x i − 1 ± x i − 1 ) x i = H x 1 ± x 2 ± ⋯ x i − 1 ± = H r i − 1 Hr_ix_i=H(x_1^\pm x_2^\pm\cdots x_{i-1}^\pm x_i^{-1})x_i=Hx_1^\pm x_2^\pm\cdots x_{i-1}^\pm=Hr_{i-1} Hrixi=H(x1±x2±xi1±xi1)xi=Hx1±x2±xi1±=Hri1 可知 r i − 1 = r i x i ‾ r_{i-1}=\overline{r_ix_i} ri1=rixi,因此 r i − 1 x i − 1 r i − 1 r_{i-1}x_i^{-1}r_i^{-1} ri1xi1ri1 T T T 的某个元素的逆元。至此就证明了 h h h 可以由 Y Y Y 中的元素生成。

特别地,在引理中取 H = G α H=G_\alpha H=Gα,则 r ∈ R r\in R rR 可以替换成 u β ( β ∈ α G ) u_\beta(\beta\in\alpha^G) uβ(βαG),同时注意到 G α ( u β x ) G_\alpha(u_\beta x) Gα(uβx) α \alpha α 映射到 β x \beta^x βx,可知 u β x ‾ = u β x \overline{u_\beta x}=u_{\beta^x} uβx=uβx,从而得到 Schreier 引理的轨道-稳定子版本:

(Schreier 引理)设群 G = ⟨ X ⟩ G=\langle X\rangle G=X Ω \Omega Ω 上的置换群, α ∈ Ω \alpha\in\Omega αΩ,对任意 β ∈ α G \beta\in\alpha^G βαG 有一代表元 u β u_\beta uβ 满足 α u β = β \alpha^{u_\beta}=\beta αuβ=β,并且 u α = 1 u_\alpha=1 uα=1,则 Y = { u β x u β x − 1 : β ∈ α G , x ∈ X } Y=\{u_\beta xu_{\beta^x}^{-1}:\beta\in\alpha^G,x\in X\} Y={uβxuβx1:βαG,xX} G α G_\alpha Gα 的一组生成元。

利用上述引理可以在计算轨道和 transversal 的同时计算稳定子的生成元。

def orbit_stabilizer(alpha, X):
	## Calculate alpha^G and a transversal of G_alpha where G=,
	## and a generator set Y of =G_alpha.
	Delta = [(alpha, 1)]
	Y = []
	for beta, u_beta in Delta:
		for x in X:
			if beta^x not in [b for b,_ in Delta]:
				Delta.append((beta^x, u_beta*x))
			Y.append((u_beta)*x*(u_(beta^x))')
	return Delta,Y

伪代码中 x ′ x' x 表示 x x x 的逆元。上述代码很明显可以改进:如果 if 条件判断结果为真,则 u β x = u β x u_{\beta^x}=u_\beta x uβx=uβx,此时 u β x u β x − 1 = 1 u_\beta x u_{\beta^x}^{-1}=1 uβxuβx1=1,不需要把它添入 Y Y Y 中,修改后的伪代码如下:

def orbit_stabilizer(alpha, X):
	## Calculate alpha^G and a transversal of G_alpha where G=,
	## and a generator set Y of =G_alpha.
	Delta = [(alpha, 1)]
	Y = []
	for beta, u_beta in Delta:
		for x in X:
			if beta^x not in [b for b,_ in Delta]:
				Delta.append((beta^x, u_beta*x))
			else:
				Y.append((u_beta)*x*(u_(beta^x))')
	return Delta,Y
Schreier 向量

假如判断 β x \beta^x βx 是否属于 Δ \Delta Δ 的操作可以在常数时间内完成(当然,如果判断结果为真,则顺便可以找到相应的 u β x u_{\beta^x} uβx),则 orbit_u 的伪代码的时间复杂度应为 O ( ∣ X ∣ n 2 ) O(|X|n^2) O(Xn2)(准确地说是 O ( ∣ X ∣ ∣ Δ ∣ n ) O(|X||\Delta|n) O(XΔn)),其中 n = ∣ Ω ∣ n=|\Omega| n=Ω。这是因为循环次数是 O ( ∣ X ∣ ∣ Δ ∣ ) O(|X||\Delta|) O(XΔ) 的,而每次循环都需要计算置换的复合,用时为 O ( n ) O(n) O(n)。(当然如果只求轨道不求 u β u_\beta uβ,就无需计算置换的复合,时间复杂度为 O ( ∣ X ∣ ∣ Δ ∣ ) O(|X||\Delta|) O(XΔ)。)

是否可以在常数时间内完成判断操作?当然可以。不妨设 Ω = { 1 , ⋯   , n } \Omega=\{1,\cdots,n\} Ω={1,,n},只要开一个长度为 n n n 的数组 A A A,若 β ∈ Δ \beta\in\Delta βΔ,则 A [ β ] = u β A[\beta]=u_\beta A[β]=uβ,否则 A [ β ] A[\beta] A[β] 为空对象。初始时将数组中所有元素置空,仅将 A [ α ] A[\alpha] A[α] 设置为恒等置换即可。时间复杂度 O ( ∣ X ∣ n 2 ) O(|X|n^2) O(Xn2),空间复杂度 O ( n 2 ) O(n^2) O(n2)

如果需要节省空间,可以改用平衡树等数据结构维护 Δ \Delta Δ 列表,但这样只是把空间复杂度改进为 O ( ∣ Δ ∣ n ) O(|\Delta|n) O(Δn),如果 Δ = Ω \Delta=\Omega Δ=Ω(或者 Δ \Delta Δ n n n 小不了多少),这种改进就有点废了。实际上,我们未必要对所有 β ∈ Δ \beta\in\Delta βΔ 都存储 u β u_\beta uβ,那样太浪费空间了;我们用类似于记录前驱的方式存储必要的信息,需要算 u β u_\beta uβ 时可以临时再算。

对于 α ≠ γ ∈ Δ \alpha\ne\gamma\in\Delta α̸=γΔ,根据上述算法可知存在 β ∈ Δ , x ∈ X \beta\in\Delta,x\in X βΔ,xX 使得 γ = β x , u γ = u β x \gamma=\beta^x,u_\gamma=u_\beta x γ=βx,uγ=uβx,因此对任意 γ \gamma γ 只要存储与之相关的 β , x \beta,x β,x 即可。事实上只需存储 x x x β \beta β 可由 β = γ x − 1 \beta=\gamma^{x^{-1}} β=γx1 计算;而我们不需要存储 x x x 的真实值,只需存储 x x x X X X 中第几个元素即可。也就是说,我们需要存储一个向量 v [ 1.. n ] v[1..n] v[1..n],满足:

v [ α ] = − 1 v[\alpha]=-1 v[α]=1
γ \gamma γ 是通过 β x i \beta^{x_i} βxi 添加进 Δ \Delta Δ 的(其中 β , γ ∈ Δ \beta,\gamma\in\Delta β,γΔ),则说明 u γ = u β x i u_\gamma=u_\beta x_i uγ=uβxi x i x_i xi 是列表 X X X 的第 i i i 个元素,从 1 开始计数),记 v [ γ ] = i v[\gamma]=i v[γ]=i
β ∉ Δ \beta\notin\Delta β/Δ,则记 v [ β ] = 0 v[\beta]=0 v[β]=0

该向量称为 Schreier 向量,它可以同时完成“判断 β x \beta^x βx 是否属于 Δ \Delta Δ”和“存储 u β u_\beta uβ”的操作。对应的伪代码如下:

def orbit_sv(alpha, X):
	## Calculate alpha^G and a transversal of G_alpha where G=.
	## Assuming Omega = [1..n].
	r = len(X)
	v = [0] * (n+1)
	v[alpha]=-1
	Delta = [alpha]
	for beta in Delta:
		for i in range(1, r+1):
			if v[beta^(X[i])] == 0:
				Delta.append(beta^(X[i]))
				v[beta^(X[i])] = i
	return Delta, v

该算法的时间复杂度为 O ( ∣ X ∣ ∣ Δ ∣ ) O(|X||\Delta|) O(XΔ),空间复杂度为 O ( n ) O(n) O(n),可以说是相当不错。当然这也不是毫无代价的:计算 u β u_\beta uβ 有额外的时间开销。计算 u β u_\beta uβ 的算法实际上就是沿向量 v v v 提供的数值一路倒推,直到倒推到 α \alpha α,具体如下所示。

def u_beta(beta, v, X):
	## Calculate u_beta.
	## Input v is a Schreier vector for alpha in Omega.
	if v[beta] == 0:
		return None
	u = 1
	k = v[beta]
	while k != -1:
		u = X[k]*u
		beta = beta^(X[k]')
		k = v[beta]
	return u

关于时间复杂度,需要提醒的是计算 β x k − 1 \beta^{x_k^{-1}} βxk1 所需时间是常数(这与置换的存储方式有关,对于置换 g g g,如果同时存储 β g , β g − 1 ( β ∈ Ω ) \beta^g,\beta^{g^{-1}}(\beta\in\Omega) βg,βg1(βΩ),就能在常数时间内算出 β x k − 1 \beta^{x_k^{-1}} βxk1)。在这里这件事无所谓了,因为计算 u ← x k u u\leftarrow x_ku uxku 的时间是 O ( n ) O(n) O(n)。算法整体的复杂度最坏情况下可能达到 O ( n 2 ) O(n^2) O(n2),例如 X X X 仅有一个元素 ( 123 ⋯ n ) (123\cdots n) (123n),而 α = 1 , β = n \alpha=1,\beta=n α=1,β=n,此时需要倒推 n − 1 n-1 n1 步。显然这与 Schreier 树(就是刚才进行搜索得到的搜索树)的深度有关,深度最坏情况下可以达到 O ( n ) O(n) O(n),这样时间复杂度就比较糟糕,而如果 X X X 不只有一个元素,哪怕只有两个元素,树的深度也可能大幅降低。

至于如何计算稳定子,实际上我们不一定要立刻把 Schreier 生成元一次算出来,那样也会浪费空间。后面会看到,我们需要遍历所有 Schreier 生成元,用来判断稳定子 G α G_\alpha Gα 是否是另一个群的子群。回顾 Schreier 生成元的定义 Y = { u β x u β x − 1 : β ∈ α G , x ∈ X } Y=\{u_\beta xu_{\beta^x}^{-1}:\beta\in\alpha^G,x\in X\} Y={uβxuβx1:βαG,xX},显然我们随时可以通过遍历 β ∈ α G , x ∈ X \beta\in\alpha^G,x\in X βαG,xX 来遍历所有 Schreier 生成元,不用先算出所有生成元存起来。

基(Base)& 强生成元(SGS)

基本定义

本节继续假设 G ≤ Sym ⁡ ( Ω ) , Ω = { 1 , ⋯   , n } . G\le\operatorname{Sym}(\Omega),\Omega=\{1,\cdots,n\}. GSym(Ω),Ω={1,,n}.

B = ( β 1 , ⋯   , β m ) B=(\beta_1,\cdots,\beta_m) B=(β1,,βm) Ω \Omega Ω 中某些互不相同的元素组成的序列,若群 G G G 中唯一固定所有 β i ( 1 ≤ i ≤ m ) \beta_i(1\le i\le m) βi(1im) 的置换是恒等置换,则称 B B B G G G 的一组(base)。该定义可以用逐点稳定子的概念重新叙述:记 G ( β 1 , ⋯   , β m ) = { g ∈ G : β i g = β i ( 1 ≤ i ≤ m ) } G_{(\beta_1,\cdots,\beta_m)}=\{g\in G:\beta_i^g=\beta_i(1\le i\le m)\} G(β1,,βm)={gG:βig=βi(1im)} G G G 关于 ( β 1 , ⋯   , β m ) (\beta_1,\cdots,\beta_m) (β1,,βm) 的逐点稳定子,则 B B B G G G 的一组基当且仅当 G ( β 1 , ⋯   , β m ) = 1 G_{(\beta_1,\cdots,\beta_m)}=1 G(β1,,βm)=1

接下来,我们定义 G [ i ] = G ( β 1 , ⋯   , β i − 1 ) = { g ∈ G : β j g = β j ( 1 ≤ j < i ) } , G^{[i]}=G_{(\beta_1,\cdots,\beta_i-1)}=\{g\in G:\beta_j^g=\beta_j(1\le j<i)\}, G[i]=G(β1,,βi1)={gG:βjg=βj(1j<i)},特别地,定义 G [ 1 ] = G G^{[1]}=G G[1]=G,则该定义实际上给出了一条子群链: G = G [ 1 ] ≥ G [ 2 ] ≥ ⋯ ≥ G [ m ] ≥ G [ m + 1 ] = 1. G=G^{[1]}\ge G^{[2]}\ge\cdots\ge G^{[m]}\ge G^{[m+1]}=1. G=G[1]G[2]G[m]G[m+1]=1.如果链中每相邻两个群都不相等,则称这组基 ( β 1 , ⋯   , β m ) (\beta_1,\cdots,\beta_m) (β1,,βm) 是无赘余(nonredundant)的。同一个群可以有长度不同的无赘余基,例如取 G = ⟨ ( 12 ) ( 3456 ) ⟩ ≤ S 6 G=\langle(12)(3456)\rangle\le S_6 G=(12)(3456)S6,则 ( 1 , 3 ) (1,3) (1,3) ( 3 ) (3) (3) 都是无赘余基。

链中相邻两项 G [ i ] G^{[i]} G[i] G [ i + 1 ] G^{[i+1]} G[i+1] 的唯一区别是后者的置换必须固定 β i \beta_i βi,前者不需要,容易看出 G [ i + 1 ] G^{[i+1]} G[i+1] 实际上就等于 G β i [ i ] G^{[i]}_{\beta_i} Gβi[i]。通过计算阶数可得
∣ G ∣ = ∏ i = 1 m ∣ G [ i ] : G [ i + 1 ] ∣ = ∏ i = 1 m ∣ β i G [ i ] ∣ , |G|=\prod_{i=1}^m|G^{[i]}:G^{[i+1]}|=\prod_{i=1}^m|\beta_i^{G^{[i]}}|, G=i=1mG[i]:G[i+1]=i=1mβiG[i],因此可以估计链的长度: 2 ∣ B ∣ ≤ ∣ G ∣ ≤ n ∣ B ∣ 2^{|B|}\le|G|\le n^{|B|} 2BGnB(当然前提是这组基无赘余),即链的长度不会超过 log ⁡ ∣ G ∣ \log|G| logG。当 ∣ G ∣ |G| G 大到接近 ∣ Sym ⁡ ( Ω ) ∣ = n ! |\operatorname{Sym}(\Omega)|=n! Sym(Ω)=n! 时,这个结论没什么用,因为 O ( log ⁡ ( n ! ) ) = n log ⁡ n O(\log(n!))=n\log n O(log(n!))=nlogn,而显然 ∣ B ∣ ≤ n |B|\le n Bn。但如果 ∣ G ∣ |G| G 比较小,这个结论就有用了。

S S S G G G 的生成元集,若 S S S 满足更强的条件: ⟨ S ∩ G [ i ] ⟩ = G [ i ] ( 1 ≤ i ≤ m + 1 ) , \langle S\cap G^{[i]}\rangle=G^{[i]}(1\le i\le m+1), SG[i]=G[i](1im+1),(需要提醒的是 ⟨ ∅ ⟩ = 1. \langle\varnothing\rangle=1. =1.)则称 S S S 为群 G G G 关于基 B B B强生成元集(SGS)。
初次接触该定义可能会一脸懵逼,其实并不难理解: ⟨ S ∩ G [ i ] ⟩ = G [ i ] \langle S\cap G^{[i]}\rangle=G^{[i]} SG[i]=G[i] 大体上意思就是“使用生成元集 S S S 中的元素,不需要移动元素 β 1 , ⋯   , β i − 1 \beta_1,\cdots,\beta_{i-1} β1,,βi1 就可以自由排列其他的元素”。不妨考虑魔方群的情况,假设 S = { U , D , L , R , F , B } S=\{\text{U},\text{D},\text{L},\text{R},\text{F},\text{B}\} S={U,D,L,R,F,B} 是 6 种基本操作的集合, β 1 , ⋯   , β 28 \beta_1,\cdots,\beta_{28} β1,,β28 是下两层的所有非中心块贴纸。熟悉魔方的都知道不动前两层不可能还原第三层(除非运气好),因此不符合 SGS 的定义。具体地说,就是 ⟨ S ∩ G [ 29 ] ⟩ = ⟨ U ⟩ ≠ G [ 29 ] \langle S\cap G^{[29]}\rangle=\langle\text{U}\rangle\ne G^{[29]} SG[29]=U̸=G[29]
另外再举一个简单的置换群例子:取 G = S 4 G=S_4 G=S4 为 4 阶对称群, B = ( 1 , 2 , 3 ) B=(1,2,3) B=(1,2,3) 是一组基,则 S = { ( 1234 ) , ( 34 ) } S=\{(1234),(34)\} S={(1234),(34)} G G G 的一组生成元,但不是 SGS,因为 ⟨ S ∩ G [ 2 ] ⟩ ≠ G [ 2 ] \langle S\cap G^{[2]}\rangle\ne G^{[2]} SG[2]̸=G[2]。这是因为在固定元素 1 1 1 不动的前提下 S S S 中允许的置换只有 ( 34 ) (34) (34),仅用该置换不能对元素 2 , 3 , 4 2,3,4 2,3,4 自由置换。可以验证 S ′ = { ( 1234 ) , ( 234 ) , ( 34 ) } S'=\{(1234),(234),(34)\} S={(1234),(234),(34)} 是一组 SGS。

筛选过程

假设我们已经有了一组基和对应的一组 SGS,定义 Δ i = β i G [ i ] \Delta_i=\beta_i^{G^{[i]}} Δi=βiG[i],稳定子 G β i [ i ] G^{[i]}_{\beta_i} Gβi[i] 对应的 transversal 为 R i R_i Ri。通过 orbit_sv 和 u_beta 可以算出 Δ i \Delta_i Δi R i R_i Ri。现在对任意 g ∈ G g\in G gG,我们可以将 g g g 分解为如下形式: g = r m r m − 1 ⋯ r 1 ( r i ∈ R i , 1 ≤ i ≤ m ) . g=r_mr_{m-1}\cdots r_1(r_i\in R_i,1\le i\le m). g=rmrm1r1(riRi,1im).分解方法是逐步进行的:注意 G = G [ 1 ] G=G^{[1]} G=G[1]。记 g 1 = g g_1=g g1=g,首先取 r 1 = u β 1 g 1 , r_1=u_{\beta_1^{g_1}}, r1=uβ1g1,
此时有 β 1 r 1 = β 1 g 1 \beta_1^{r_1}=\beta_1^{g_1} β1r1=β1g1,这说明 g 1 r 1 − 1 ∈ G β 1 [ 1 ] = G [ 2 ] g_1r_1^{-1}\in G^{[1]}_{\beta_1}=G^{[2]} g1r11Gβ1[1]=G[2],将它记为 g 2 g_2 g2。然后再取 r 2 = u β 2 g 2 , r_2=u_{\beta_2^{g_2}}, r2=uβ2g2,此时有 β 2 r 2 = β 2 g 2 \beta_2^{r_2}=\beta_2^{g_2} β2r2=β2g2,这说明 g 2 r 2 − 1 ∈ G β 2 [ 2 ] = G [ 3 ] g_2r_2^{-1}\in G^{[2]}_{\beta_2}=G^{[3]} g2r21Gβ2[2]=G[3],将它记为 g 3 g_3 g3。该过程可以循环进行,直到得到 g m + 1 ∈ G [ m + 1 ] = 1 g_{m+1}\in G^{[m+1]}=1 gm+1G[m+1]=1。此时就有 g = g 1 = g 2 r 1 = g 3 r 2 r 1 = ⋯ = g m + 1 r m ⋯ r 1 = r m ⋯ r 1 . g=g_1=g_2r_1=g_3r_2r_1=\cdots=g_{m+1}r_m\cdots r_1=r_m\cdots r_1. g=g1=g2r1=g3r2r1==gm+1rmr1=rmr1.实际上,该算法可以用于 S n S_n Sn 中的任意元素,只不过对 g ∉ G g\notin G g/G 执行该算法会在中途终止。一种情况是计算过程中某个 g i g_i gi 不满足 β i g i ∈ β 1 G [ i ] \beta_i^{g_i}\in\beta_1^{G^{[i]}} βigiβ1G[i],这样分解过程只能终止。另一种情况是 g m + 1 ≠ 1 g_{m+1}\ne 1 gm+1̸=1,这说明 G [ m + 1 ] ≠ 1 G^{[m+1]}\ne 1 G[m+1]̸=1,即 ( β 1 , ⋯   , β m ) (\beta_1,\cdots,\beta_m) (β1,,βm) 并不是一组基。该算法称为筛选(sift)过程,该过程要么判断 g ∈ G g\in G gG 并给出一组分解 g = r m r m − 1 ⋯ r 1 g=r_mr_{m-1}\cdots r_1 g=rmrm1r1,要么判断 g ∉ G g\notin G g/G 并返回最终得到的那个 g i g_i gi 以及 i i i 值,这两个返回值都是有用的——它可以用来填补未完成的 SGS 的漏洞,以得到合乎要求的 SGS。返回值 g i g_i gi 称为 siftee,似乎可以翻译为筛渣。

Schreier-Sims 算法

下面的引理与 Schreier-Sims 算法有关。

引理 设 G ≤ Sym ⁡ ( Ω ) G\le\operatorname{Sym}(\Omega) GSym(Ω) ( β 1 , ⋯   , β k ) (\beta_1,\cdots,\beta_k) (β1,,βk) Ω \Omega Ω 中某些互不相同的元素组成的序列。对 1 ≤ j ≤ k + 1 1\le j\le k+1 1jk+1,设 S j S_j Sj G [ j ] = G ( β 1 , ⋯   , β j − 1 ) G^{[j]}=G_{(\beta_1,\cdots,\beta_{j-1})} G[j]=G(β1,,βj1) 的子集,满足 ⟨ S j ⟩ ≥ ⟨ S j + 1 ⟩ ( 1 ≤ j ≤ k ) \langle S_j\rangle\ge\langle S_{j+1}\rangle(1\le j\le k) SjSj+1(1jk),且 ⟨ S 1 ⟩ = G , S k + 1 = ∅ \langle S_1\rangle=G,S_{k+1}=\varnothing S1=G,Sk+1=。如果条件 ⟨ S j ⟩ β j = ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}=\langle S_{j+1}\rangle Sjβj=Sj+1(该条件称为条件 *)对任意 1 ≤ j ≤ k 1\le j\le k 1jk 都成立,则 B = ( β 1 , ⋯   , β k ) B=(\beta_1,\cdots,\beta_k) B=(β1,,βk) G G G 的一组基, S = ⋃ j = 1 k S j S=\bigcup_{j=1}^k S_j S=j=1kSj G G G 关于 B B B 的一组 SGS。
注:条件 * 等价于 ⟨ S j ⟩ β j ≤ ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}\le\langle S_{j+1}\rangle SjβjSj+1,这是因为 ⟨ S j ⟩ β j ≥ ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}\ge\langle S_{j+1}\rangle SjβjSj+1 总成立: ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1 的所有元素都属于 ⟨ S j ⟩ \langle S_j\rangle Sj,且都固定 β j \beta_j βj 不动。
证明:对 k k k 使用归纳法。 k = 1 k=1 k=1 时条件变为 ⟨ S 1 ⟩ β 1 = G β 1 = ⟨ S j + 1 ⟩ = ⟨ ∅ ⟩ = 1 \langle S_1\rangle_{\beta_1}=G_{\beta_1}=\langle S_{j+1}\rangle=\langle\varnothing\rangle=1 S1β1=Gβ1=Sj+1==1,容易验证命题是成立的。
现在假设该命题对更小的 k k k 成立,于是立刻可得 B ∗ = ( β 2 , ⋯   , β k ) B^*=(\beta_2,\cdots,\beta_k) B=(β2,,βk) G ∗ = ⟨ S 2 ⟩ G^*=\langle S_2\rangle G=S2 的基, S ∗ = ⋃ j = 2 k S j S^*=\bigcup_{j=2}^k S_j S=j=2kSj G ∗ G^* G 关于 B ∗ B^* B 的一组 SGS。下面验证 ⟨ S ∩ G [ i ] ⟩ = G [ i ] \langle S\cap G^{[i]}\rangle=G^{[i]} SG[i]=G[i] 1 ≤ i ≤ m 1\le i\le m 1im 均成立。
i = 1 i=1 i=1 时该条件显然是成立的,因为 ⟨ S ∩ G ⟩ = ⟨ S ⟩ ≥ ⟨ S 1 ⟩ = G \langle S\cap G\rangle=\langle S\rangle\ge\langle S_1\rangle=G SG=SS1=G
i = 2 i=2 i=2 时需要验证 ⟨ S ∩ G [ 2 ] ⟩ = ⟨ G [ 2 ] ⟩ \langle S\cap G^{[2]}\rangle=\langle G^{[2]}\rangle SG[2]=G[2],这可由 G β 1 = ⟨ S 1 ⟩ β 1 = ⟨ S 2 ⟩ ≤ ⟨ S ∩ G β 1 ⟩ G_{\beta_1}=\langle S_1\rangle_{\beta_1}=\langle S_2\rangle\le\langle S\cap G_{\beta_1}\rangle Gβ1=S1β1=S2SGβ1 得到(反向不等号是显然的)。
i > 2 i>2 i>2 时需要用到归纳假设,已知 S ∗ S^* S G ∗ G^* G 关于 B ∗ B^* B 的一组 SGS,说明 ⟨ S ∗ ∩ ⟨ S 2 ⟩ ( β 2 , ⋯   , β i − 1 ) ⟩ = ⟨ S 2 ⟩ ( β 2 , ⋯   , β i − 1 ) , \langle S^*\cap\langle S_2\rangle_{(\beta_2,\cdots,\beta_{i-1})}\rangle=\langle S_2\rangle_{(\beta_2,\cdots,\beta_{i-1})}, SS2(β2,,βi1)=S2(β2,,βi1),从而有 ⟨ S ∩ G ( β 2 , ⋯   , β i − 1 ) ⟩ ≥ ⟨ S ∗ ∩ ⟨ S 2 ⟩ ( β 2 , ⋯   , β i − 1 ) ⟩ = ⟨ S 2 ⟩ ( β 2 , ⋯   , β i − 1 ) = ( G β 1 ) ( β 2 , ⋯   , β i − 1 ) = G [ i ] . \langle S\cap G_{(\beta_2,\cdots,\beta_{i-1})}\rangle\ge\langle S^*\cap\langle S_2\rangle_{(\beta_2,\cdots,\beta_{i-1})}\rangle=\langle S_2\rangle_{(\beta_2,\cdots,\beta_{i-1})}=(G_{\beta_1})_{(\beta_2,\cdots,\beta_{i-1})}=G^{[i]}. SG(β2,,βi1)SS2(β2,,βi1)=S2(β2,,βi1)=(Gβ1)(β2,,βi1)=G[i].同样,反向不等号是显然的。

实际上条件 ⟨ S j ⟩ β j = ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}=\langle S_{j+1}\rangle Sjβj=Sj+1 的意思就是“使用 S j + 1 S_{j+1} Sj+1 中的元素自由组合,不需要移动元素 β j \beta_j βj 就可以组合出 S j S_j Sj 中元素的任意组合”。
Schreier-Sims 算法的输入是 G = ⟨ X ⟩ G=\langle X\rangle G=X,输出是 G G G 的一组无赘余基 B B B,以及相关的 SGS S S S。算法执行过程中需要维护以下对象:

  • 维护变量 m m m,表示当前 B B B 的长度。
  • 维护一组序列 B = ( β 1 , ⋯   , β m ) B=(\beta_1,\cdots,\beta_m) B=(β1,,βm),保证 B B B 是无赘余的,即 G [ i ] > G [ i + 1 ] ( 1 ≤ i ≤ m ) G^{[i]}>G^{[i+1]}(1\le i\le m) G[i]>G[i+1](1im),事实上该算法保证 G [ i ] ( 1 ≤ i ≤ m ) G^{[i]}(1\le i\le m) G[i](1im) 必有一个移动 β i \beta_i βi 的元素;(不保证 B B B 是基,也就是说 G [ m + 1 ] G^{[m+1]} G[m+1] 未必是平凡群 1 1 1
  • 维护一组集合 ( S 1 , ⋯   , S m ) (S_1,\cdots,S_m) (S1,,Sm),满足 S i ⊆ G [ i ] , ⟨ S i ⟩ ≥ ⟨ S i + 1 ⟩ ( 1 ≤ i ≤ m ) S_i\subseteq G^{[i]},\langle S_i\rangle\ge\langle S_{i+1}\rangle(1\le i\le m) SiG[i],SiSi+1(1im),这里默认 S m + 1 = ∅ S_{m+1}=\varnothing Sm+1=
  • 维护一个变量 j j j,满足 0 ≤ j ≤ m 0\le j\le m 0jm,并且保证对任意 i ∈ ( j , m ] i\in(j,m] i(j,m] 都满足条件 *,即 ⟨ S i ⟩ β i = ⟨ S i + 1 ⟩ ( j < i ≤ m ) \langle S_i\rangle_{\beta_i}=\langle S_{i+1}\rangle(j<i\le m) Siβi=Si+1(j<im)。换句话说, j j j 后面那些集合 S j + 1 , ⋯   , S m S_{j+1},\cdots,S_m Sj+1,,Sm 满足引理的条件,因而能够组成 SGS。

初始时,我们设置 m = 1 , B = ( β 1 ) , S 1 = X , j = 1 , m=1,B=(\beta_1),S_1=X,j=1, m=1,B=(β1),S1=X,j=1,其中 β 1 \beta_1 β1 是任意一个至少被 X X X 中某个置换移动的元素,这样可以保证无赘余。
初始化之后进入循环。每次循环开始时,首先对群 ⟨ S j ⟩ \langle S_j\rangle Sj 和元素 β j \beta_j βj 使用之前介绍的算法,计算轨道和 transversal。然后检验 ⟨ S j ⟩ β j \langle S_j\rangle_{\beta_j} Sjβj 的所有 Schreier 生成元是否都属于 ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1(用筛选过程检验属于关系,因为我们已经拥有了 ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1 的一组基 ( β j + 1 , ⋯   , β m ) (\beta_{j+1},\cdots,\beta_m) (βj+1,,βm) 以及相应的 SGS ⋃ i = j + 1 m S i \bigcup_{i=j+1}^m S_i i=j+1mSi),也就是检验 ⟨ S j ⟩ β j \langle S_j\rangle_{\beta_j} Sjβj 是否是 ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1 的子群。结果有以下两种。

  • ⟨ S j ⟩ β j ≤ ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}\le\langle S_{j+1}\rangle SjβjSj+1,此时直接令 j j j 自减 1 1 1

这里没什么好说的,我们需要维护的性质显然还是成立的。

  • ⟨ S j ⟩ β j ̸ ≤ ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}\not\le\langle S_{j+1}\rangle Sjβj̸Sj+1,失败原因是 ⟨ S j ⟩ β j \langle S_j\rangle_{\beta_j} Sjβj 的某个生成元 g g g 不属于 ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1。此时将筛选过程返回的 siftee(记为 h h h)添入 S j + 1 S_{j+1} Sj+1 中,并令 j j j 自增 1 1 1。如果 j = m j=m j=m,则令 m m m 也自增 1 1 1,并取 β m \beta_m βm 为任意一个被置换 h h h 移动的元素。

这里需要检验一下需要维护的性质是否仍然成立。因为 ⟨ S j + 1 ⟩ \langle S_{j+1}\rangle Sj+1 的基是 ( β j + 1 , ⋯   , β m ) (\beta_{j+1},\cdots,\beta_m) (βj+1,,βm),并且已知 g g g 固定 β 1 , ⋯   , β j \beta_1,\cdots,\beta_j β1,,βj 不动,从而筛选过程可将 g g g 分解为 g = h r i r i − 1 ⋯ r j + 1 , r k ∈ R k ( j < k ≤ i ) , h ≠ 1 , j ≤ i ≤ m , g=hr_i r_{i-1}\cdots r_{j+1},r_k\in R_k(j<k\le i),h\ne 1,j\le i\le m, g=hriri1rj+1,rkRk(j<ki),h̸=1,jim,并且 h h h 固定 β 1 , ⋯   , β i \beta_1,\cdots,\beta_i β1,,βi 不动。于是将 h h h 添入 S j + 1 S_{j+1} Sj+1 之后就离 ⟨ S j ⟩ β j = ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}=\langle S_{j+1}\rangle Sjβj=Sj+1 更近了一步。但是 ⟨ S j + 1 ⟩ β j + 1 = ⟨ S j + 2 ⟩ \langle S_{j+1}\rangle_{\beta_{j+1}}=\langle S_{j+2}\rangle Sj+1βj+1=Sj+2 此时可能不再成立,因此需要让 j j j 自增 1 1 1。真正需要验证的是 ⟨ S i ⟩ ≥ ⟨ S i + 1 ⟩ \langle S_i\rangle\ge\langle S_{i+1}\rangle SiSi+1 是否仍成立,这是显然的( h h h 显然属于 ⟨ S j ⟩ \langle S_j\rangle Sj)。 h h h 属于 G [ j + 1 ] G^{[j+1]} G[j+1] 也是显然的。
对于 j = m j=m j=m 的情况要特别讨论,因为我们将 h h h 添入 S m + 1 S_{m+1} Sm+1 中显然违反了 S m + 1 = ∅ S_{m+1}=\varnothing Sm+1= 的规定。此时考虑让 m m m 自增 1 1 1,并取 β m \beta_m βm 为任意一个被置换 h h h 移动的元素,这样就能保证 G [ m ] G^{[m]} G[m] 有一个移动了 β m \beta_m βm 的元素。

终止条件为 j = 0 j=0 j=0,此时 B B B 是一组无赘余的基, S = ⋃ i = 1 m S i S=\bigcup_{i=1}^m S_i S=i=1mSi G G G 关于 B B B 的 SGS。

接下来证明该算法一定能终止,并讨论时间复杂度。注意到:

  • B = ( β 1 , ⋯   , β m ) B=(\beta_1,\cdots,\beta_m) B=(β1,,βm) 的长度是 O ( log ⁡ ∣ G ∣ ) O(\log|G|) O(logG)
  • 每个 S k S_k Sk 扩张的次数是 O ( log ⁡ ∣ G ∣ ) O(\log|G|) O(logG),因为每次扩张都会使 ⟨ S k ⟩ \langle S_k\rangle Sk 的阶严格增加,从而至少变为原来的 2 2 2 倍;(至此已经说明该算法一定会终止)
  • 每次扩张 S k S_k Sk 时都需要重新计算 ⟨ S k ⟩ , β k \langle S_k\rangle,\beta_k Sk,βk 对应的轨道和 transversal。但是庆幸的是,我们不必推倒重来:原先已经确定属于 Δ k \Delta_k Δk 的元素无论是 Schreier 向量的值还是直接存储 u β u_\beta uβ 值,都没必要重算了。换句话说,在整个算法过程中,对任意 γ ∈ Ω \gamma\in\Omega γΩ g ∈ S k g\in S_k gSk 都只需要计算一次 γ g \gamma^g γg。因此这一部分的时间复杂度为 O ( ∣ B ∣ log ⁡ ∣ G ∣ n + ∣ X ∣ n ) O(|B|\log|G|n+|X|n) O(BlogGn+Xn)(因为 S 1 S_1 S1 初始时有 ∣ X ∣ |X| X 个元素),也就是 O ( n log ⁡ 2 ∣ G ∣ + ∣ X ∣ n ) O(n\log^2|G|+|X|n) O(nlog2G+Xn)
  • 如果我们需要显式存储 transversal(即直接计算 u β u_\beta uβ 值并存储),注意对每个 S k S_k Sk β k \beta_k βk,搜索时 Ω \Omega Ω 中每个元素最多只会访问一次(即使考虑 S k S_k Sk 的动态扩充也一样,因为已经算过的没必要重算),也就是说为了计算 u β u_\beta uβ 值,需要计算 O ( n ) O(n) O(n) 次置换复合,每次用时 O ( n ) O(n) O(n),因此这一部分额外的时间复杂度为 O ( n 2 log ⁡ ∣ G ∣ ) O(n^2\log|G|) O(n2logG)
  • 最后考虑检验 ⟨ S j ⟩ β j ≤ ⟨ S j + 1 ⟩ \langle S_j\rangle_{\beta_j}\le\langle S_{j+1}\rangle SjβjSj+1 是否成立的开销,此时注意对任意 ( β , s ) ∈ Δ k × S k (\beta,s)\in\Delta_k\times S_k (β,s)Δk×Sk,我们只会计算一次 u β s u β s − 1 u_\beta su_{\beta^s}^{-1} uβsuβs1 的值并进行筛选过程,因为如果结果是它属于 ⟨ S k + 1 ⟩ \langle S_{k+1}\rangle Sk+1,那以后肯定也一直属于,没必要再算了;如果不属于 ⟨ S k + 1 ⟩ \langle S_{k+1}\rangle Sk+1,那把 siftee 添入 S k + 1 S_{k+1} Sk+1 之后 u β s u β s − 1 u_\beta su_{\beta^s}^{-1} uβsuβs1 就自动属于 ⟨ S k + 1 ⟩ \langle S_{k+1}\rangle Sk+1 了,以后也没必要再算。因此整个算法过程中 Δ k × S k \Delta_k\times S_k Δk×Sk 的每个元素只需遍历一遍,再对 1 ≤ k ≤ m 1\le k\le m 1km 做加总,次数为 O ( n log ⁡ 2 ∣ G ∣ + ∣ X ∣ n ) O(n\log^2|G|+|X|n) O(nlog2G+Xn)
    此处体现了显式存储 transversal 的长处,可以在常数时间内得到 u β u_\beta uβ,之后每次筛选操作只需进行 O ( log ⁡ ∣ G ∣ ) O(\log|G|) O(logG) 次置换复合,于是该部分的时间开销就是 O ( n 2 log ⁡ 3 ∣ G ∣ + ∣ X ∣ n 2 log ⁡ ∣ G ∣ ) O(n^2\log^3|G|+|X|n^2\log|G|) O(n2log3G+Xn2logG)。如果不显式计算 transversal 而用 Schreier 向量存储,那每次得到 u β u_\beta uβ 的最坏时间复杂度就是 O ( n 2 ) O(n^2) O(n2)(此时置换复合的时间复杂度就不是主要矛盾了),这一操作需要进行 O ( log ⁡ ∣ G ∣ ) O(\log|G|) O(logG) 次,该部分的时间开销就是 O ( n 3 log ⁡ 3 ∣ G ∣ + ∣ X ∣ n 3 log ⁡ ∣ G ∣ ) O(n^3\log^3|G|+|X|n^3\log|G|) O(n3log3G+Xn3logG)

至此可以断定:

如果显式存储 transversal,算法的时间复杂度为 O ( n 2 log ⁡ 3 ∣ G ∣ + ∣ X ∣ n 2 log ⁡ ∣ G ∣ ) O(n^2\log^3|G|+|X|n^2\log|G|) O(n2log3G+Xn2logG);如果用 Schreier 向量间接存储 transversal,算法的时间复杂度为 O ( n 3 log ⁡ 3 ∣ G ∣ + ∣ X ∣ n 3 log ⁡ ∣ G ∣ ) O(n^3\log^3|G|+|X|n^3\log|G|) O(n3log3G+Xn3logG)

现在看空间复杂度,显然空间复杂度的主要部分是 S k S_k Sk,所有 S k S_k Sk 的元素总数是 O ( log ⁡ 2 ∣ G ∣ ) O(\log^2|G|) O(log2G)(这里没有考虑初始的 ∣ X ∣ |X| X 个生成元占的空间,只考虑额外空间),因此空间复杂度为 O ( n log ⁡ 2 ∣ G ∣ ) O(n\log^2|G|) O(nlog2G)。如果显式存储 transversal,则要算上存储 transversal 耗费的空间,对每个 S k S_k Sk 和每个 β ∈ Δ k \beta\in\Delta_k βΔk 都要存储置换 u β u_\beta uβ,从而这一部分的空间复杂度是 O ( n 2 log ⁡ ∣ G ∣ ) O(n^2\log|G|) O(n2logG)。这样就得到了空间复杂度的结论:

如果显式存储 transversal,算法的空间复杂度为 O ( n 2 log ⁡ ∣ G ∣ + n log ⁡ 2 ∣ G ∣ + ∣ X ∣ n ) O(n^2\log|G|+n\log^2|G|+|X|n) O(n2logG+nlog2G+Xn);如果用 Schreier 向量间接存储 transversal,算法的空间复杂度为 O ( n log ⁡ 2 ∣ G ∣ + ∣ X ∣ n ) O(n\log^2|G|+|X|n) O(nlog2G+Xn)

事实上第一种情况的空间复杂度可以确定为 O ( n 2 log ⁡ ∣ G ∣ + ∣ X ∣ n ) O(n^2\log|G|+|X|n) O(n2logG+Xn),因为可以证明对称群 S n S_n Sn 的真子群链长度至多为 O ( n ) O(n) O(n)——这说明每个 S k S_k Sk 的元素个数都是 O ( n ) O(n) O(n),从而所有 S k S_k Sk 的元素总数是 O ( n log ⁡ ∣ G ∣ ) O(n\log|G|) O(nlogG),即 S k S_k Sk 所占空间也是 O ( n 2 log ⁡ ∣ G ∣ ) O(n^2\log|G|) O(n2logG)。现在重新叙述一遍空间复杂度的结论。

如果显式存储 transversal,算法的空间复杂度为 O ( n 2 log ⁡ ∣ G ∣ + ∣ X ∣ n ) O(n^2\log|G|+|X|n) O(n2logG+Xn);如果用 Schreier 向量间接存储 transversal,算法的空间复杂度为 O ( n log ⁡ 2 ∣ G ∣ + ∣ X ∣ n ) O(n\log^2|G|+|X|n) O(nlog2G+Xn)

总结

介绍完 Schreier-Sims 算法,本文也就该结束了,但是 Schreier-Sims 算法的相关内容远不止这些,可以说这里水很深。

例如通过 Schreier 向量计算 u β u_\beta uβ 的最坏时间复杂度是 O ( n 2 ) O(n^2) O(n2),可能读者会觉得这个时间上限太粗糙了,通常情况下的时间复杂度要比这好得多。但按照本文介绍的算法,已经算过的 Schreier 树不会再更新,偏偏我们在初始化 S k ( k > 1 ) S_k(k>1) Sk(k>1) 时只添了一个元素,这时构建的树的深度很容易过高,而它一旦定形就不会再更改了!一种改进方法是每次更新 S k S_k Sk 时都重新算一遍 Schreier 树,但这种操作需要小心行事,如果原先的 Schreier 树不深,再算一遍就是浪费时间了。

对第一种算法(显式存储 Schreier 树),时间复杂度是 O ( n 5 ) O(n^5) O(n5)(此处需要利用对称群 S n S_n Sn 的真子群链长度至多为 O ( n ) O(n) O(n) 的结论),Knuth 在 1991 年给出了一组数据 X X X,使得算法的时间复杂度达到 Θ ( n 5 ) \Theta(n^5) Θ(n5)。如果 X X X 是随机选取的,通常认为期望时间复杂度是 Θ ( n 4 ) \Theta(n^4) Θ(n4)

对 Schreier-Sims 算法稍作改进,可以消除时间复杂度第二项的一个 n n n 因子。Schreier-Sims 算法还有一种运用 Monte Carlo 方法的改进,可以做到时间复杂度 O ( n log ⁡ n log ⁡ 4 ∣ G ∣ + ∣ X ∣ n log ⁡ ∣ G ∣ ) O(n\log n\log^4|G|+|X|n\log|G|) O(nlognlog4G+XnlogG),空间复杂度 O ( n log ⁡ ∣ G ∣ + ∣ X ∣ n ) O(n\log|G|+|X|n) O(nlogG+Xn)。这些就只能在下一篇文章里再讲了(如果有的话)。

参考文献

[1] Seress, A. Permutation Group Algorithms, Cambridge U Press, 2002.
[2] Derek F. Holt, Bettina Eick, Eamonn A. O’Brien, “Handbook of computational group theory”, Discrete Mathematics and its Applications (Boca Raton). Chapman & Hall/CRC, Boca Raton, Florida, 2005. ISBN 1-58488-372-3
[3] Cameron, P. J., Solomon, R., and Turull, A. (1989). Chains of
subgroups in symmetric groups. J. Algebra, 127:340–352.
[4] Knuth, D. E. (1991). Notes on efficient representation of perm groups.
Combinatorica, 11:57–68.

你可能感兴趣的:(计算群论基础算法:Schreier-Sims 算法)