我需要为网络客户端Data Structure
定义Algorithm
和Circular Data Graph
。
在服务器上,数据将以2列CSV格式提供(例如发件人,接收者)
最终输出将以JSON
格式呈现并发送到Web请求
我看过一些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.
问题:
Data Structure
是什么?对于
示例Tree
喜欢还是其他? Tree
中设置或者是否有任何标准算法
那里? 任何有效的例子在这里都会有所帮助:)
请参阅下面的工作解决方案。
答案 0 :(得分:6)
我确信你已经发现的树例子在如何实现树状结构方面是正确的。在您的情况下,您有一个额外的复杂性,即递归循环可能存在,因为某些子项将是对其祖先之一的精确对象引用。 (右?)
由于这种复杂性,任何试图通过遍历每个节点的子节点来遍历树的进程都将围绕这些递归连接循环,直到发生堆栈溢出。
在这种情况下,您不再真正处理树。在数学上,树被定义为Graph without cycles。在您的情况下,您有cycles,因此不是树而是circular graph。
我过去曾处理过这种情况,我想你可以通过两种方式解决这个问题。
打破周期(在对象级别),返回树。在发生这些递归连接之一的情况下,不要将真实对象引用放在祖先上,而是指示它连接到哪个对象的存根,而不是对该项的对象引用。
接受您有一个圆形图表,并确保您的代码在遍历图表时可以应对此问题。确保与图形交互的任何客户端代码都可以检测它何时处于递归分支中并适当地处理它。
恕我直言选项2不是很有吸引力,因为您可能会发现难以保证约束并且它经常导致错误。只要您可以在树中为每个项目分配一个唯一标识符,选项1就可以正常工作,尽管客户端仍然需要意识到这种可能性,因此他们可以处理解耦链接并正确表示它(例如在树中)查看UI)。您仍然希望为循环图建模,但是将使用树在对象级别表示它,因为它简化了代码(和表示)。
选项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;
}
}
}
答案 1 :(得分:1)
将您的示例存储在内存中?由于您所描述的是图表,请看一下:Three ways to store a graph in memory, advantages and disadvantages
答案 2 :(得分: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;
}
}
答案 3 :(得分:-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()
。如果您有地图或树,那不应该那么难。
然而,这个例子并没有对循环引用进行处理,然后会永远循环。