Algorithm 修剪章鱼-移除O(N)中不属于循环一部分的有向图的所有分支

Algorithm 修剪章鱼-移除O(N)中不属于循环一部分的有向图的所有分支,algorithm,graph,Algorithm,Graph,预备阶段-可以安全跳过:这个问题与。现在,这里有一点争议,关于只分离有向图中的循环,迭代的或递归的,等等,有多琐碎或不琐碎,所以我决定把它作为一个问题来讨论——我想会有其他非CS毕业生(像我一样)谁能在未来受益于清晰且解释清楚的答案 这个问题现在可以说是“学术性的”——目的是获得尽可能多的答案: 如果一个完全连通的图遵循“每个节点恰好有一个子节点”规则(因此必须存在恰好一个循环)删除O(N)中不属于循环的所有节点该算法应最少地回答“循环的长度是多少”的问题,则节点的标识/索引是额外的。关于复杂性

预备阶段-可以安全跳过:这个问题与。现在,这里有一点争议,关于只分离有向图中的循环,迭代的或递归的,等等,有多琐碎或不琐碎,所以我决定把它作为一个问题来讨论——我想会有其他非CS毕业生(像我一样)谁能在未来受益于清晰且解释清楚的答案

这个问题现在可以说是“学术性的”——目的是获得尽可能多的答案:

如果一个完全连通的图遵循“每个节点恰好有一个子节点”规则(因此必须存在恰好一个循环)删除O(N)中不属于循环的所有节点


该算法应最少地回答“循环的长度是多少”的问题,则节点的标识/索引是额外的。关于复杂性的考虑(为什么是O(N))将受到欢迎。

如果algo能够处理非全连通图并识别所有循环(不仅是属于任何循环的节点,而且还使用循环标识符对其进行分离/标记),这将是一个额外的好处

答案越清晰,解释得越清楚,对未来的寻求答案者来说就越好 这就是为什么我要悬赏(金黄色)并放宽语言要求

下图中的示例

节点沿线路从低到高连接

如果我没有弄错的话,邻接向量(索引指定
节点
,值指定
节点。下一步
)将读取

   [ 1,  2,  3,  4,  5,  6,  7,  8,  9, 80,
     11, 12, 13, 14, 15, 16, 17, 18, 19, 81,
     21, 22, 23, 24, 25, 26, 27, 28, 29, 82,
     31, 32, 33, 34, 35, 36, 37, 38, 39, 83,
     41, 42, 43, 44, 45, 46, 47, 48, 49, 84,
     51, 52, 53, 54, 55, 56, 57, 58, 59, 85,
     61, 62, 63, 64, 65, 66, 67, 68, 69, 86,
     71, 72, 73, 74, 75, 76, 77, 78, 79, 87,
     81, 82, 83, 84, 85, 86, 87, 80 ]
其他测试数据:

  • 奇点-
    [0]
    单个节点“卷曲”到自身上(就像“弦论”中的额外维度)
  • 活动黑洞-
    [1,2,2,2]
    -指数2处的一个奇点,其中左右两侧都下降
  • 倒转套索-
    [1,0,1,2,3]
    -从套圈开始,尾巴朝向套圈

我的回答在原始问题中被接受,所以这里是我对原始问题的解决方案,作为JS片段。运行时O(n),空间O(n)

var data=[1,2,3,4,5,6,7,8,9,80,
11, 12, 13, 14, 15, 16, 17, 18, 19, 81,
21, 22, 23, 24, 25, 26, 27, 28, 29, 82,
31, 32, 33, 34, 35, 36, 37, 38, 39, 83,
41, 42, 43, 44, 45, 46, 47, 48, 49, 84,
51, 52, 53, 54, 55, 56, 57, 58, 57, 85,
61, 62, 63, 64, 65, 66, 67, 68, 69, 86,
71, 72, 73, 74, 75, 76, 77, 78, 79, 87,
81, 82, 83, 84, 85, 86, 87, 80
];
var visited=新数组(data.length);
var链=[];
对于(变量i=0;i“+访问[i].chain+”);
如果(i%10==9){
文件。填写(“”);
}
}
文件。填写(“”);

文件。写(“链长度:“+chains+”

”)如果只需要圆的计数,则可以在O(1)空间和O(n)时间内滥用邻接向量:

函数getCycleCount(邻接){
设i=0,count=0;
while(邻接[i]>=0){
设j=邻接[i];
邻接[i]=-邻接[i]-1;
i=j;
}
while(邻接[i]<0){
邻接[i]=-邻接[i]-1;
i=邻接[i];
计数++;
}
for(i=0;邻接[i]<0;i=邻接[i])
邻接[i]=-邻接[i]-1;
返回计数;
}

以下是

  • 消除不属于任何循环的顶点

  • 输出所有周期

  • 输出最大循环及其长度

  • 代码是自描述性的,并且有很多注释,人们可以简单地理解正确的输入格式(与OP提供的格式不同)

    顺便说一句,也许ideone会更早地销毁代码,所以我也把它包括在这里

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public class Node
    {
        public int id;
        public Node next;
        public int indegree = 0;
        public bool visited = false;
    }
    public class Test
    {
        public static void Main()
        {
    
            // read number of nodes of the graph
            int n = Convert.ToInt32(Console.ReadLine());
    
            Node []nodes = new Node[n];
    
            // initiate nodes
            for (int i=0;i<n;i++)
                nodes[i]=new Node{id = i};
    
            // reading input and initializing indegrees
            for (int i=0;i<n;i++)
            {
                // Each vertex has outgoing edge, so always there is a "next" neighbour
                var next = Convert.ToInt32(Console.ReadLine());
    
                nodes[i].next = nodes[next];    
                nodes[next].indegree++;
            }
    
            // Removing vertices of indegree zero recursively.
            // The code runs in O(n), in fact, it visits each edge only once, so it 
            // is O(|E|), but as every vertex has out degree one, |E|=O(|V|).
            for (int i=0;i<n;i++)
            {
                    var current = nodes[i];
    
                    while (current != null && current.indegree == 0)
                    {
                        current.next.indegree--;
                        var tmp = current.next;
                        nodes[current.id] = null;
                        current = tmp;
                    }
            }
    
            // Find All Cycles and Write Them in Console
            var cycles = new List<List<int>>();
            var count = 0;
            for (int i=0;i<n;i++)
            {
                if (nodes[i]!=null&& !nodes[i].visited)
                {
                    cycles.Add(new List<int>());
                    var current = nodes[i];
    
                    while (!current.visited)
                    {
                        cycles[count].Add(current.id);
                        nodes[current.id].visited = true;
                        current = current.next;
                    }
                    count++;
                }
            }
    
            // Print cycles and find largest cycle and its length
            if (cycles.Count > 0)
            {
                Console.WriteLine("All cycles:");
                for (int i=0;i<cycles.Count;i++)
                    Console.WriteLine(String.Join(", ", cycles[i]));
    
                int maxLength = cycles.Max(x=>x.Count);
                Console.WriteLine("Maximum cycle length is: " + maxLength);
    
                 Console.WriteLine("Largest cycle is: " +
                                    String.Join(", ",cycles.FirstOrDefault( x=>x.Count==maxLength)));
            }
            else
                Console.WriteLine("There is no cycle in the graph");
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用System.Linq;
    公共类节点
    {
    公共int id;
    公共节点下一步;
    公共int indegree=0;
    公共bool=false;
    }
    公开课考试
    {
    公共静态void Main()
    {
    //读取图形的节点数
    int n=Convert.ToInt32(Console.ReadLine());
    节点[]节点=新节点[n];
    //启动节点
    
    对于(int i=0;i如果每个循环节点都有触手,那么我们只需要找到哪些节点有两个以上的链接就可以找到循环的节点。这也适用于“非完全连通图”

    大部分代码都是用graphviz生成图形的。我还没有检查,但显然提供了一个在网络中查找循环的真正解决方案


    @Adriancolomithi示例数据可以通过添加一些节点来改进,这些节点是循环的一部分,但是没有其他节点指向它们(例如88左右的节点)。@Lucero,第二个答案没有问题,只是它只是隐式地回答了问题,你应该明确地写下我所说的实际上是正确的(或者问题中的问题是正确的),然后提供代码或证明它。之后,我可以删除我的downvote@Lucero“样本数据可以通过……[etc]进行改进。”你的意思是没有附加字符串的节点?为什么这会使问题变得更难?@Adriancolomithi,是的。根据当前数据,仅我的解决方案的第一个过程就返回正确答案,但如果添加没有附加字符串的节点,则需要第二个过程。顺便说一句,c
    # Create an octopus flavoured node map, in the format:
    # {0: [1], 1: [2], 2: [3], 3: [4], 4: [5], 5: [6], 6: [7], 7: [8], 8: [72], 9: [10], 10: [11], 11: [12], 12: [13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [73], 18: [19], 19: [20], 20: [21], 21: [22], 22: [23], 23: [24], 24: [25], 25: [26], 26: [74], 27: [28], 28: [29], 29: [30], 30: [31], 31: [32], 32: [33], 33: [34], 34: [35], 35: [75], 36: [37], 37: [38], 38: [39], 39: [40], 40: [41], 41: [42], 42: [43], 43: [44], 44: [76], 45: [46], 46: [47], 47: [48], 48: [49], 49: [50], 50: [51], 51: [52], 52: [53], 53: [77], 54: [55], 55: [56], 56: [57], 57: [58], 58: [59], 59: [60], 60: [61], 61: [62], 62: [78], 63: [64], 64: [65], 65: [66], 66: [67], 67: [68], 68: [69], 69: [70], 70: [71], 71: [79], 72: [73], 73: [74], 74: [75], 75: [76], 76: [77], 77: [78], 78: [79], 79: [72]}
    class Octopus():
        def create(self, octopus_id, complete=True):
            cycle_len = 8
            tentacle_len = 9
            node_n = 0
            output = {}
            first_node = octopus_id * ((tentacle_len * cycle_len) + cycle_len)
    
            for i in range(cycle_len):
                for j in range(tentacle_len - 1):
                    tn = (i*tentacle_len) + j
                    output[first_node+tn] = [first_node+tn+1]
    
            for k in range(cycle_len):
                cni = (k * tentacle_len) + tentacle_len - 1
                cnf = (tentacle_len * cycle_len) + k
                if cni not in output:
                    output[first_node+cni] = []
                if cnf not in output:
                    output[first_node+cnf] = []
    
                output[first_node+cni] += [first_node+cnf]
                if cnf + 1 >= (tentacle_len * cycle_len) + cycle_len:
                    if complete:
                        output[first_node+cnf] += [first_node+(tentacle_len * cycle_len)]
                else:
                    output[first_node+cnf] += [first_node+cnf+1]
    
            return output
    
    
    # The tool that performs the node triage
    class CycleFinder():
    
        nodes = {}
    
        # Distinguish nodes from different octopuses
        def find_groups(self, newcomers, cycles=[]):
            cycle_groups = []
            new_cycle = [newcomers]
            for cycle in cycles:
                linked_cycle = False
                for newcomer in newcomers:
                    if newcomer in cycle:
                        linked_cycle = True
                        new_cycle += [cycle]
                        new_cycle = [list(set([item for sublist in new_cycle for item in sublist]))]
                if not linked_cycle:
                    cycle_groups += [list(set(cycle))]
            cycle_groups += new_cycle
    
            return [list(i) for i in set(tuple(i) for i in cycle_groups)]
    
        # Given a node number, return the cycle id to which it belongs
        def get_cycle_id(self, node, cycles):
            for i,cycle in enumerate(cycles):
                if node in cycle:
                    return i
    
        # Find nodes with more than two links
        def get_cycles(self):
            serialized_data = {}
            for node,link in self.nodes.items():
                if link:
                    link = link[0]
                    if node not in serialized_data:
                        serialized_data[node] = []
                    if link not in serialized_data:
                        serialized_data[link] = []
    
                    serialized_data[node] += [link]
                    serialized_data[link] += [node]
            cycle_nodes = set()
            for node, links in serialized_data.items():
                if len(links) > 2:
                    cycle_nodes.add(node)
    
            cycles=[]
            for node, links in serialized_data.items():
                cycles = self.find_groups([node] + links, cycles)
    
            grouped_cycle_nodes = {}
            for node in cycle_nodes:
                group_id = self.get_cycle_id(node, cycles)
                if group_id not in grouped_cycle_nodes:
                    grouped_cycle_nodes[group_id] = []
                grouped_cycle_nodes[group_id] += [node]
            return grouped_cycle_nodes.values()
    
    
        # *** Generate graph ***
    
        def generate_colors(self, total_colors):
            colors = []
            for color in range(total_colors):
                h = float(color) / total_colors 
                rgb = tuple(int(i * 255) for i in self.colorsys.hsv_to_rgb(h,1,1))
                colors += ['#%02x%02x%02x' % (int(rgb[0]), int(rgb[1]), int(rgb[2]))]
            return colors
    
        # Draw pngs
        def output_images(self, image_id, cycles):
    
            A=self.pgv.AGraph()
            A.edge_attr['style']='bold'
            A.edge_attr['len']='2'
            A.node_attr['style']='filled, bold'
            A.node_attr['shape']='circle'
            A.node_attr['width']='.6'
            A.node_attr['height']='.6'
            A.node_attr['fixedsize']='true'
            A.node_attr['fontsize']='10'
            A.node_attr['fontname']='Helvetica-Bold'
    
            for node, links in self.nodes.items():
                for link in links:
                    A.add_edge(node,link)
    
            # Get the color gradient in hex
            total_colors = 3
            colors = self.generate_colors(total_colors)
    
            for color_i, group in enumerate(cycles):
                _color = colors[color_i]
                for node in group:
                    A.get_node(node).attr['fillcolor']= _color
    
            A.write('{folder_name}/tmp.dot'.format(folder_name=self.settings['image_folder_name'])) # write to simple.dot
    
            B=self.pgv.AGraph('{folder_name}/tmp.dot'.format(folder_name=self.settings['image_folder_name'])) # create a new graph from file
            B.layout('sfdp') # layout with default (neato)
            B.draw('{folder_name}/{file_name_prefix}_{file_n}.png'.format(
                    folder_name=self.settings['image_folder_name'], 
                    file_n = image_id,
                    file_name_prefix = self.settings['image_file_name_prefix'],
                )
            ) 
    
        # Create output folder for the graph image
        def prepare_output_folder(self, folder_name, file_name_prefix):
    
            if not self.os.path.exists(folder_name):
                self.os.makedirs(folder_name)
            else:
                if self.settings['delete_previously_generated_images']:
                    # Careful, this will clean up old png files with our file prefix 
                    # from the output directory we created.
                    files = self.glob.glob(folder_name + '/' + file_name_prefix + '*.png')
    
                    for f in files:
                        self.os.remove(f)
    
        # Initialize our puzzle solver
        def __init__(self, **settings):
    
            # save settings in a property
            self.settings = {}
            for key, value in settings.iteritems(): 
                self.settings[key] = value
    
            self.nodes_per_group = 3
    
            # init image creation handling, if specified in the settings
            if 'create_images' in self.settings and self.settings['create_images']:
    
                # import all modules related to generating the images
                self.os = __import__('os')
                self.glob = __import__('glob')
                self.random = __import__('random')
                self.pgv = __import__('pygraphviz')
                self.colorsys = __import__('colorsys')
    
                self.prepare_output_folder(self.settings['image_folder_name'], self.settings['image_file_name_prefix'])
    
            self.nodes = self.settings['nodes']
    
        def close(self):
            self.log_status()
            print 'Finished.'
    
    
    
    def main():
    
        # Create a node network, to start
        octopuses = Octopus().create(0, complete=False) # create an octopus with a non closed cycle
    
        for i in range(1,3):
            octopuses.update(Octopus().create(i)) # create 2 other octopuses
    
        # Create our puzzle solver
    
        ng = CycleFinder(
    
            # Create images
            create_images = True,
            # Default path where graph images will be created
            image_folder_name = 'graphs',
            # Filename prefix. Images will be postfixed with a number, eg: combination_0.png
            image_file_name_prefix = 'combination',
            # Clean up images folder before creating new batch
            delete_previously_generated_images = True,
    
            nodes = octopuses,
        )
    
        # Discriminate nodes that are part of the cycle
        cycles = ng.get_cycles()
    
        # Create an output graph
        ng.output_images(1, cycles)
    
    
    
    if __name__ == "__main__":
    
        main()