45、三连通有根平面图的枚举

三连通有根平面图的枚举

1. 引言

在图论领域,枚举特定图类中的所有图是一个基础且重要的问题。对图进行编目具有多种用途,比如寻找数学猜想的反例、从候选图中挑选最优图,以及测试图算法在所有可能输入图上的平均性能。

大多数高效枚举算法的核心思想是在给定图类的所有图之间定义父子关系,从而构建一个连接该类所有图的有根树,即家族树 (F),其中 (F) 中的每个节点对应一个图。然后按照家族树 (F) 的深度优先遍历逐个生成该类中的所有图。枚举算法的时间延迟是指两个连续输出之间的时间界限。以多项式时间延迟枚举图相对容易,因为我们可以随时检查当前图的整体结构。然而,要实现最坏情况下的常数时间延迟算法是一个难题,这需要充分理解待枚举图的结构,因为不仅要求两个连续输出之间的差异为 (O(1)),而且任何用于检查对称性和识别需要修改以获得下一个输出的边/顶点的操作都必须在 (O(1)) 时间内执行。

图的枚举在多个领域有实际应用,例如推断化合物的结构、虚拟探索化学宇宙以及根据分子特征重建分子结构。已知美国国家癌症研究所(NCI)化学数据库中 94.3% 的化合物具有平面结构,因此平面图是一个值得深入研究的重要图类。相关研究团队一直在开发满足各种约束条件的化学图枚举算法,目前的目标是为更广泛的图类(如仙人掌图和外平面图)提供高效的枚举算法。

本文聚焦于 (G_3(n, g)) 类,即所有恰好有 (n) 个顶点且每个内面大小至多为 (g) 的三连通有根平面图。三连通平面图的结构比双连通平面图更为复杂。本文提出了一种算法,能在 (O(n)) 空间和最坏情况下每个图 (O(1)) 时间内枚举 (G_3(n, g)) 中的所有平面图。该算法不使用任何表示四连通分量的动态数据结构来测试待生成图的三连通性,因为要实现 (O(1)) 时间延迟,维护这样的数据结构似乎极其困难。此外,该算法还能得到一个 (O(n^3)) 时间延迟的算法,用于生成所有恰好有 (n) 个顶点且每个内面大小至多为 (g) 的三连通无根平面图。

2. 预备知识

2.1 图的基本定义

  • 除非另有说明,本文中的图均指简单无向图,用 (G = (V, E)) 表示,其中 (V) 是顶点集,(E) 是边集。
  • 对于图 (G),其顶点集和边集分别记为 (V(G)) 和 (E(G))。
  • 对于 (E’\subseteq E(G)),(G - E’) 表示从图 (G) 中移除 (E’) 中的边后得到的图;对于 (X\subseteq V(G)),(G - X) 表示从图 (G) 中移除 (X) 中的顶点以及与 (X) 中顶点关联的边后得到的图。
  • 顶点 (v) 在图 (G) 中的度记为 (deg(v; G)),顶点 (v) 的邻接点集记为 (\Gamma(v; G)),对于 (X\subseteq V),(\Gamma(X; G) = \cup_{v\in X}\Gamma(v; G) - X)。图 (G) 的顶点连通度记为 (\kappa(G))。

2.2 特殊图的定义

  • 扇图 :由至少有一个顶点的路径 (P) 加上一个新顶点 (v) 以及与路径中每个顶点关联的边得到的图称为扇图,顶点 (v) 称为扇图的中心。具有 (n) 个顶点的扇图记为 (F_n)。
  • 轮图 :由 (F_n)((n\geq4))通过连接两个度为 2 的顶点得到的图 (W_n) 称为轮图。注意,(F_n)((n\geq2))是双连通的,(W_n)((n\geq4))是三连通的。

2.3 平面图和平面嵌入

  • 平面图是指其顶点和边可以在平面上绘制为点和曲线,使得除端点外没有两条曲线相交的图。具有固定嵌入的平面图称为平面图形,其中一个面被指定为外部面,其他面称为内部面。
  • 平面图形 (G) 的面集记为 (F(G)),对于平面图形 (G) 中的面 (f),(V(f)) 和 (E(f)) 分别表示面 (f) 的边界上的顶点集和边集,面 (f) 的大小 (|f|) 定义为 (|V(f)|)。对于顶点 (v) 和边 (e),(F(v))(或 (F(v; G)))表示包含顶点 (v) 的内面集,(F(e))(或 (F(e; G)))表示包含边 (e) 的内面集。内面 (f\in F(v)) 称为 (v) - 面。

2.4 有根平面图

有根平面图是指具有指定外部边 ((u, r))(方向从 (u) 到 (r))的平面图形,其中 (r) 称为根。两个有根平面图 (G_1) 和 (G_2) 等价,如果它们的顶点集之间存在一个双射,使得 (G_1) 中的指定有向边以及边与顶点/面的关联关系对应于 (G_2) 中的相应关系。

3. 三连通图

3.1 保持三连通性的变换

在这部分,我们引入一种保持图三连通性的变换。以下引理是设计几种图变换的关键性质,通过检查常数数量的顶点的度,可以测试所得新图的三连通性。

引理 1 :设 (G) 是一个三连通图,(e = (u_1, u_2)) 是一条边,使得度为 3 的顶点 (v) 与 (G) 中的两个端点 (u_1) 和 (u_2) 都关联。如果 (G - e) 不是三连通的,那么任何大小 (|X|\leq2) 的顶点割 (X) 由 (X = \Gamma(u_1; G - e)) 或 (\Gamma(u_2; G - e)) 给出。

3.2 图的操作

  • 对于三连通图 (G) 中度数为 3 的顶点 (u),(G/u) 表示从 (G) 中移除顶点 (u) 并在 (u) 的任意两个非相邻邻接点之间添加一条新边得到的图。
  • 对于图 (G) 中长度为 3 的环 (C = (v_1, v_2, v_3)),(G/C) 表示从 (G) 中添加一个新顶点 (u) 以及三条新边 ((u, v_i))((i = 1, 2, 3))得到的图。

引理 2 :设 (G) 是一个三连通图。
- 对于 (G) 中度数为 3 的顶点 (u),图 (G/u) 仍然是简单且三连通的。
- 对于 (G) 中长度为 3 的环 (C = (v_1, v_2, v_3)),图 (G/C) 仍然是简单且三连通的。对于 (C) 中的边子集 (F),图 (G/C - F) 是三连通的当且仅当每个 (v_i) 的度数至少为 3。

对于图 (G) 中长度为 3 的环 (C),(G_3(G/C)) 表示从 (G/C) 中移除 (C) 中的边,使得每个顶点 (v_i) 的度数至少为 3 得到的所有图的集合,其中 (|G_3(G/C)|\leq2^3)。

4. 三连通有根平面图

4.1 边的可移除性

设 (G) 是一个三连通有根平面图,根 (r) 是一个外部顶点。按顺时针顺序将 (G) 的边界上的顶点记为 (u_1 = r, u_2, \cdots, u_B),并记 (e_i) 为外部边 ((u_i, u_{i + 1})),其中 (e_B = (u_B, u_1))。对于每条外部边 (e),(f(e)) 表示包含 (e) 在其边界上的唯一内面。对于 (G) 中的两个外部顶点 (u) 和 (v),(\beta[u, v]) 表示从 (u) 到 (v) 顺时针遍历 (G) 的边界得到的路径。

在三连通平面图 (G) 中,如果 (G - e) 仍然是三连通的,则边 (e) 称为可移除的,否则称为不可移除的。

引理 3 :设 (e_i = (u_i, u_{i + 1})) 是三连通平面图 (G) 中的一条外部边。如果 (W^o(f(e_i))\neq\varnothing),那么对于 (W^o(f(e_i))) 中的每个顶点 (v),(G - e_i) 有一个大小为 2 的顶点割 (X) 且 (v\in X)。此外,(e_i) 不可移除当且仅当 (W^o(f(e_i))\neq\varnothing)。

对于每条外部边 (e_i = (u_i, u_{i + 1})),(\tau_{first}(e_i))(分别地,(\tau_{last}(e_i)))是当我们从 (u_i) 顺时针遍历 (G) 的边界时,(W^o(f(e_i))) 中第一个(分别地,最后一个)出现的顶点。对于每条不可移除的外部边 (e_i = (u_i, u_{i + 1})),定义 (\beta(e_1)) 为路径 (\beta[u_{i + 1}, \tau_{first}(e_i)])。

引理 4 :设 (e_i = (u_i, u_{i + 1})) 是具有 (n\geq4) 个顶点的三连通平面图 (G) 中的一条不可移除的外部边。那么 (\beta(e_i)) 包含一条可移除的外部边或一个度数为 3 的外部顶点。

4.2 临界顶点和活跃路径

根据引理 4,三连通平面图 (G) 的边界总是包含一条可移除的外部边或一个度数为 3 的外部顶点。第一个可移除元素定义为从根开始顺时针遍历 (G) 的边界时出现的第一个这样的边或顶点。考虑 (G) 的第一个可移除元素,如果是边 (e) 或顶点 (u),分别记为 (e_p = (u_p, u_{p + 1})) 和 (u_p)。我们称顶点 (u_p) 为临界顶点,称路径 (\beta[r, u_p]) 为活跃路径。

4.3 活跃路径的结构

对于根 (r),设 (z) 表示 (r) 的第二左邻接点。包含根边 ((u_B, r)) 的 (r) - 面称为最左 (r) - 面,记为 (f_L)。注意,(|f_L| = 3) 当且仅当 ((u_B, z)\in E(G))。

(G) 的扇因子定义为外部顶点的最大序列 (u_{B + 1 - t}, u_{B + 2 - t}, \cdots, u_B),使得每个 (s_i = u_{B - t + i})((1\leq i\leq t))的度数为 3,并且 (z) 与每个 (u_{B - t + i})((0\leq i\leq t))相邻(因此 (s_i) 由两个长度为 3 的内面共享)。记 (\psi(G)) 为 (G) 的扇因子。注意,(G) 是 (W_n) 当且仅当 (|\psi(G)| = n - 2)。为了方便记法,设 (s_0 = u_B) 和 (k_0 = B)。

引理 5 :设 (G) 是一个具有 (n\geq4) 个顶点的三连通平面图,使得 (|f_L| = 3)。设 (t = |\psi(G)|),(u_p) 为临界顶点。那么对于活跃路径 (\beta[u_1, u_p]) 中的每条边 (e_i = (u_i, u_{i + 1})),满足 (u_{k_i} = \tau_{last}(e_i)) 的索引 (k_i) 满足 (k_i\in[i + 2, B]),并且有 (p + 1\leq k_{p - 1}\leq\cdots\leq k_2\leq k_1\leq k_0 = B)。此外,如果顶点 (u_p) 是第一个可移除元素,那么 (2\leq p\leq B - t - 1)。

4.4 图的操作

  • 设 (G\oplus s_{t + 1}) 表示从 (G)((t = |\psi(G)|))中引入一个新顶点 (s_{t + 1}) 以及一条新边 ((s_{t + 1}, z)),并将 ((s_t, r)) 替换为两条边 ((s_t, s_{t + 1})) 和 ((s_{t + 1}, r)) 得到的图。当从 (G) 构造 (G\oplus s_{t + 1}) 时,称 (\psi(G)) 增加 1。注意,(G\oplus s_{t + 1}) 仍然是三连通的。
  • 对于 (G) 中 (\psi(G)) 的最后一个顶点 (s_t)((t = |\psi(G)|\geq1)),设 (G\ominus s_t) 表示通过移除顶点 (s_t) 并将与 (s_t) 关联的两个外部顶点 (s_{t - 1}) 和 (r) 用一条外部边重新连接得到的图;即 (G) 是从 (G\ominus s_t) 通过在边 ((s_{t - 1}, r)) 上插入顶点 (s_t) 并连接 (s_t) 和 (z) 得到的图。当从 (G) 构造 (G\ominus s_t) 时,称 (\psi(G)) 减少 1。通过对 (G\ominus s_t = G/s_t) 应用引理 2(i),可知 (G\ominus s_t) 仍然是三连通的。

4.5 分离 (r) - 面

设 (f’) 是与最左 (r) - 面 (f_L) 相邻的 (u_B) - 面,(z) 是 (r) 的第二左邻接点。(F(r; G) - {f_L}) 中的 (r) - 面 (f) 称为分离 (r) - 面,如果 (f) 和 (f’) 共享一个除 (z) 以外的内顶点。

引理 6 :设 (G) 是一个以边 ((u_B, r = u_1)) 为根的三连通平面图,使得 (|f_L| = 3),即 (e = (u_B, z)\in E(G))。那么 (G - e) 是三连通的当且仅当 (deg(u_B; G)\geq4) 且 (G) 没有分离 (r) - 面。

相关概念总结

概念 定义
扇图 (F_n) 由至少有一个顶点的路径 (P) 加上一个新顶点 (v) 以及与路径中每个顶点关联的边得到的图,顶点 (v) 为中心
轮图 (W_n)((n\geq4)) 由 (F_n) 通过连接两个度为 2 的顶点得到的图
可移除边 在三连通平面图 (G) 中,若 (G - e) 仍为三连通,则边 (e) 为可移除边
临界顶点 从根开始顺时针遍历 (G) 的边界时,第一个可移除元素对应的顶点
活跃路径 从根到临界顶点的路径
扇因子 (\psi(G)) 外部顶点的最大序列 (u_{B + 1 - t}, u_{B + 2 - t}, \cdots, u_B),满足特定条件
分离 (r) - 面 (F(r; G) - {f_L}) 中的 (r) - 面 (f),与 (f’) 共享除 (z) 以外的内顶点

三连通有根平面图的操作流程

graph TD;
    A[开始] --> B[定义三连通有根平面图G];
    B --> C[判断边的可移除性];
    C --> D{是否有可移除边};
    D -- 是 --> E[确定临界顶点和活跃路径];
    D -- 否 --> C;
    E --> F[分析活跃路径结构];
    F --> G[进行图的操作(如G⊕st+1或G⊖st)];
    G --> H[判断是否满足三连通条件];
    H -- 是 --> I[继续后续操作];
    H -- 否 --> G;
    I --> J[结束];

5. 三连通有根平面图的父图

在枚举三连通有根平面图的过程中,为了构建家族树 (F),需要明确每个图的父图。对于一个三连通有根平面图 (G),根据前面定义的第一个可移除元素来确定其父图。

5.1 父图的确定方法

  • 可移除边情况 :如果第一个可移除元素是边 (e_p=(u_p, u_{p + 1})),则从 (G) 中移除这条边得到的图 (G’ = G - e_p) 作为 (G) 的父图。需要注意的是,要确保 (G’) 仍然是三连通有根平面图。
  • 可移除顶点情况 :若第一个可移除元素是顶点 (u_p),且 (u_p) 的度数为 3,根据引理 2(i),可以通过 (G/u_p) 的操作得到父图。即从 (G) 中移除顶点 (u_p) 并在 (u_p) 的任意两个非相邻邻接点之间添加一条新边。

5.2 父图的性质

  • 三连通性保持 :通过上述方法得到的父图 (G’) 仍然保持三连通性。这是因为移除可移除边或进行 (G/u_p) 操作时,基于前面引理的保证,不会破坏图的三连通性。
  • 根的继承 :父图 (G’) 的根与 (G) 的根保持一致,即仍然以相同的外部顶点 (r) 作为根,以保证在家族树 (F) 中根的连续性。

6. 三连通有根平面图的子图

确定了父图后,还需要了解如何从一个三连通有根平面图 (G) 生成其子图。子图的生成是枚举过程中从一个图扩展到其他图的关键步骤。

6.1 子图的生成方式

  • 添加边 :在 (G) 的基础上,选择合适的位置添加边。例如,可以在满足一定条件的两个非相邻顶点之间添加边,使得新图仍然是三连通有根平面图。添加边时要考虑边的可移除性和图的结构,避免破坏三连通性。
  • 添加顶点 :引入新的顶点并连接到图中的现有顶点。比如,对于一个长度为 3 的环 (C),可以通过 (G/C) 的操作添加一个新顶点 (u) 以及三条新边 ((u, v_i))((i = 1, 2, 3))。同时,对于 (C) 中的边子集 (F),移除这些边时要保证每个 (v_i) 的度数至少为 3,以维持图的三连通性。

6.2 子图的约束条件

  • 内面大小限制 :生成的子图必须满足每个内面的大小至多为 (g) 的条件。在添加边或顶点时,要检查新形成的内面是否符合这一要求。
  • 三连通性要求 :子图必须是三连通的。这需要在添加边或顶点后,通过检查图的结构和顶点连通度来确保。

子图生成的步骤总结

步骤 操作
1 选择添加边或顶点的方式
2 检查添加位置是否满足三连通性和内面大小限制
3 进行添加操作得到子图
4 再次检查子图的三连通性和内面大小

7. 枚举算法

7.1 算法描述

基于前面对于三连通有根平面图的父图和子图的定义,设计枚举 (G_3(n, g)) 中所有图的算法。算法的核心思想是按照家族树 (F) 的深度优先遍历顺序生成图。

算法:枚举 G3(n, g) 中的所有三连通有根平面图
输入:正整数 n ≥ 1 和 g ≥ 3
输出:G3(n, g) 中的所有三连通有根平面图

1. 初始化家族树 F,将初始图(如轮图 \(W_n\) 等满足条件的图)作为根节点。
2. 从根节点开始进行深度优先遍历:
    - 对于当前节点对应的图 G:
        - 确定 G 的第一个可移除元素,找到其父图 G'。
        - 根据 G 生成所有满足条件的子图。
        - 将子图作为新的节点添加到家族树 F 中。
        - 输出图 G 与上一个输出图的差异。
3. 重复步骤 2,直到遍历完家族树 F 的所有节点。

7.2 复杂度分析

  • 时间复杂度 :该算法在最坏情况下每个图的时间复杂度为 (O(1))。这是因为在生成每个图时,只需要进行常数时间的操作,如确定父图、生成子图和输出差异等。同时,由于不使用复杂的动态数据结构来测试三连通性,避免了额外的时间开销。
  • 空间复杂度 :算法的空间复杂度为 (O(n))。主要的空间开销在于存储家族树 (F) 中的节点和图的相关信息。由于每个图的顶点数为 (n),存储图的结构和相关属性所需的空间与 (n) 成正比。

7.3 算法优势

  • 常数时间延迟 :能够在常数时间内输出每个图与上一个输出图的差异,使得枚举过程高效。
  • 不依赖复杂数据结构 :避免了使用表示四连通分量的动态数据结构来测试三连通性,降低了算法的复杂度和维护难度。

枚举算法的流程

graph TD;
    A[开始] --> B[初始化家族树 F 和初始图];
    B --> C[进行深度优先遍历];
    C --> D[确定当前图 G 的父图 G'];
    D --> E[生成 G 的所有子图];
    E --> F[将子图添加到家族树 F];
    F --> G[输出图 G 与上一个输出图的差异];
    G --> H{是否遍历完所有节点};
    H -- 否 --> C;
    H -- 是 --> I[结束];

8. 总结

本文围绕三连通有根平面图的枚举问题展开研究,提出了一种高效的枚举算法。通过定义图的父图和子图,构建家族树 (F),并按照深度优先遍历的方式生成 (G_3(n, g)) 中的所有图。

8.1 主要成果

  • 算法设计 :给出了一个在 (O(n)) 空间和最坏情况下每个图 (O(1)) 时间内枚举 (G_3(n, g)) 中所有图的算法。该算法不依赖复杂的动态数据结构来测试三连通性,具有较高的效率。
  • 图结构分析 :对三连通有根平面图的结构进行了深入分析,包括边的可移除性、临界顶点、活跃路径、扇因子等概念的定义和性质研究,为算法的设计提供了理论基础。

8.2 应用前景

该算法在多个领域具有潜在的应用价值,如化学化合物结构的推断、虚拟化学宇宙的探索以及分子结构的重建等。由于平面图在化学领域的广泛存在,该算法可以帮助筛选和分析满足特定条件的化学图。

8.3 未来展望

虽然本文的算法已经取得了较好的效果,但仍有一些方面可以进一步研究和改进。例如,可以考虑扩展算法以处理更复杂的图类,或者优化算法的实现以提高实际运行效率。同时,探索算法在其他领域的应用也是未来的研究方向之一。

通过本文的研究,为三连通有根平面图的枚举问题提供了一种有效的解决方案,为相关领域的研究和应用提供了有力的支持。

【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较](Matlab代码实现)内容概要:本文围绕基于改进粒子群算法的无人机路径规划展开研究,重点探讨了在复杂环境中利用改进粒子群算法(PSO)实现无人机维路径规划的方法,并将其与遗传算法(GA)、标准粒子群算法等传统优化算法进行对比分析。研究内容涵盖路径规划的多目标优化、避障策略、航路点约束以及算法收敛性和寻优能力的评估,所有实验均通过Matlab代码实现,提供了完整的仿真验证流程。文章还提到了多种智能优化算法在无人机路径规划中的应用比较,突出了改进PSO在收敛速度和全局寻优方面的优势。; 适合人群:具备一定Matlab编程基础和优化算法知识的研究生、科研人员及从事无人机路径规划、智能优化算法研究的相关技术人员。; 使用场景及目标:①用于无人机在复杂地形或动态环境下的维路径规划仿真研究;②比较不同智能优化算法(如PSO、GA、蚁群算法、RRT等)在路径规划中的性能差异;③为多目标优化问题提供算法选型和改进思路。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注算法的参数设置、适应度函数设计及路径约束处理方式,同时可参考文中提到的多种算法对比思路,拓展到其他智能优化算法的研究与改进中。
标题中的"EthernetIP-master.zip"压缩文档涉及工业自动化领域的以太网通信协议EtherNet/IP。该协议由罗克韦尔自动化公司基于TCP/IP技术架构开发,已广泛应用于ControlLogix系列控制设备。该压缩包内可能封装了协议实现代码、技术文档或测试工具等核心组件。 据描述信息判断,该资源主要用于验证EtherNet/IP通信功能,可能包含测试用例、参数配置模板及故障诊断方案。标签系统通过多种拼写形式强化了协议主题标识,其中"swimo6q"字段需结合具体应用场景才能准确定义其技术含义。 从文件结构分析,该压缩包采用主分支命名规范,符合开源项目管理的基本特征。解压后预期可获取以下技术资料: 1. 项目说明文档:阐述开发目标、环境配置要求及授权条款 2. 核心算法源码:采用工业级编程语言实现的通信协议栈 3. 参数配置文件:预设网络地址、通信端口等连接参数 4. 自动化测试套件:包含协议一致性验证和性能基准测试 5. 技术参考手册:详细说明API接口规范与集成方法 6. 应用示范程序:展示设备数据交换的标准流程 7. 工程构建脚本:支持跨平台编译和部署流程 8. 法律声明文件:明确知识产权归属及使用限制 该测试平台可用于构建协议仿真环境,验证工业控制器与现场设备间的数据交互可靠性。在正式部署前开展此类测试,能够有效识别系统兼容性问题,提升工程实施质量。建议用户在解压文件后优先查阅许可协议,严格遵循技术文档的操作指引,同时需具备EtherNet/IP协议栈的基础知识以深入理解通信机制。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace ScaffoldMaterialStatistics { public class ScaffoldStatsCommands { // 定义材料类型枚举 private enum MaterialType { HorizontalBar, // 横杆 VerticalBar, // 立杆 DiagonalBrace, // 水平斜拉杆 SteelTread, // 钢踏板 SteelSection, // 型钢 WallConnector, // 连墙件 Fastener // 扣件 } // 定义材料信息结构 private struct MaterialInfo { public MaterialType Type; public string Name; public double Length; // 单位:毫米 public int Quantity; } // 预编译正则表达式以提高性能 private static readonly Regex _lengthRegex = new Regex( @"(\d+\.?\d*)\s*(mm|米|m)|^(\d+\.?\d*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase ); private static readonly Regex _diagonalBraceRegex = new Regex( @"水平斜拉杆", RegexOptions.Compiled | RegexOptions.IgnoreCase ); // 缓存常用字符串比较 private static readonly string[] FastenerKeywords = { "扣件" }; private static readonly string[] SteelTreadKeywords = { "钢踏板" }; private static readonly string[] SteelSectionKeywords = { "型钢", "工字钢", "XCG", "悬挑", "90度", "90°", "45度", "45°", "角型钢" }; private static readonly string[] HorizontalBarKeywords = { "横杆" }; private static readonly string[] VerticalBarKeywords = { "立杆" }; private static readonly string[] WallConnectorKeywords = { "连墙件" }; // 存储步骤2-6的选择结果 private static int _firstGroupPoleCount = 0; private static int _secondGroupPoleCount = 0; private static int _horizontalBarCount = 0; private static int _diagonalBraceCount = 0; private static int _floorLineCount = 0; [CommandMethod("MCS", CommandFlags.Modal)] public void ScaffoldMaterialStatistics() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; try { // 步骤1:提示用户选择对象 PromptSelectionOptions opts = new PromptSelectionOptions(); opts.MessageForAdding = "请框选平面已布置构件: "; PromptSelectionResult selRes = ed.GetSelection(opts); if (selRes.Status != PromptStatus.OK) { ed.WriteMessage("\n操作已取消。"); return; } // 步骤2:请框选第一组立杆 if (!GetSelectionCount(ed, "请框选第一组立杆", "ScaffoldPole", out _firstGroupPoleCount)) { ed.WriteMessage("\n已跳过第一组立杆选择。"); } // 步骤3:请框选第二组立杆 if (!GetSelectionCount(ed, "请框选第二组立杆", "ScaffoldPole", out _secondGroupPoleCount)) { ed.WriteMessage("\n已跳过第二组立杆选择。"); } // 步骤4:请框选立横杆 if (!GetSelectionCount(ed, "请框选立横杆", "HorizontalBar", out _horizontalBarCount)) { ed.WriteMessage("\n已跳过立横杆选择。"); } // 步骤5:请框选外架斜拉杆 if (!GetSelectionCount(ed, "请框选外架斜拉杆", "DiagonalBrace", out _diagonalBraceCount)) { ed.WriteMessage("\n已跳过外架斜拉杆选择。"); } // 步骤6:请框选或点选楼层线 if (!GetFloorLineCount(ed, out _floorLineCount)) { ed.WriteMessage("\n已跳过楼层线选择。"); } // 显示处理进度 ed.WriteMessage("\n正在处理选中的对象,请稍候..."); // 使用Stopwatch监控性能 Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<MaterialInfo> materials; using (Transaction tr = db.TransactionManager.StartTransaction()) { // 获取选择集 SelectionSet selSet = selRes.Value; // 据对象数量选择合适的处理方法 if (selSet.Count > 10000) { ed.WriteMessage("\n检测到大量对象,启用批量处理模式..."); materials = CountMaterialsBatch(selSet, tr, ed); } else { materials = CountMaterials(selSet, tr, ed); } // 检查是否有统计数据 if (materials.Count == 0) { ed.WriteMessage("\n未找到可统计的材料!"); return; } // 应用步骤2-6的乘数规则 ApplyMultiplierRules(materials); stopwatch.Stop(); ed.WriteMessage($"\n材料统计完成,耗时: {stopwatch.Elapsed.TotalSeconds:F2}秒"); // 创建统计表格 CreateStatisticsTable(doc, materials, ed); tr.Commit(); } ed.WriteMessage("\n外架材料统计完成!"); } catch (System.Exception ex) { ed.WriteMessage($"\n错误: {ex.Message}\n{ex.StackTrace}"); } } // 获取选择数量的辅助方法 private bool GetSelectionCount(Editor ed, string message, string filterType, out int count) { count = 0; PromptSelectionOptions opts = new PromptSelectionOptions(); opts.MessageForAdding = message + " (点击右键跳过): "; PromptSelectionResult selRes = ed.GetSelection(opts); if (selRes.Status != PromptStatus.OK) { return false; } count = selRes.Value.Count; return true; } // 获取楼层线数量的特殊方法 private bool GetFloorLineCount(Editor ed, out int count) { count = 0; PromptSelectionOptions opts = new PromptSelectionOptions(); opts.MessageForAdding = "请框选或点选楼层线 (点击右键跳过): "; PromptSelectionResult selRes = ed.GetSelection(opts); if (selRes.Status != PromptStatus.OK) { return false; } count = selRes.Value.Count; return true; } // 应用乘数规则 private void ApplyMultiplierRules(List<MaterialInfo> materials) { // 步骤2和步骤3:立杆数量调整 if (_firstGroupPoleCount > 0 || _secondGroupPoleCount > 0) { for (int i = 0; i < materials.Count; i++) { if (materials[i].Type == MaterialType.VerticalBar) { MaterialInfo material = materials[i]; // 计算调整系数 double multiplier = (_firstGroupPoleCount / 2.0) + (_secondGroupPoleCount / 2.0); material.Quantity = (int)(material.Quantity * multiplier); materials[i] = material; } } } // 步骤4:横杆数量调整 if (_horizontalBarCount > 0) { for (int i = 0; i < materials.Count; i++) { if (materials[i].Type == MaterialType.HorizontalBar) { MaterialInfo material = materials[i]; double multiplier = _horizontalBarCount / 3.0; material.Quantity = (int)(material.Quantity * multiplier); materials[i] = material; } } } // 步骤5:水平斜拉杆数量调整 if (_diagonalBraceCount > 0) { for (int i = 0; i < materials.Count; i++) { if (materials[i].Type == MaterialType.DiagonalBrace) { MaterialInfo material = materials[i]; material.Quantity = material.Quantity * _diagonalBraceCount; materials[i] = material; } } } // 步骤6:连墙件及扣件数量调整 if (_floorLineCount > 0) { for (int i = 0; i < materials.Count; i++) { if (materials[i].Type == MaterialType.WallConnector || materials[i].Type == MaterialType.Fastener) { MaterialInfo material = materials[i]; material.Quantity = material.Quantity * _floorLineCount; materials[i] = material; } } } } private List<MaterialInfo> CountMaterials(SelectionSet selSet, Transaction tr, Editor ed) { // 使用字典直接存储MaterialInfo结构 Dictionary<string, MaterialInfo> materialDict = new Dictionary<string, MaterialInfo>(); int unrecognizedCount = 0; int processedCount = 0; int totalCount = selSet.Count; // 使用Stopwatch控制进度更新频率 Stopwatch progressTimer = new Stopwatch(); progressTimer.Start(); // 批量处理对象,减少事务开销 ObjectId[] objectIds = selSet.GetObjectIds(); // 预处理图层和块名映射 var layerCache = new Dictionary<ObjectId, string>(); var blockNameCache = new Dictionary<ObjectId, string>(); // 预先获取所有图层和块名 foreach (ObjectId id in objectIds) { if (!id.IsValid) continue; Entity entity = tr.GetObject(id, OpenMode.ForRead) as Entity; if (entity == null) continue; layerCache[id] = entity.Layer.ToUpper(); if (entity is BlockReference blockRef) { BlockTableRecord btr = tr.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; blockNameCache[id] = btr.Name.ToUpper(); } } // 处理每个对象 foreach (ObjectId id in objectIds) { if (!id.IsValid) continue; processedCount++; // 每处理1000个对象或每2秒更新一次进度(减少频繁更新) if (processedCount % 1000 == 0 || progressTimer.ElapsedMilliseconds > 2000) { ed.WriteMessage($"\n已处理 {processedCount}/{totalCount} 个对象..."); progressTimer.Restart(); } string layerName = layerCache.ContainsKey(id) ? layerCache[id] : ""; string blockName = blockNameCache.ContainsKey(id) ? blockNameCache[id] : ""; MaterialInfo? info = null; if (!string.IsNullOrEmpty(blockName)) { // 解析块名和图层名并获取材料信息 info = ParseBlockAndLayerName(blockName, layerName); } else { // 非块对象 info = ParseLayerName(layerName); } if (info.HasValue) { MaterialInfo material = info.Value; // 应用乘数规则 if (material.Type == MaterialType.SteelTread && !material.Name.Contains("单片")) { // 非单片钢踏板数量×3 material.Quantity = 3; } else if (material.Type == MaterialType.Fastener && material.Name == "900mm扣件") { // 900mm扣件数量×2 material.Quantity = 2; } else { material.Quantity = 1; } // 创建唯一标识键 string key = $"{material.Type}_{material.Name}_{material.Length:F1}"; // 更新材料数量 if (materialDict.TryGetValue(key, out MaterialInfo existing)) { existing.Quantity += material.Quantity; materialDict[key] = existing; } else { materialDict[key] = material; } } else { unrecognizedCount++; } } // 输出未识别对象数量 if (unrecognizedCount > 0) { ed.WriteMessage($"\n警告: 有 {unrecognizedCount} 个对象未被识别"); } return materialDict.Values.ToList(); } // 批量处理方法,用于处理超大量数据 private List<MaterialInfo> CountMaterialsBatch(SelectionSet selSet, Transaction tr, Editor ed, int batchSize = 5000) { Dictionary<string, MaterialInfo> materialDict = new Dictionary<string, MaterialInfo>(); int unrecognizedCount = 0; ObjectId[] allObjectIds = selSet.GetObjectIds(); int totalCount = allObjectIds.Length; // 使用Stopwatch控制进度更新频率 Stopwatch progressTimer = new Stopwatch(); progressTimer.Start(); for (int i = 0; i < totalCount; i += batchSize) { int endIndex = Math.Min(i + batchSize, totalCount); int batchCount = endIndex - i; ed.WriteMessage($"\n正在处理批次 {i / batchSize + 1}/{(totalCount + batchSize - 1) / batchSize} ({batchCount}个对象)..."); // 处理当前批次 for (int j = i; j < endIndex; j++) { ObjectId id = allObjectIds[j]; if (!id.IsValid) continue; Entity entity = tr.GetObject(id, OpenMode.ForRead) as Entity; if (entity == null) continue; string layerName = entity.Layer.ToUpper(); string blockName = ""; if (entity is BlockReference blockRef) { BlockTableRecord btr = tr.GetObject(blockRef.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; blockName = btr.Name.ToUpper(); } MaterialInfo? info = !string.IsNullOrEmpty(blockName) ? ParseBlockAndLayerName(blockName, layerName) : ParseLayerName(layerName); if (info.HasValue) { MaterialInfo material = info.Value; // 应用乘数规则 if (material.Type == MaterialType.SteelTread && !material.Name.Contains("单片")) { // 非单片钢踏板数量×3 material.Quantity = 3; } else if (material.Type == MaterialType.Fastener && material.Name == "900mm扣件") { // 900mm扣件数量×2 material.Quantity = 2; } else { material.Quantity = 1; } // 创建唯一标识键 string key = $"{material.Type}_{material.Name}_{material.Length:F1}"; // 更新材料数量 if (materialDict.TryGetValue(key, out MaterialInfo existing)) { existing.Quantity += material.Quantity; materialDict[key] = existing; } else { materialDict[key] = material; } } else { unrecognizedCount++; } } // 每处理完一个批次,更新进度 if (progressTimer.ElapsedMilliseconds > 2000) { ed.WriteMessage($"\n已处理 {endIndex}/{totalCount} 个对象..."); progressTimer.Restart(); } } // 输出未识别对象数量 if (unrecognizedCount > 0) { ed.WriteMessage($"\n警告: 有 {unrecognizedCount} 个对象未被识别"); } return materialDict.Values.ToList(); } // 主要解析函数(块和图层) private MaterialInfo? ParseBlockAndLayerName(string blockName, string layerName) { // 1. 处理扣件 (包含900mm扣件识别) if (ContainsAny(blockName, FastenerKeywords) || ContainsAny(layerName, FastenerKeywords)) { bool is900mm = blockName.Contains("900") || layerName.Contains("900"); return new MaterialInfo { Type = MaterialType.Fastener, Name = is900mm ? "900mm扣件" : "标准扣件", Length = 0 }; } // 2. 处理钢踏板 (包含单片识别) if (ContainsAny(blockName, SteelTreadKeywords) || ContainsAny(layerName, SteelTreadKeywords)) { double length = ExtractLengthFromString(blockName); if (length <= 0) { length = ExtractLengthFromString(layerName); } bool isSingle = blockName.Contains("单片") || layerName.Contains("单片"); return new MaterialInfo { Type = MaterialType.SteelTread, Name = isSingle ? "单片钢踏板" : "钢踏板", Length = length }; } // 3. 增强型钢识别 if (IsSteelSection(blockName, layerName)) { double length = ExtractSteelLength(blockName, layerName); string name = "型钢"; if (blockName.Contains("工字钢连梁") || layerName.Contains("工字钢连梁")) name = "工字钢连梁"; else if (blockName.Contains("悬挑") || layerName.Contains("悬挑")) name = "悬挑型钢"; else if (blockName.Contains("90") || layerName.Contains("90")) name = "90度型钢"; else if (blockName.Contains("右45") || layerName.Contains("右45")) name = "右45°角型钢"; else if (blockName.Contains("左45") || layerName.Contains("左45")) name = "左45°角型钢"; return new MaterialInfo { Type = MaterialType.SteelSection, Name = name, Length = length }; } // 4. 其他材料类型 if (ContainsAny(blockName, HorizontalBarKeywords) || ContainsAny(layerName, HorizontalBarKeywords)) { double length = ExtractLengthFromString(blockName); if (length <= 0) { length = ExtractLengthFromString(layerName); } return new MaterialInfo { Type = MaterialType.HorizontalBar, Name = "横杆", Length = length }; } if (_diagonalBraceRegex.IsMatch(blockName) || _diagonalBraceRegex.IsMatch(layerName)) { double length = ExtractLengthFromString(blockName); if (length <= 0) { length = ExtractLengthFromString(layerName); } return new MaterialInfo { Type = MaterialType.DiagonalBrace, Name = "水平斜拉杆", Length = length }; } if (ContainsAny(blockName, VerticalBarKeywords) || ContainsAny(layerName, VerticalBarKeywords)) { return new MaterialInfo { Type = MaterialType.VerticalBar, Name = "立杆", Length = 0 }; } if (ContainsAny(blockName, WallConnectorKeywords) || ContainsAny(layerName, WallConnectorKeywords)) { double length = ExtractWallConnectorLength(blockName, layerName); return new MaterialInfo { Type = MaterialType.WallConnector, Name = "连墙件", Length = length }; } // 未识别 return null; } // 图层解析(独立对象) private MaterialInfo? ParseLayerName(string layerName) { // 1. 钢踏板 if (ContainsAny(layerName, SteelTreadKeywords)) { bool isSingle = layerName.Contains("单片"); return new MaterialInfo { Type = MaterialType.SteelTread, Name = isSingle ? "单片钢踏板" : "钢踏板", Length = ExtractLengthFromString(layerName) }; } // 2. 型钢 if (IsSteelSection("", layerName)) { double length = ExtractSteelLength("", layerName); string name = "型钢"; if (layerName.Contains("工字钢连梁")) name = "工字钢连梁"; else if (layerName.Contains("悬挑")) name = "悬挑型钢"; else if (layerName.Contains("90")) name = "90度型钢"; else if (layerName.Contains("右45")) name = "右45°角型钢"; else if (layerName.Contains("左45")) name = "左45°角型钢"; return new MaterialInfo { Type = MaterialType.SteelSection, Name = name, Length = length }; } // 3. 连墙件 if (ContainsAny(layerName, WallConnectorKeywords)) { return new MaterialInfo { Type = MaterialType.WallConnector, Name = "连墙件", Length = ExtractWallConnectorLength("", layerName) }; } // 4. 扣件 if (ContainsAny(layerName, FastenerKeywords)) { bool is900mm = layerName.Contains("900"); return new MaterialInfo { Type = MaterialType.Fastener, Name = is900mm ? "900mm扣件" : "标准扣件", Length = 0 }; } return null; } // 判断是否为型钢 private bool IsSteelSection(string blockName, string layerName) { string combined = $"{blockName} {layerName}".ToUpper(); return ContainsAny(combined, SteelSectionKeywords); } // 从字符串中提取长度信息 private double ExtractLengthFromString(string input) { if (string.IsNullOrEmpty(input)) return 0; var match = _lengthRegex.Match(input); if (!match.Success) return 0; // 处理带单位的数字 if (double.TryParse(match.Groups[1].Value, out double result)) { string unit = match.Groups[2].Value.ToLower(); // 使用传统的switch语句替换switch表达式 switch (unit) { case "米": case "m": return result * 1000; // 米转毫米 default: return result; } } // 处理纯数字 if (double.TryParse(match.Groups[3].Value, out result)) return result; return 0; } // 提取型钢长度(优先考虑图层名) private double ExtractSteelLength(string blockName, string layerName) { // 优先从图层名提取 double length = ExtractLengthFromString(layerName); if (length > 0) return length; // 然后从块名提取 return ExtractLengthFromString(blockName); } // 提取连墙件长度 private double ExtractWallConnectorLength(string blockName, string layerName) { // 优先从图层名提取 double length = ExtractLengthFromString(layerName); if (length > 0) return length; // 然后从块名提取 return ExtractLengthFromString(blockName); } // 创建统计表格 private void CreateStatisticsTable(Document doc, List<MaterialInfo> materials, Editor ed) { Database db = doc.Database; // 获取插入点 PromptPointResult ppr = ed.GetPoint("\n请指定统计表插入点: "); if (ppr.Status != PromptStatus.OK) { ed.WriteMessage("\n未指定插入点,取消创建表格。"); return; } Point3d insertPoint = ppr.Value; using (Transaction tr = db.TransactionManager.StartTransaction()) { // 创建表格 Table table = new Table(); table.SetDatabaseDefaults(db); table.Position = insertPoint; // 设置表格尺寸 table.NumRows = materials.Count + 3; // 标题+表头+数据行+说明行 table.NumColumns = 4; // 设置列宽 table.SetColumnWidth(0, 10); // 序号 table.SetColumnWidth(1, 25); // 材料名称 table.SetColumnWidth(2, 25); // 杆件长度 table.SetColumnWidth(3, 15); // 数量 // 设置标题行 table.Cells[0, 0].Value = "外架材料统计表"; table.Cells[0, 0].Alignment = CellAlignment.MiddleCenter; table.Cells[0, 0].TextHeight = 3.0; // 合并标题行单元格 table.MergeCells(CellRange.Create(table, 0, 0, 0, 3)); // 设置表头 string[] headers = { "序号", "材料名称", "杆件长度(mm)", "数量" }; for (int i = 0; i < headers.Length; i++) { table.Cells[1, i].Value = headers[i]; table.Cells[1, i].Alignment = CellAlignment.MiddleCenter; table.Cells[1, i].TextHeight = 2.5; } // 填充数据 int row = 2; int index = 1; foreach (var material in materials.OrderBy(m => m.Type).ThenBy(m => m.Length)) { table.Cells[row, 0].Value = index++.ToString(); // 序号 table.Cells[row, 1].Value = material.Name; // 材料名称 table.Cells[row, 2].Value = material.Length > 0 ? material.Length.ToString("F0") : ""; // 杆件长度(取整) table.Cells[row, 3].Value = material.Quantity; // 数量 // 设置数据单元格样式 for (int j = 0; j < 4; j++) { table.Cells[row, j].Alignment = CellAlignment.MiddleCenter; table.Cells[row, j].TextHeight = 2.0; } row++; } // 添加说明行 table.Cells[row, 0].Value = "说明:①本表中钢踏板除单钢踏板外其余钢踏板已乘3计算数量;本表中钢踏板按一步一设计算数量。②连墙件按楼层计算数量;扣件按每连墙件4个扣件计算数量;③本表中横杆计算数量含外侧两道护腰横杆内侧未涉及在内;本表中材料用量为预估算用量;具体实际用量按现场实际施工用量为准。"; table.Cells[row, 0].Alignment = CellAlignment.MiddleLeft; table.Cells[row, 0].TextHeight = 1.2; // 合并说明行单元格 table.MergeCells(CellRange.Create(table, row, 0, row, 3)); // 将表格添加到当前空间 BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; btr.AppendEntity(table); tr.AddNewlyCreatedDBObject(table, true); tr.Commit(); } } // 辅助方法:检查字符串是否包含任何关键字 private bool ContainsAny(string input, string[] keywords) { if (string.IsNullOrEmpty(input)) return false; foreach (string keyword in keywords) { if (input.Contains(keyword)) return true; } return false; } } } 1、立杆没有分类统计 ①步骤2和步骤3统计计算要求:按步骤1统计的立杆数量乘以步骤2框选的第一组立杆如2500mm两除以2倍加上步骤3框选的第二组立杆如2500mm两除以2倍,按规格统计到表中;举例: 材料名称 杆件长度(mm) 数量 立杆 2500 100 2、水平斜拉杆统计错误多算,举例:框选的平面布置的1800mm斜拉杆4立面框选的外架斜拉杆是9应该统计数量为361800mm斜拉杆 3、钢踏板计计算要求:按步骤1统计的钢踏板数量乘以(步骤4框选立横杆数量除以3) 4、“ScaffoldPole固定桩.dwg”统计计算要求:按步骤1统计的立杆数量,在按步骤2或步骤3框选的第一组或第二组立杆配杆自动识别立杆部是否为固定桩若是固定桩,在统计表中填写固定桩、数量;若是底拖在统计表中填写底拖、数量;(平面布置多少立杆就等于多少固定桩或底拖) 5、在步骤1后面新增加一个步骤; 步骤要求:命令栏显示:“请选择第二道轮廓线”,点击鼠标右键可忽略这一步;选择线后命令栏显示:“请框选平面已布置构件”,点击鼠标右键可忽略这一步(框选后自动识别与统计第二道轮廓线上布置的各规格的横杆数量,) 原步骤4统计计算要求修改为:按步骤1统计的横杆数量乘以(原步骤4框选立横杆数量除以3)加上(新增步骤框选统计第二道轮廓线上布置的横杆数量乘以原步骤4框选立横杆数量除以3乘以2)必须按这计算方式计算严禁更改 修改一套完整代码
09-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值