如何使用面向对象设计实现图搜索(DFS)

时间:2016-11-25 18:07:25

标签: java oop design-patterns graph

我不太确定在面向对象的世界中建模这个问题的最佳方法是什么。

假设我必须用Java表示图形及其节点,并假设我想使用深度优先搜索(DFS)遍历图形。

这两者中软件工程的最佳方法是什么:

  • 创建一个类Node,一个类Graph和一个类GraphsFunctions,其中GraphsFunctions类包含DFS方法,该方法采用{ {1}}和Graph作为参数。这样的事情:Node和电话public void DFS(Graph g, Node n)

  • 创建一个类new GraphsFunctions().DFS(new Graph(), new Node())和一个类Node,其中类Graph包含仅Graph作为参数的方法DFS 。这样的事情:Node和电话public void DFS(Node n)

我宁愿使用这两个选项中的第一个,但我不能说为什么它对我来说是最好的。你能告诉我什么是最好的选择,实际上是最好的吗?

2 个答案:

答案 0 :(得分:5)

这主要取决于两件事:

  1. 您希望在项目中使用多少封装。更多的封装通常会更好,但它可能会以过度设计问题为代价,这可能会导致您希望避免的小型项目需要做更多的工作。
  2. 你个人的风格。每个开发人员将随着时间的推移开发自己的风格,这将与其他开发人员明显区分开来。最重要的是整个项目的一致性
  3. 假设您希望(仅)良好封装,我个人会像下面这样做..

    创建一个界面GraphSearch并在其中定义您的search(Graph g, Node n)方法。现在有一个名为DFSearch和可能BFSearch的班级。 如果 - 在某个时刻 - 某个方法想要在Graph上执行搜索,您可以指定应该使用的搜索算法。

    如果您想要精彩封装,我建议您使用iterator pattern作为DFS,而BFS基本上只是迭代订单。

    首先创建一个接口GraphIterator。它只是扩展Iterator<Node>,这意味着它可以按某种顺序迭代节点。

    public interface GraphIterator extends Iterator<Node> {
    
    }
    

    根据需要创建此算法的任意数量的实现。 DFS的空结构如下所示:

    public class DFSIterator implements GraphIterator {
        private Graph g;
    
        public DFSIterator(Graph g) {
            this.g = g;
        }
    
        @Override
        public boolean hasNext() {
            // todo: implement
            return false;
        }
    
        @Override
        public Node next() {
            // todo: implement
            return null;
        }
    }
    

    接下来,您仍然可以创建界面GraphSearch ..

    public interface GraphSearch {
        Node search(Graph g, Node n);
    }
    

    一个简单的实现是IterationSearch,它只能执行给定Iterator的搜索。

    public class IterationSearch {
        public Node search(Graph g, Node n, GraphIterator iter){
            Node current = null;
            while (iter.hasNext()){
                current = iter.next();
                if (current.equals(n)){
                    return n;
                }
            }
            return null;
        }
    }
    

    它只是迭代Iterator并将每个元素与搜索到的节点进行比较。接下来创建您的班级DFSearch,这是第一个实际的GraphSearch ...

    public class DFSearch extends IterationSearch implements GraphSearch{
        @Override
        public Node search(Graph g, Node n) {
            return search(g, n, new DFSIterator(g));
        }
    }
    

    现在你的Graph可以扩展Itarable<Node>并返回一个迭代器作为默认迭代器。通过这种方式,您可以很好地迭代Graph

    public class Graph implements Iterable<Node> {
        @Override
        public Iterator<Node> iterator() {
            return new DFSIterator(this);
        }
    }
    

    并使用它如下:

    Graph g = createGraph();
    for (Node n : g) {
        // do things...
    }
    

    此解决方案的优点在于,您可以创建Iterator的{​​{1}}内部类,这允许他们访问私有成员。这通常可以显着提高效果,因为您不必将Graph视为black box

答案 1 :(得分:1)

在快速编码,易于修改或其他一些标准方面最好?除非你想出一个非常精确的“什么是好”的定义,否则不要期望找到“最好的”。

我的第一个答案是使用Java图形库。大多数人已经实施了DFS:

  • 已经实施和调试。
  • 良好的文档。
  • 没有公司通常会要我重新发明轮子。

但是,由于你的问题可能是自己动手课程的一部分,我建议:

  • Node类,可以提供对邻居的访问,并且具有equals和hashcode方法。
  • Graphs类,可以构建图形并显示它们。包含Dfs(Graph g, Node start, DfsVisitor visitor)方法。这将是一个具有全静态方法的实用程序类,与JDK的CollectionsFiles完全相同。
  • Graph类,包含节点列表(以及可能的其他内容,例如将迭代器返回到边列表的可能性)。
  • DfsVisitor,访问者界面:
  • Main类,它构建一个图形并使用访问者中有用的有效负载调用图表上的DFS。

因为DFS 本身是无用的 - 它只是一个节点访问策略。它的值在之前之后访问节点时的值。除非您允许自定义,否则将DFS算法放入GraphsFunctionsGraphs没有太大区别:它不可能在您构建到其中的任何有效负载之外重用。

 public interface DfsVisitor() {
    void started(Graph g, Node node);   // when first encountered
    void finished(Graph g, Node node);  // when all children processed
 }