Python 检查边的集合是否形成循环

Python 检查边的集合是否形成循环,python,mesh,Python,Mesh,我正在寻找最好的代码来检查一组边是否形成了一个闭环 输入数据是一个边数组,每个边=线段由其两个端点定义。例如:[[1,2],[2,3],[3,4],[4,1]]表示4条边:第一个连接点1与点2等,其中1,2,。。。每个点都有一些唯一的标签,即1可能代表点1、1、2 上面的例子是一个闭环。另一个例子可能是:[[44,52],[69,71],[8101],[52,77],[71,77],[69,8]不代表一个闭合循环,因为从点44开始,我们将连接44->52->77->71->69->8->101并

我正在寻找最好的代码来检查一组边是否形成了一个闭环

输入数据是一个边数组,每个边=线段由其两个端点定义。例如:[[1,2],[2,3],[3,4],[4,1]]表示4条边:第一个连接点1与点2等,其中1,2,。。。每个点都有一些唯一的标签,即1可能代表点1、1、2

上面的例子是一个闭环。另一个例子可能是:[[44,52],[69,71],[8101],[52,77],[71,77],[69,8]不代表一个闭合循环,因为从点44开始,我们将连接44->52->77->71->69->8->101并不是以44结束。还要注意:每条边可以任意顺序拥有两个点,边的顺序也是随机的,尽管您当然可以对它们进行排序

我正在寻找确定循环是否闭合的最佳代码。最佳代码还需要最大限度地提高效率-应用程序在网格生成中,需要为许多不同的边集确定循环/非循环,每个边集由100个数量级顶点组成

谢谢你的帮助

编辑

我的尝试:如果是一个闭合的循环,每个点应该出现两次。所以一般来说,将列表展平并检查是否有任何顶点只出现一次。但是,我不确定这是否会阻止任何其他结构出现分支?等等

flat = [x for sublist in pts for x in sublist]
single = [x for x in flat if flat.count(x) == 1]
if len(single) > 0:
   # Not a closed loop....

进行此操作的最佳方法可能是创建一个大尺寸的数组,最大边坐标大小,其中所有值最初为-1。当获得[a,b]形式的边时,将数组[a]=b

当试图找出它是否是一个循环时,从值不是-1的任何索引开始,然后跟随值


因此,从数组[a]=b,您将转到数组[b]=c。如果即将出现的任何值为-1,则它是一个开环,但如果返回到开始的位置,则它是一个闭环。

以下过程将测试是否存在循环

假设字典中的键是顶点,值是相邻顶点的列表。从[[1,2]、[2,3]、[3,4]、[4,1]]可以得到{1:[2,4]、2:[1,3]、3:[2,4]、4:[1,3]}

处理关键点列表中的每个顶点。 查找只有1个相邻顶点的第一个顶点。 从字典中删除它并查看它的相邻顶点。 如果相邻顶点具有:

1相邻顶点找到端点后,将其删除并返回到剩余顶点列表 2个相邻的顶点,这样在移除 叶,继续到其剩余的相邻顶点,将其从 词典 3个或更多相邻顶点从连接列表中删除顶点 然后返回到剩余顶点列表。 返回“剩余顶点”列表时,查找只有一个相邻顶点的第一个顶点。如果字典为空,则原始图没有圈。如果找不到具有1个相邻顶点的顶点,即所有顶点都有2个或更多相邻顶点,则原始图有圈。 如果在删除边时将其收集到列表中,还可以生成不属于循环或循环的边列表。如果子图以空列表结束,则删除的边列表也不会连接到任何循环或循环

此过程将边集划分为三个子集中的一个子集。边集是与循环断开的悬挂字符串和分支的一部分,与循环/循环相连但不属于循环/循环的一部分,其余的边集

剩余字典包含的边是一个或多个循环的一部分,或者是连接两个循环的路径的一部分。想象两个由哑铃形状的顶点路径连接的循环。末端的权重是循环,条形是循环之间的一组顶点。每个顶点将有两个或多个相邻顶点,但graph包含不属于循环的边

在具有奇数个相邻顶点的顶点之间,周期之间连接端点处的顶点更可能是具有奇数个相邻顶点的顶点,但这不是唯一或必要的属性。一个上下文中连接循环的路径可能是另一个上下文中循环的一部分。周期之间或周期中连接路径的成员资格是相对的边的非排他性,考虑这个图:

1->2->3->4->5->6->7->8->6
         4->1     7->9->10->2
循环2->3->4->5->6->7->8->9->->10->2将包含循环1->2->3->4->1和6->7->8->6之间的顶点4->5->6的路径。此外,顶点2、4、6和7将有3个相邻顶点

要生成循环之间路径中的边列表而不是任何循环中的边列表是一个更难的问题。您可以生成所有循环的列表,计算每条边所属的循环数,并删除零个循环中的边。或者使用集合而不是计数可能更有效。在每个循环中创建一组边,然后减去每个边从所有边的集合中选择其中的一个,您将保留连接中的边 两个循环/循环之间

无论哪种情况,您都需要找到所有的周期。找到循环集需要遍历图形。在查找所有周期的过程中,您可以通过访问顶点和遍历边的次数来找到答案

进一步思考: 考虑到效率,这实际上取决于你在上游处理什么。 超集、子集的属性以及子集的选择方式没有很好的定义,这些对于找到最有效的解决方案非常重要。在每个步骤中效率最高的可能不是最佳的,而且总是存在RAM/处理时间的权衡

查找连接的子集(看起来就是您的子集)需要跟踪边,因此如果您首先在顶点的超集上使用该算法,然后对每个边子集进行排序以生成连接边的两个子集,则总体效率可能会更高。一个包含循环,另一个包含字符串和树

将计数和收集放在调试中整个流程的每个步骤中,或者代码的探索性分析分支可以帮助找到一个好的解决方案。实现这一点的一种方法是使用函数visitvertex和traverseedge或类方法vertex.visit和edge.traverse,并通过关键字参数、装饰器或类的专门化注入收集、计数和删除。您还可以找到一个多进程map reduce解决方案。 图的表示法。 只要两个顶点之间只能有一条边,那么网格将映射到一个图,而不是一个多重图,这将使映射不同表示更加复杂。 图是一组顶点和它们之间的一组边。图形的三种主要表示方式是:

一组顶点和一组边,将每条边表示为 成对的顶点。如果图是连通图,则顶点集 可能隐含在边列表中。 一组顶点和与每个顶点相邻的顶点列表。 一个nxn矩阵,其中n是顶点的计数。矩阵通常是 1或true,其中顶点通过边连接;0或false,其中 事实并非如此。 每个表示都可以快速访问不同的属性

顶点和边的集合对于双向图来说是最有效的内存。您可以将边表示为两个元素集,因为顺序无关紧要,并且会使[1,2]=[2,1]自动,因为这两个元素集都是集合{1,2}

双向图的矩阵或邻接列表表示将是多余的。每条边将在两个顶点的邻接列表中表示,或表示为沿矩阵中对角线的反射。如果顶点a与顶点b相邻,则顶点b也与顶点a相邻,对于矩阵G,G[a,b]=G[b,a]。对于从网格G[a,a]=0映射的图,没有自反图边

对于遍历计数,字典键会受到一些限制。只有哈希值可以用作字典键。在大多数情况下,最好也保持密钥不变。这意味着列表、集合和字典不是有效的字典键。您可以改为使用基元类型的对应元组。也可以使用不可变集类型

您的问题可能有足够的复杂性,足以保证包含一个类似的库

可能会有更快的测试。更可能的是,如果我提出一种算法来寻找循环,那么可以使用从网格映射的图的约束构造一个更好的算法。 在一个图中搜索所有的循环并不是一件小事,还需要进一步的研究。 图论中的循环通常指自反边。这是从顶点到自身的边。图论中的许多定理都探讨了没有这些边的图的子集。因此,搜索循环而不是循环将更有帮助


寻找所有循环有很多方法。你也可以试试数学问题网站。例如,我发现这个解释与我的一些松散使用的术语有所不同

以下是一种使用NetworkX的方法:

import networkx as nx

edges = [[1,2], [2,3], [3,4], [4,1]]
g = nx.Graph()
g.add_edges_from(edges)

try:
    cycle = nx.find_cycle(g)
    cycle = set(map(frozenset, cycle))
    edges = set(map(frozenset, edges))
    if cycle == edges:
        print("The edges form a loop")
    else: print("The edges don't form a loop")
except nx.exception.NetworkXNoCycle: 
    print("No cycle found")
我们创建一个无向图并寻找一个循环。如果我们找到一条,我们会比较形成循环的边和输入边。如果匹配,输入边将形成一个循环

这些线路:

cycle = set(map(frozenset, cycle))
edges = set(map(frozenset, edges))
允许您独立于边的顺序来比较边

这个解决方案并不完美,因为nx.find\u cycle只返回它找到的第一个周期,所以可能会得到假阴性。至少,您可以确定,如果抛出异常,这些边不会形成循环

您可以在每个循环中循环,直到找到一个与输入边匹配的循环,但我不确定这对大型边集的效率有多高。有关使用NetworkX查找图形中所有周期的方法,请参见。也许把find_all_循环转换成生成器函数