【问题标题】:Data Structure and Algorithm for Circular Graph圆形图的数据结构和算法
【发布时间】:2012-06-08 02:18:50
【问题描述】:

我需要为 Web 客户端的 Circular Data Graph 定义 Data StructureAlgorithm
在服务器端,数据将以 2 列 CSV 格式(例如发送方、接收方)提供。
最终输出将以JSON 格式呈现并发送到网络请求。
我见过一些Tree 的例子,它们可以帮助建立父子关系。但就我而言,我有一个递归关系i.e. A Parent's grand child can also be used as a Parent;当我陷入无限循环时,这让生活有点困难。

数据:

Sender,Receiver
A,B
A,H
B,C
B,D
D,E
E,F
F,G
G,C
H,I
H,J
J,K
K,L
L,M
M,K
L,N
N,O
N,P
P,A
N,Q

客户端可能会这样渲染(我只关心 Java 结构):
客户端可以请求任何节点,我必须生成整个树并发送响应,即 A、K 或 N。

问题:

  1. 什么是最适合此要求的Data Structure?为了 例如Tree 喜欢还是其他?
  2. 我是否应该编写自己的逻辑来阅读 数据并设置在Tree 或者是否有任何标准算法 那里?
  3. 避免递归的最佳方法是什么?

任何工作示例都会在这里真正有所帮助:)

另请参阅下面的我的工作解决方案。

【问题讨论】:

  • 我不确定这里的问题是什么......
  • Oli 我正在寻找一个 Java 程序来生成这个输出。对你们中的一些人来说这听起来很简单,但对我来说真的很重要。
  • 您发现的树示例中是否有任何内容可以阻止树中多个位置的存在?
  • 奥利,我已经修改了问题,所以请重新考虑您的评论,因为它可能会给读者留下不好的印象。

标签: java algorithm tree


【解决方案1】:

我确信您已经找到的树示例在如何实现树状结构方面是正确的。在您的情况下,您有一个额外的复杂性,即递归循环可能存在,因为某些子级将是对其祖先之一的精确对象引用。 (对吧?)

由于这种复杂性,任何尝试通过迭代每个节点的子节点来遍历树的进程都会循环这些递归连接,直到发生堆栈溢出。

在这种情况下,您不再是真正处理一棵树。在数学上,一棵树被定义为Graph without cycles。在你的情况下,你有cycles,因此不是一棵树,而是一个circular graph

我过去曾处理过这种情况,我认为您可以通过两种方式处理这种情况。

  1. 打破循环(在对象级别),返回树。在这些递归连接之一发生的地方,不要放置对祖先的真实对象引用,而是放置一个存根,指示它连接到哪个对象,而不是对该项目的 对象引用。

  2. 接受您有一个圆形图,并确保您的代码在遍历该图时可以处理这个问题。确保与您的图形交互的任何客户端代码都可以检测到它何时位于递归分支中并适当地处理它。

恕我直言,选项 2 不是很有吸引力,因为您可能会发现很难保证约束并且经常导致错误。只要您可以为树中的每个项目分配一个唯一标识符,选项 1 就可以很好地工作,尽管客户端仍然需要意识到这种可能性的发生,以便他们可以处理解耦链接并正确表示它(例如在树中查看用户界面)。您仍然想要为圆形图建模,但将使用树在对象级别表示它,因为它简化了代码(和表示)。

选项 1 的完整示例:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


public class CyclicGraphTest 
{   
    public static void main(String[] args) 
    {       
        new CyclicGraphTest().test();           
    }

    public void test()
    {
        NodeManager manager = new NodeManager();
        Node root = manager.processNode("ZZZ");
        root.add(manager.processNode("AAA"));
        manager.get("AAA").add(manager.processNode("BBB"));
        manager.get("AAA").add(manager.processNode("CCC"));
        manager.get("AAA").add(manager.processNode("DDD")); 
        manager.get("DDD").add(manager.processNode("EEE"));
        manager.get("EEE").add(manager.processNode("FFF"));
        manager.get("FFF").add(manager.processNode("AAA"));
        manager.get("AAA").add(manager.processNode("JJJ")); 
        root.add(manager.processNode("EEE"));
        GraphWalker walker = new GraphWalker(manager, root, 1);
        System.out.println(walker.printGraph());        
    }

    /**
     * Basic Node
     */
    public class Node implements Iterable<Node>
    {
        private String id;
        private List<Node> children = new ArrayList<Node>();

        public Node(String id) {            
            this.id = id;
        }

        public boolean add(Node e) {
            return children.add(e);
        }

        public String getId() {
            return id;
        }

        @Override
        public Iterator<Node> iterator() {          
            return children.iterator();
        }           
    }

    /**
     * Cyclical Reference
     * 
     */
    public class ReferenceNode extends Node
    {
        private String refId;

        public ReferenceNode(String id, String refId) {
            super(id);
            this.refId = refId;
        }

        @Override
        public boolean add(Node e) {
            throw new UnsupportedOperationException("Cannot add children to a reference");
        }

        public String getRefId() {
            return refId;
        }           
    }   

    /**
     * Keeps track of all our nodes. Handles creating reference nodes for existing
     * nodes.
     */
    public class NodeManager
    {
        private Map<String, Node> map = new HashMap<String, Node>();

        public Node get(String key) {
            return map.get(key);
        }

        public Node processNode(String id)
        {
            Node node = null;
            if(map.containsKey(id))
            {
                node = new ReferenceNode(getRefId(id), id);
                map.put(node.getId(), node);                
            }
            else
            {
                node = new Node(id);
                map.put(id, node);
            }
            return node;
        }

        private String getRefId(String id) {
            int i = 0;
            String refId = null;
            while(map.containsKey(refId = id + "###" + i)) { i++; }
            return refId;
        }

        public Node resolve(ReferenceNode node) {
            return map.get(node.getRefId());
        }
    }

    /**
     * Walks a tree representing a cyclical graph down to a specified level of recursion
     */
    public class GraphWalker
    {
        private NodeManager manager;
        private Node root;
        private int maxRecursiveLevel;

        public GraphWalker(NodeManager manager, Node root, int recursiveLevel) {
            super();
            this.manager = manager;
            this.root = root;
            this.maxRecursiveLevel = recursiveLevel;
        }

        public String printGraph()
        {
            return printNode(root, 0, "   ").toString();
        }

        private StringBuilder printNode(Node node, int recursionDepth, String prefix) {
            Node resolvedNode = resolveNode(node, recursionDepth);
            if(resolvedNode != node) {
                recursionDepth ++;
                node = resolvedNode;
            }
            StringBuilder sb = new StringBuilder(node.getId());
            int i = 0;
            for(Node child : node)
            {
                if(i != 0) sb.append("\n").append(prefix);
                sb.append(" -> ").append(printNode(child, recursionDepth, prefix + "       "));             
                i++;
            }
            return sb;
        }

        /**
         * Returns a resolved reference to another node for reference nodes when the 
         * recursion depth is less than the maximum allowed
         * @param node
         * @param recursionDepth
         * @return
         */
        private Node resolveNode(Node node, int recursionDepth) 
        {           
            return (node instanceof ReferenceNode) && (recursionDepth < maxRecursiveLevel) ? 
                    manager.resolve((ReferenceNode) node) : node;
        }
    }

}

【讨论】:

  • 马尔科恩,总结得很好。是的,这是一个循环图,因此是递归问题。感谢您宝贵的时间。为您的帖子投票。
  • 正在考虑并最终敲定了一个实现... :)
  • 刚刚看到您的更新 - 我根据您最初提供的信息编写了我的示例。我认为它仍然适用。
  • Malcolm,非常感谢您抽出宝贵的时间编写此示例。我想我已经很接近解决这个问题了。如果没有您的帮助,这将很难 :) 代码中需要进行一些调整,稍后我将发布这些调整。但与此同时,我将此答案标记为已接受只是为了表示我对您的工作的礼貌。
【解决方案2】:

将您的示例存储在内存中?由于您描述的是一个图表,请看一下:Three ways to store a graph in memory, advantages and disadvantages

【讨论】:

    【解决方案3】:

    我自己的解决方案:

    public class MyTree 
    {
        static Set<String> fileDataList = new HashSet<String>();
        static
        {
            fileDataList.add("A,B");
            fileDataList.add("A,B");
            fileDataList.add("A,H");
            fileDataList.add("B,C");
            fileDataList.add("B,D");
            fileDataList.add("D,E");
            fileDataList.add("E,F");
            fileDataList.add("F,G");
            fileDataList.add("G,C");
            fileDataList.add("H,I");
            fileDataList.add("H,J");
            fileDataList.add("J,K");
            fileDataList.add("K,L");
            fileDataList.add("L,M");
            fileDataList.add("M,K");
            fileDataList.add("L,N");
            fileDataList.add("N,O");
            fileDataList.add("N,Q");
            fileDataList.add("O,P");
            fileDataList.add("P,A");
        }
    
        static Map<String, Set<String>> dataMap =new HashMap<String, Set<String>>();
        static Map<String, Set<Node>> nodeMap =new HashMap<String, Set<Node>>();
    
        public static void main(String args[]) throws IOException
        {
            //String fileName= "data.csv";
            //fileDataList = JSSTest.getFileData(fileName);        
            //String lineageFor="100";
            String lineageFor="A";
    
            //Build map
            for(String kv : fileDataList)
            {
                String arr[] = kv.split(",");
                String p = arr[0];
                String c = arr[1];
                if(dataMap.containsKey(p))
                {
                    dataMap.get(p).add(c);
                }
                else
                {
                    Set<String> list = new HashSet<String>();
                    list.add(c);
                    dataMap.put(p, list);
                }
            }
    
            //Get the lineage for Application
            Node lineage = getLineage(lineageFor);
            print(lineage, "");
        }
    
        private static void print(Node node, String space) 
        {
            System.out.println(space + node.getName());
    
            if(node.getInNode() != null && node.getInNode().size() > 0)
            {
                for(Node child:node.getInNode())
                {
                    if(child.getRecursive() == null)
                    {
                        print(child, space+".");
                    }
                    else
                    {
                        System.out.println(space + child.getName());
                        System.out.println(space  + "."+ child.getRecursive().getName());
                    }
                }
            }
        }
    
        /**
         * Get Lineage
         * @param appName
         * @return
         */
        private static Node getLineage(String appName) 
        {
            Node node = new Node(appName);
            Set<String> allInNodes = new HashSet<String>();
            setInnerNodes(node, dataMap.get(appName), allInNodes);
            return node;
        }
    
        private static void setInnerNodes(Node node, Set<String> childrenList, Set<String> allInNodes) 
        {
            if(childrenList == null) return;
    
            for(String child:childrenList)
            {
                Node containedNode = nodeContains(node, child);
                if(containedNode != null)
                {
                    node.setRecursive(new Node(child));
                    continue;
                }
    
                Node childNode = new Node(child);            
                node.addNode(childNode);
                if(dataMap.containsKey(child))
                {
                    setInnerNodes(childNode, dataMap.get(child), allInNodes);
                }
            }
        }
    
        static int nodeCounter=1;
        private static Node nodeContains(Node node, String child) 
        {
            if(node.getName().equals(child)) return node;
    
            if(node.getParent() != null)
            {
                if(node.getParent().getName().equals(child)) return node.getParent();
    
                if(node.getParent().getParent() != null && nodeContains(node.getParent().getParent(), child) != null)
                {
                    return node.getParent();
                }
            }
    
            return null;
        }
    }
    class Node
    {
        private String name;
        private Node parent;
        private Node recursive;
    
        public Node getParent() 
        {
            return parent;
        }
    
        public void setParent(Node parent) {
            this.parent = parent;
        }
    
        private Set<Node> inNode = new LinkedHashSet<Node>();
    
        public Node(String name) 
        {
            this.name=name;
        }
    
        public void addNode(Node child)
        {
            child.parent=this;
            this.inNode.add(child);
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Set<Node> getInNode() {
            return inNode;
        }
    
        public void setInNode(Set<Node> inNode) {
            this.inNode = inNode;
        }
    
        public Node getRecursive() {
            return recursive;
        }
    
        public void setRecursive(Node recursive) {
            this.recursive = recursive;
        }
    }
    

    【讨论】:

      【解决方案4】:

      使用递归并提供一个深度参数,该参数告诉每个输出元素前面有多少个空格。

      类似的东西:

      private final int INDENTSIZE = 4;
      
      public void printNodes() {
          for (Node n : roots) {
              System.out.print("- " + n.getName());
              printNode(n, 1);
          }
      }
      
      private void printNode(Node n, int depth) {
          List<Node> children = n.getChildren();
      
          for (Node child : children) {
              printIndent(depth);
              System.out.print("- " + child.getName());
              printNode(child, depth + 1);
          }
      }
      
      private void printIndent(int depth) {
          System.out.println();
          for (int i = 0; i < depth * INDENTSIZE; i++) {
              System.out.print(" ");
          }
      }
      

      当然,getChildren() 你必须自己实现。如果您有地图或树,应该不会那么难。

      但是,此示例不处理循环引用,然后将永远循环。

      【讨论】:

      • 很抱歉给您带来了困惑。我并不是要在屏幕上这样输出。我的意思是在 Java 数据结构中这样的输出。
      猜你喜欢
      • 2022-01-20
      • 1970-01-01
      • 2022-06-20
      • 2017-05-04
      • 2013-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多