2

我需要为 Web 客户端定义和使用Data Structure。 在服务器端,数据将以 2 列 CSV 格式(例如发送方、接收方)提供。 最终输出将以格式呈现并发送到 Web 请求。 我已经看到了一些可以帮助建立亲子关系的例子。但就我而言,我有一个递归关系;当我陷入无限循环时,这让生活变得有点困难。AlgorithmCircular Data Graph

JSON
Treei.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. 避免递归的最佳方法是什么?

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

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

4

4 回答 4

6

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

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

在这种情况下,您不再是真正处理一棵树。在数学上,树被定义为没有环的图。在您的情况下,您有循环,因此不是树,而是圆形图

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

  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;
        }
    }

}
于 2012-06-03T22:04:39.110 回答
1

将您的示例存储在内存中?既然你描述的是一个Graph,那就看看这个:在内存中存储一​​个graph的三种方式,优点和缺点

于 2012-06-03T21:43:17.507 回答
0

我自己的解决方案:

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;
    }
}
于 2012-06-08T16:42:12.637 回答
-1

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

类似于以下内容:

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()你必须自己实现。如果您有地图或树,应该不会那么难。

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

于 2012-06-03T21:21:04.643 回答