n阶无向简单图的不同构个数——Burnside定理及代码实现

信息安全数学基础的期末报告,在这里简单写一部分,要具体看证明过程不要看这个,在这里只写了一部分算法和实现

算法

  1. 计算 n n n元对称群 S n S_n Sn的阶,即为 ∣ S n ∣ = n ! |S_n|=n! Sn=n!

  2. 进行和式分解,如
    4 = 1 + 1 + 1 + 1 = 2 + 1 + 1 = 2 + 2 = 3 + 1 \begin{aligned} 4&=1+1+1+1\\ &=2+1+1\\ &=2+2\\ &=3+1 \end{aligned} 4=1+1+1+1=2+1+1=2+2=3+1

  3. 计算每种置换类型的个数
    n n n次对称群 S n S_n Sn,有
    ϕ ( g ) = n ! 1 λ 1 ⋅ λ 1 ! ⋅ 2 λ 2 ⋅ λ 2 ! ⋯ n λ n ⋅ λ n ! \phi(g) = \frac{n!}{1^{\lambda_1} \cdot \lambda_1! \cdot 2^{\lambda_2} \cdot \lambda_2! \cdots n^{\lambda_n} \cdot \lambda_n!} ϕ(g)=1λ1λ1!2λ2λ2!nλnλn!n!
    其中, ϕ ( g ) = ∣ ( 1 ) λ 1 ( 2 ) λ 2 ⋯ ( n ) λ n ∣ \phi(g) = |(1)^{\lambda_1} (2)^{\lambda_2} \cdots (n)^{\lambda_n}| ϕ(g)=(1)λ1(2)λ2(n)λn

  4. 利用Burnside公式求解
    N = 1 n ! ∑ g ∈ S n n ! 1 λ 1 ⋅ λ 1 ! ⋅ 2 λ 2 ⋅ λ 2 ! ⋯ n λ n ⋅ λ n ! ⋅ 2 N g N = \frac{1}{n!}\sum_{g \in S_n} {\frac{n!}{1^{\lambda_1} \cdot \lambda_1! \cdot 2^{\lambda_2} \cdot \lambda_2! \cdots n^{\lambda_n} \cdot \lambda_n!} \cdot 2^{N_g}} N=n!1gSn1λ1λ1!2λ2λ2!nλnλn!n!2Ng
    其中 N g = 1 o ( g ) ∑ k = 1 o ( g ) ( C λ k 1 2 + λ k 2 ) N_g = \frac{1}{o(g)} \sum^{o(g)}_{k=1}{(C^2_{\lambda_{k1}} + \lambda_{k_2})} Ng=o(g)1k=1o(g)(Cλk12+λk2)

g k g^k gk的类型为 1 k 1 2 k 2 ⋯ n k n 1^{k_1}2^{k_2} \cdots n^{k_n} 1k12k2nkn o ( g ) o(g) o(g) g g g的阶

注:若 λ k 1 < 2 \lambda_{k1} < 2 λk1<2 C λ k 1 2 {C^2_{\lambda_{k1}}} Cλk12 0 0 0

python实现

import math

def combinations(n, m): # 计算组合数C(n, m)
    if 0 <= n < 2:
        return 0
    else:
        return math.factorial(n)//(math.factorial(n-m)*math.factorial(m))

# 将整数分解为多个简单数之和
result_list = [] # 记录所有分解结果
# 递归,相当于一个减法,减到结果为0或比0小时停止
def partition(n, start=1, result=[]):   # n为要分解的数,start自增,以达到最大值
    for i in range(start, n + 1):
        if i == n:  # 当最后一个被减数与n相同时,result中的元素和加上i刚好等于n
            result += [i]
            result_list.append(result)  # 将结果元素加入分解结果列表,以便后续统计
            return
        elif i > n:
            return
        partition(n - i, start=i, result=result + [i]) # 递归迭代求解

# =========================================================================
# # 递归,对所得到的所有分解式进行分析,得到置换分解的角标表示(m)^a(n)^b
# # 列表中的数两个为一组,记录置换分解,如列表为[1,3,2,1]
# # 实际意义为(1)^3(2)^1,表示该置换由三个1阶置换和一个2阶置换生成
# 对该递归的举例:如对于四阶置换群,4=1+1+2
# 第一层递归:start=0,result[start]=1,result.count(result[start])=2,意为该分解式中有2个1
# 第二层递归:该层start变为上层start值加上分解式中1的个数,start=0+2=2,那么此时result[start]为2,
# result.count(result[start])为1,意为该分解式中有1个2,此时已遍历到最后,跳出递归
# 结果:analysis列表为[1, 2, 2, 1],两两一组,则4阶置换可分解为(1)^2(2)^1,即两个1阶置换和一个2阶置换
# =========================================================================
analysis_list = []
def analyze_partition(result, start=0, analysis=[]):
    # print(*result)****************************
    # 依次填入result[start]和result[start]的个数
    analysis = analysis + [result[start]] + [result.count(result[start])]
    # 当遍历到最后一种变量时,跳出递归
    if start + result.count(result[start]) == len(result):
        analysis_list.append(analysis)
        return
    # 当前分析的项不是分解式的最后一种
    elif start + result.count(result[start]) > len(result):
        return
    analyze_partition(result, \
        start = start + result.count(result[start]), analysis = analysis)

def count_permutation_group(order, analysis_list): # 返回列表,记录每种分解的置换在Sn中的个数
    # 公式为φ(g) = n! / Π (m^λm)·λm!
    # n为Sn中的n,λm为置换分解后(m)的指数,如(1)^3(2)^1中λ1=1
    phi = [] # phi即为希腊字母φ,存储每种置换分解的个数,下标与analysis_list对应
    for analysis in analysis_list:
        denominator = 1
        for i in range(0, len(analysis), 2):
            denominator *= math.pow(analysis[i], analysis[i+1]) \
                * math.factorial(analysis[i+1])
        phi.append(int(order // denominator)) # order即为n!
    return phi

def generate_higher_order(analysis): # 返回列表,记录analysis分解的高阶复合类型
    higher_order_list = []
    # 若σ = (i)^l,则σ^k的置换类型为(i/d)^dl,其中d=gcd(i, k)
    higher_order = analysis[:] # 先对analysis进行复制,防止二者指向同一对象
    higher_order_list.append(analysis)
    order = 1
    for i in range(0, len(higher_order), 2):
        order *= higher_order[i] # order为analysis所代表置换群的阶数
    # 注意,以下的循环中所用的k均为真实值,因此从2开始计数
    for k in range(2, order + 1): # k为幂数,为真实值
        for i in range(0, len(analysis), 2):
            d = math.gcd(analysis[i], k)
            higher_order[i] = analysis[i] // d
            higher_order[i+1] = analysis[i+1] * d
        tmp_list = higher_order[:] # 临时队列,防止最终结果中列表都是同一对象
        higher_order_list.append(tmp_list)
    return higher_order_list

def count_orbit(higher_order_list): # 计算每种置换及其高阶置换生成的轨道数
    order = 1 # 即为群的阶o(g)
    lambda1_list = [] # 记录各高阶生成中λ1的值
    lambda2_list = [] # 记录各高阶生成中λ2的值
    for higher_order in higher_order_list:
        lambda1 = lambda2 = 0 # λ1, λ2为高阶生成群中阶数为1和2的置换的幂
        for i in range(0, len(higher_order), 2):
            # 统计λ1, λ2
            if higher_order[i] == 1:
                lambda1 += higher_order[i+1]
            elif higher_order[i] == 2:
                lambda2 += higher_order[i+1]
        lambda1_list.append(lambda1)
        lambda2_list.append(lambda2)
    for i in range(0, len(higher_order_list[0]), 2): # 第一个元素为1阶置换
        order *= higher_order_list[0][i] # order为analysis所代表置换群的阶数
    # 计算该置换对二元置换群的轨道数
    # 公式为 [1/o(g)] * Σ [(λ1的2组合)+λ2],Σ从1到o(g)
    orbit_num = sum(combinations(lambda1_list[i], 2) + lambda2_list[i] for i in range(0, order)) // order
    return orbit_num

def count_graphs(n, phi, orbit_num_list): # 利用Burnside引理求最终的不同构图个数
# 参数为节点数目n,count_permutation_group所求φ值列表和count_orbit求得的所有轨道数列表
    graphs_num \
    = sum(phi[i] * math.pow(2, orbit_num_list[i]) for i in range(0, len(phi))) \
         // math.factorial(n) # Burnside公式
    return graphs_num

def main():
    n = int(input("\n请输入简单无向图的节点数目:")) # n即为n元对称群Sn中的n
    while n < 0:
        n = int(input("请输入大于等于0的正整数:"))

    # 第一步,计算n元对称群的阶
    print("\n第一步,计算%d元对称群的阶" % n)
    order = math.factorial(n) # 群的阶
    print("%d元对称群S%d的阶为 %d! = %d" % (n, n, n, order))

    # 第二步,将n分解为多个正整数的和,并分析分解式
    print("\n第二步,将%d分解为多个正整数的和,进而得出置换的分解类型" % n)
    partition(n) # 分解n为多个整数的和
    print("共%d种情况:" % len(result_list))
    for result in result_list: # 序列化输出结果
        print("%d=" % n + "+".join(str(x) for x in result), end="; ")

    # 分析分解式
    for result in result_list:
            analyze_partition(result)
    # 格式化输出为(m)^a(n)^b的形式,并输出相应的分解在Sn中的个数
    print("\n该置换的分解类型及各分解类型的个数为:")
    phi = count_permutation_group(order, analysis_list)
    j = 0
    for analysis in analysis_list:
        for i in range(0, len(analysis), 2):
            print('({0})^{1}'.format(analysis[i], analysis[i+1]), end="") # 同一个分解式不换行
        print("-------------", str(phi[j])) # 换行
        j += 1

    # 第三步,计算每种分解对它的二元子集作用的轨道数,分为两小步
    print("\n第三步,计算每种分解对它的二元子集{0,1}作用的轨道数")
    orbit_num_list = []
    for analysis in analysis_list:
        # 第一小步,首先计算出g, g^2, ... g^k的表达式,其中k为g=(m)^a(n)^b的阶,即|m*n|
        higher_order_list = generate_higher_order(analysis)
        # 第二小步,计算每个高阶结果的Ng
        tmp = count_orbit(higher_order_list)
        orbit_num_list.append(tmp)
    print("上述%d种分解对二元置换的轨道数为:" % len(phi))
    print(", ".join(str(x) for x in orbit_num_list))

    # 第四步,由Burnside引理计算不同构图数目
    graphs_num = count_graphs(n, phi, orbit_num_list)
    print("\n第四步,由Burnside引理计算不同构图数目")
    print("由Burnside引理,可得到置换群诱导的S%d上的等价类个数为:" % n)
    print("+".join("({0}*2^{1})".format(phi[i], orbit_num_list[i]) \
         for i in range(0, len(phi))) + (" / %d!" % n))
    print("\n所以%d阶简单无向图(不保证连通)的不同构图数目为%d\n" % (n, graphs_num))

if __name__ == '__main__':
    main()

第一次用python写程序,挺好用,特别是格式化的部分

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
这是一个组合数学问题,我们可以使用 Burnside 引理来解决。Burnside 引理是一个计数定理,可以用于计算一个群在一些操作下的不动点个数。 对于这个问题,我们可以使用置换群 $S_n$,它包含 $n!$ 个置换,每个置换都对应着集合 $A$ 上的一个等价关系。我们需要计算的是在这 $n!$ 个置换中有多少个是等价的。 对于一个置换 $\sigma \in S_n$,我们定义它的循环节数为 $\operatorname{lcm}(k_1, k_2, \ldots, k_m)$,其中 $k_1, k_2, \ldots, k_m$ 是 $\sigma$ 的所有循环的长度。例如,对于置换 $(1\ 2\ 3)(4\ 5)$,它的循环节数为 $\operatorname{lcm}(3, 2) = 6$。 根据 Burnside 引理,不同等价关系的个数等于所有置换的循环节数的平均数。也就是说,我们需要计算: $$ \frac{1}{n!} \sum_{\sigma \in S_n} \operatorname{lcm}(k_1, k_2, \ldots, k_m) $$ 其中 $k_1, k_2, \ldots, k_m$ 是 $\sigma$ 的所有循环的长度。这个式子看起来很难计算,但是我们可以使用 Polya 定理来简化它。 Polya 定理是一个计数定理,可以用于计算一个群在一些操作下的循环节数。我们在这里简单介绍一下它的用法。对于一个置换群 $G$,我们可以定义它的一个操作为一个二元组 $(g, x)$,其中 $g \in G$,$x$ 是一个元素。这个操作将 $x$ 变成 $g(x)$。我们需要计算 $G$ 在所有操作下的不动点个数之和。根据 Polya 定理,这个和等于 $$ \frac{1}{|G|} \sum_{g \in G} a(g)^{c(g)} $$ 其中 $a(g)$ 是 $g$ 操作下的不动点个数,$c(g)$ 是 $g$ 的循环节数。 对于这个问题,我们可以使用 $S_n$ 的所有置换作为操作。对于一个置换 $\sigma \in S_n$,我们将它表示成一个 $n$ 元组 $(a_1, a_2, \ldots, a_n)$,其中 $a_i$ 是 $\sigma(i)$。我们定义操作 $(\tau, (a_1, a_2, \ldots, a_n))$ 将元组 $(a_1, a_2, \ldots, a_n)$ 变成 $(\tau(a_1), \tau(a_2), \ldots, \tau(a_n))$。 我们需要计算 $S_n$ 在所有操作下的不动点个数之和。根据 Polya 定理,这个和等于 $$ \frac{1}{n!} \sum_{\sigma \in S_n} 2^{c(\sigma)} $$ 其中 $c(\sigma)$ 是 $\sigma$ 的循环节数。注意到这个式子只和 $\sigma$ 的循环节数有关,不和具体的置换有关。因此,我们可以将它改写成 $$ \frac{1}{n!} \sum_{k=1}^n p(n, k) 2^{k} $$ 其中 $p(n, k)$ 表示将 $n$ 个元素划分成 $k$ 个非空的循环的方案数。它的值可以使用 Bell 数来计算。也就是说, $$ p(n, k) = \sum_{i=0}^k {k \choose i} B_{n-i} $$ 其中 $B_n$ 是第 $n$ 个 Bell 数。它的递推式是 $$ B_{n+1} = \sum_{k=0}^n {n \choose k} B_k $$ 边界条件是 $B_1 = 1$。 综上所述,我们可以使用下面的 Python 代码来解决这个问题:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值