有限Abel群的结构(3)

复制代码
  版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址

   https://www.cnblogs.com/Colin-Cai/p/18931900.html

  作者:窗户

  QQ/微信:6679072

  E-mail:6679072@qq.com

本节在上一节的结论基础上,通过程序对于给定阶数生成该阶下所有可能的Abel群。

有限Abel群结构定理

我们再回忆一下有限Abel群的所有可能结构:

任何一个阶数大于1的有限Abel群都可以同构为数个阶数大于1的循环群的直积Z_{a_1} \\otimes Z_{a_2} \\otimes ... \\otimes Z_{a_n},并满足各循环群的阶数从左到右为约数关系,也就是a_1\|a_2\|...\|a_n,另外a_1,a_2,...,a_n相乘等于原Abel群的阶数。

另外,

如果a_1,a_2,...,a_n都是大于1的整数,b_1,b_2,...,b_m也都是大于1的整数,

a_1\|a_2\|...\|a_n,并且b_1\|b_2\|...\|b_m

那么Z_{a_1} \\otimes Z_{a_2} \\otimes ... \\otimes Z_{a_n} \\cong Z_{b_1} \\otimes Z_{b_2} \\otimes ... \\otimes Z_{b_m}

的充要条件是

m=n \\land a_1=b_1 \\land a_2=b_2 \\land ... \\land a_n = b_n

也就是只要有一个循环群不同,两个Abel群就不同构。

比如72阶Abel群,总共有以下几个同构:

Z_{72}

Z_2 \\otimes Z_{36}

Z_3 \\otimes Z_{24}

Z_6 \\otimes Z_{12}

Z_2 \\otimes Z_2 \\otimes Z_{18}

Z_2 \\otimes Z_6 \\otimes Z_6

以下,我们用程序实现,还是继续选择Python语言。

生成器递归实现

我们首先考虑生成器,我想程序员们大多对于此问题应该有递归的直觉。递归很多时候在于问题的分解。

Python的生成器其实是有点拗口的,它从语法上并非那么自然,使得像我这样有点语法洁癖的人,总觉得有点疙瘩。

比如如下代码:

复制代码
def f(x):
    if x <= 0:
        return -x
    else:
        for i in range(x):
            yield i
for i in f(10):
    print(i)
print(f(-10))

它最后对于f(-10)的返回居然是<generator object f at 0x7f38ee923740>

Python对其解释是,一日生成器终身生成器,也就是生成器和函数是不一样的东西。好吧,唐僧取经最后都有几卷经书晒在礁石上撕不下来,世间很难完美。

生成器中,部分流是从另外一个生成器中来的话,可以

for i in generator2(args):

yield i

不可以直接

generator2(args)

因为如此,Python会当成只是执行了一个函数。比较简洁的写法是

yield from generator2(args)

以上可以用于我们生成器的递归,而我们工作的核心在于如何分解任务。

其实,DC(分治)对一个问题可能有多种分解方式,这里我们就想一种最简单明了的。

打个比方,我们来求180的所有可能。

那么,它可以是单独的

(180)

也可能是

(2 ...)

也可能是

(3 ...)

也可能是

(6 ...)

注意,如果至少有两个数,那么后面至少会有一个数,而且必须是前面数的倍数,也就是说,第一个数的平方必须是整体的约数,

2\^2\|180

3\^2\|180

6\^2\|180

当然,我们不用考虑1(这种平凡的Abel群我们可以当特例直接排除)的分解,那么,既然要考虑递归,就要更一般的考虑下去

假如我们已经把数字N分解到

(a_1,a_2...a_n,...)

当然,其中

a_1\|a_2...\|a_n

假设能分解到这步,也就是后面至少还有一项是a_n的倍数,从而

(\\prod_{i=1}\^{i\\le n}{a_i})\*a_n\|N

也就是

(a_1,a_2...a_n,\\frac{N}{\\prod_{i=1}\^{i\\le n}{a_i}})

是后面仅剩一项的情况

如果

\\exists a_{n+1}:a_n\|a_{n+1} \\land (\\prod_{i=1}\^{i\\le n}{a_i})\*a_{n+1}\^2\|N

那,我们可以继续安插进下一项,这里找到的$a_{n+1}是合适的下一项,

(a_1,a_2...a_n,a_{n+1}...)

其实也就是,如果

\\exists m \\in Z\^+ : (m\*a_n)\^2\|\\frac{N}{\\prod_{i=1}\^{i\\le n}{a_i}}

那么是可以继续安插进下一项,

(a_1,a_2...a_n,a_{n+1}...)

于是,我们先给定生成器用来生成(a_1,a_2...,a_n)开头的所有的有效分解。

按照刚才的分析,生成器gen(remained, a)可以如下编写,其中这里的remained则是刚才的\\frac{N}{\\prod_{i=1}\^{i\\le n}{a_i}}

复制代码
def gen(remained, a):
    #自身是一个合理的分解
    yield a + [remained]
    if a:
        #如果a里面有元素,那么挨个m*a[-1]遍历试除一下
        x = a[-1]
        while x * x <= remained:
            if remained % (x * x) == 0:
                yield from gen(remained // x, a + [x])
            x += a[-1]
    else:
        #如果a是空列,那么挨个大于等于1的整数遍历试除一下
        x = 2
        while x * x <= remained:
            if remained % (x * x) == 0:
                yield from gen(remained // x, a + [x])
            x += 1

然后,最终的我们要的生成器,

复制代码
gen_all_abel = lambda N:gen(N,[])

嗯,这里可以像函数一样,lambda也是支持生成器的。

因式分解

考虑上面慢在哪里,找因子是依次这样试除找过来,如果我们一早知道因式分解,应该是可以提升效率的。

因式分解就采用最简单的试除如下:

复制代码
def factor(N):
    ret = []
    n = 2
    while n * n <= N:
        if N % n == 0:
            k = 0
            while N % n == 0:
                N //= n
                k += 1
            ret.append((n, k))
        n += 1
    if N != 1:
        ret.append((N, 1))
    return ret

所用算法没啥大技巧,唯一要提的也就是和之前一样,合数一定有一个约数小于等于自己的平方根。

有了这个之后,前面的算法可以先因式分解,然后再不需要依次试除,此处省略优化后的实现,由读者自己去思考吧。

迭代器字序列实现

我们再来考虑迭代器序列的实现。

首先,我们意识到因式分解的好处,自然有很多计算基于因式分解。我们可以建立一个类来实现因式分解的运算。

复制代码
class Factor(object):
    def __init__(self, arg=[]):
        if isinstance(arg, list): #传入为因式分解list
            self.factor_list = arg
        elif isinstance(arg, Factor): #传入为别的Factor对象,复制对象
            self.factor_list = [i for i in arg.factor_list]
        elif isinstance(arg, int): #传入是数字
            self._factor(arg)
    def _factor(self, n):
        self.factor_list = []
        x = 2
        while (x * x <= n):
            if n % x == 0:
                k = 0
                while n % x == 0:
                    n //= x
                    k += 1
                self.factor_list.append((x, k))
            x += 1
        if n != 1:
            self.factor_list.append((n, 1))

上面Factor()不带参数时,实际上等同于Factor(1)。

再加入一些运算,包括转整数、乘法、乘方、除法、n次根等,而加、减法我们并不关心,毕竟对我们问题的解决没有任何帮助。另外,我们希望这些因式分解对象使用平常的Python运算符,比如乘法的*,乘方的**,除法的/,这样可以做到运算符的多态(polymorphic),从而达到泛型(generic)。这一点在别的语言里也可以做到,比如C++我们可以为各个类型重载运算符、函数,再比如Haskell的typeclass也可以让各个类型使用相同的函数名、运算符。我们在这里使用Python,语言级别提供了自定义class对运算符的支持,那就是魔术方法(magic method),也就是那些两个下划线开头两个下划线结尾的方法,比如__add__(用来重载加法),mul(用来重载乘法)。我们把乘方和n次方根都用乘方符号(**)来表示,但浮点数一般并不能精确的反应分数,我们就采用Python自带的分数类(from fractions import Fraction)。

考虑一下开方运算,对于解题有帮助的开方运算定义如下:a开n次方意思是max\\{x\|x \\in Z\^+ \\land x\^n\|a\\}

比如722次方结果为6

在这样的定义下,我们重载__int__, mul, truediv, __power__以实现Factor对象的加法、乘法、除法、乘方

其中除法我们只考虑整除的情况,这些并不难实现,读者可以自行实现。

再者,我们自然得定义一下序列以便把所有的分解排成一个全序集。

比如,我们考虑5400的所有分解,如下:

(5400)

(2 \\quad 2700)

(3 \\quad 1800)

(6 \\quad 900)

(5 \\quad 1080)

(10 \\quad 540)

(15 \\quad 360)

(30 \\quad 180)

(2 \\quad 2 \\quad 1350)

(2 \\quad 6 \\quad 450)

(2 \\quad 10 \\quad 270)

(2 \\quad 30 \\quad 90)

(3 \\quad 3 \\quad 600)

(3 \\quad 6 \\quad 300)

(3 \\quad 15 \\quad 120)

(3 \\quad 30 \\quad 60)

(6 \\quad 6 \\quad 150)

(6 \\quad 30 \\quad 30)

考虑5400因式分解为2\^3\\times 3\^3 \\times 5\^2

它有三个质因数2,3,5

将上面的每一行里的每个数都写为2,3,5的幂乘积(没有则填0次幂),并且顺序为5,3,2这样从大到小,那么则为

({5}\^{2} \\times {3}\^{3} \\times {2}\^{3})

({5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{3} \\times {2}\^{2})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{2} \\times {3}\^{2} \\times {2}\^{3})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{2} \\times {2}\^{2})

({5}\^{1} \\times {3}\^{0} \\times {2}\^{0} \\quad {5}\^{1} \\times {3}\^{3} \\times {2}\^{3})

({5}\^{1} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{3} \\times {2}\^{2})

({5}\^{1} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{1} \\times {3}\^{2} \\times {2}\^{3})

({5}\^{1} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{2} \\times {2}\^{2})

({5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{3} \\times {2}\^{1})

({5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{2} \\times {2}\^{1})

({5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{3} \\times {2}\^{1})

({5}\^{0} \\times {3}\^{0} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{2} \\times {2}\^{1})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{2} \\times {3}\^{1} \\times {2}\^{3})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{1} \\times {2}\^{2})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{3})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{0} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{2})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{2} \\times {3}\^{1} \\times {2}\^{1})

({5}\^{0} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{1} \\quad {5}\^{1} \\times {3}\^{1} \\times {2}\^{1})

取指数,则为以下的列

2, 3, 3

0, 0, 1, 2, 3, 2

0, 1, 0, 2, 2, 3

0, 1, 1, 2, 2, 2

1, 0, 0, 1, 3, 3

1, 0, 1, 1, 3, 2

1, 1, 0, 1, 2, 3

1, 1, 1, 1, 2, 2

0, 0, 1, 0, 0, 1, 2, 3, 1

0, 0, 1, 0, 1, 1, 2, 2, 1

...

这个顺序关系一目了然,是以指数序列大小的顺序排列的。

我们考虑用这个顺序来取出一个正整数所有的正约数,让Factor加入一个next函数,来取下一个约数。

于是以下代码遍历参数n的所有正约数:

复制代码
def traverse_all_divisors(n):
    a = Factor(n)
    b = Factor()
    print(int(b))
    #循环获得排序下的下一个约数直到不再有约数
    while a.next(b):
        print(int(b))

比如,traverse_all_divisors(72)会依次打印72所有的正约数

1

2

4

8

3

6

12

24

9

18

36

72

以上来看并非数值从小到大,但是如果分解成指数的形式

1 [0, 0]

2 [0, 1]

4 [0, 2]

8 [0, 3]

3 [1, 0]

6 [1, 1]

12 [1, 2]

24 [1, 3]

9 [2, 0]

18 [2, 1]

36 [2, 2]

72 [2, 3]

上面则很容易看出其升序

Factor的next方法实现并不难,从低位往高位依次遍历,和数数一样的做法。

复制代码
    def next(self, s):
        len_self = len(self.factor_list)
        len_s = len(s.factor_list)
        #用index_self和index_s作为下标来遍历
        index_self = 0
        index_s = 0
        #依次来比较self和s
        while True:
            if index_self >= len_self:
                return False
            xa, ya = self.factor_list[index_self]
            index_self += 1
            if index_s >= len_s:
                s.factor_list = [(xa, 1)]
                return True
            xb, yb = s.factor_list[index_s]
            index_s += 1
            if xa != xb or ya != yb:
                break
        if xa != xb:
            s.factor_list = [(xa, 1), (xb, yb)] + s.factor_list[index_s:]
            return True
        #ya != yb
        s.factor_list = [(xa, yb + 1)] + s.factor_list[index_s:]
        return True

Python没有do/while结构,上面的while True与最后if-break是模拟do/while的写法。

有了以上完整的Factor实现之后,我们就可以按照上述所说的顺序来依次生成所有的分解,串成一个迭代器。

Python对迭代器的实现其实很简单:迭代器是一个class,实现__iter__方法返回self,__next__方法每次调用返回下一个值,当没有下一个值则发出StopIteration异常。

按照上述所说,以下给出了gen_abel的构造函数,iter__和__next

复制代码
class gen_abel(object):
    def __init__(self, n):
        self.whole_factor = Factor(n)
        self.next_value = [self.whole_factor]
    def __iter__(self):
        return self
    def __next__(self):
        if self.next_value:
            ret = list(map(int, self.next_value))
            self._next()
            return ret
        raise StopIteration

可以看到,保留两个属性,一个是whole_factor来表示n的因式分解,另一个next_value是下一次next所得到的结果,为了区分是否下一次next还有结果,则此处我们先约定没有下一次next结果则next_value为None。按照之前所说的顺序,长度为1的分解是排在最开始的,所以构造函数里给next_value的初值为 [self.whole_factor]。

接下去最后的任务就是实现这个_next函数了,它就是找whole_factor的下一个分解

复制代码
    def _next(self):
        length = len(self.next_value)
        #依次调整self.next_value[i:]的分解
        for i in range(length - 2, -1, -1):
            if self._try_next(length, i):
                return
        #只能分解长度加1
        length += 1
        for a, k in self.whole_factor.factor_list:
            #只需要找到第一个质数的幂不小于length的即可完成分解
            if k >= length:
                t = Factor([(a, 1)])
                t2 = self.whole_factor / Factor([(a, length - 1)])
                self.next_value = [t] * (length - 1) + [t2]
                return
        #实在没有了,则遍历完了所有分解,设置为None
        self.next_value = None

_try_next的实现

复制代码
    def _try_next(self, length, index):
        #把最后几项乘起来,尝试重新分配
        t = Factor()
        for i in self.next_value[index:]:
            t *= i
        #r1是总共还可以分配的
        r1 = Factor(t)
        if index != 0:
            r1 /= Factor(self.next_value[index - 1]) ** (length - index)
        #这个时候就是开方运算了
        r1 = r1 ** Fraction(1, length - index)
        r2 = Factor(self.next_value[index])
        if index != 0:
            r2 /= self.next_value[index - 1]
        #r2找个紧接着排序的值
        if r1.next(r2):
            ret = self.next_value[0:index]
            if index != 0:
                r2 *= self.next_value[index - 1]
            for j in range(length - index - 1):
                ret.append(r2)
            ret.append(t / r2 ** (length - index - 1))
            self.next_value = ret
            return True
        return False

Scheme实现

我钟爱的Lisp,怎么可以少得了它呢,以下链接是我写的实现代码。

Scheme实现

它用了Lisp的一种惰性计算模型------流(stream),其实和Python的生成器/迭代器没啥本质上的区别。

三个实现包含着之前的两种Python实现,以及未写的基于因式分解提速的递归生成器同样算法。

相关推荐
Blossom.1182 小时前
基于深度学习的图像分割:使用DeepLabv3实现高效分割
人工智能·python·深度学习·机器学习·分类·机器人·transformer
深海潜水员4 小时前
【Python】 切割图集的小脚本
开发语言·python
27669582924 小时前
东方航空 m端 wasm req res分析
java·python·node·wasm·东方航空·东航·东方航空m端
星月昭铭5 小时前
Spring AI调用Embedding模型返回HTTP 400:Invalid HTTP request received分析处理
人工智能·spring boot·python·spring·ai·embedding
Dreamsi_zh5 小时前
Python爬虫02_Requests实战网页采集器
开发语言·爬虫·python
weixin_448617057 小时前
疏老师-python训练营-Day30模块和库的导入
开发语言·python
程序员三藏7 小时前
Web UI自动化测试之PO篇
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
旧时光巷8 小时前
【Flask 基础 ①】 | 路由、参数与模板渲染
后端·python·零基础·flask·web·模板渲染·路由系统
java1234_小锋8 小时前
【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博评论IP地图可视化分析实现
python·自然语言处理·flask
golitter.9 小时前
python的异步、并发开发
开发语言·python