跟踪图访问者中的访问节点

时间:2012-02-16 16:57:29

标签: c++ graph nodes traversal visitor

我有一个图表,我使用典型的访问者模式进行遍历。我遇到了一个问题,我需要知道在当前遍历期间是否已访问过被访问的节点。

我开发了一个我认为可行的解决方案,但它需要在图遍历期间/之后创建和销毁节点“标志”。

也就是说,当访问每个节点时,将检查节点中的标志对象指针成员。如果它为NULL,则访问者将创建一个标志对象并将其分配给节点的标志对象指针。然后,访问者将对标志指针成员的引用推送到它自己的内部列表(当然是标记对象指针的指针)。否则,如果节点的标志对象指针不为NULL,则访问者将停止该节点上的遍历。

清理将在遍历完成后从访问者列表中弹出/删除标志对象,并将列表中的每个节点标志指针重新分配为NULL。

它有点牵扯并让我觉得可能容易泄漏,但我没有更好的想法......

思想?

作为附录,目的是在文本控制台中列出树的结构。但是,如果我有几个节点,它们是一个公共子图的父节点,我想只列出该子图一次,然后在其他地方使用“[Subnode1 ...]”这样的命名法来引用它。

我的意思是出于两个目的 -

  1. 我不想经常多次将相同的数据转储到屏幕上
  2. 我想要一种方法来直观地指示节点在哪里简单地引用现有图的另一部分。
  3. 因此,遍历每个节点时设置/清除bool会失败。在根节点遍历完成之前(即遍历的最后一步),我不想清除任何 bool。当然,到那时,问题就变成了,如何在不重新访问整个图形的情况下重置所有这些标志?

    无论如何,我不想两次遍历图表(一次完成工作并再次清除标记)或每次访问节点时不断迭代列表以确定我之前是否访问过它。图形并不大,但它是渲染子系统的一部分,遍历发生在帧之间,所以我希望它确保它快速运行...

3 个答案:

答案 0 :(得分:1)

单个Node类的典型访问者模式:

class Node;
class NodeVisitorInterface
{
    public:
        virtual ~NodeVisitor() {}
        virtual bool visitNode(Node& node) = 0;
};

// Note: I have made the accept() method virtual
//       But if you do derive from node you should add each derived type to 
//       the `NodeVisitorInterface` with its own specific version of visitNode.
//       
//       What makes graphs easy with the visitor pattern is that there is usually only
//       one type of node. Thus the visitor interface is trivially easy. 
class Node
{
    public:
       virtual void accept(NodeVisitorInterface& visitor)
       {
           // For the generic this node you call the visitor
           if (visitor.visitNode(*this))
           {

               // For all linked nodes you get them to accept the visitor
               // So they can call visitNode() for themselves.
               //
               foreach(LinkedNode as node)            // Note pseudo code as I don't 
               {                                      // know how you specify links
                    node.accept(visitor);
               }
           }
       }
 };

上面定义了图表访问者的通用实现 关于图形的事情是它们通常只有一个节点类型,这使得访问者界面非常容易。现在是访问者界面的一个简单实现,它确保我们不会多次处理节点。

 class VisitNodesOnce: public NodeVisitorInterface
 {
    public:
        virtual bool visitNode(Node& node)
        {
            if (visitedNodes.find(&node) != visitedNodes.end())
            {
                 // Node already handled just return.
                 return false;
            }
            // The address of a node should be a unique identifier of the node
            // Thus by keeping the address of all the visited nodes we can track
            // them and not repeat any work on these nodes.
            visitedNodes.insert(&node);

            this->doWorkOnUniqueNode(node);
            return true;
        }
        virtual void doWorkOnUniqueNode(Node& node) = 0;

    private:
        set<Node*>   visitedNodes;
 };

答案 1 :(得分:1)

图节点中应该有一个简单的bool标志。首次访问节点时设置它,或者如果已设置则跳过节点。整个遍历完成后,在单独的遍历中重置所有标志。

或者,如果由于某种原因您无法更改图节点(例如,因为并发线程可能正在遍历它),请为访问节点保留单独的setunordered_set指针。当你到达一个节点时,只需检查它是否已经存在于集合中,如果不存在则将其放在那里(如果不是则跳过它)。

答案 2 :(得分:0)

这可能是一个愚蠢的想法,我对图表没有多少工作,但也许有点阵列?

您可以找出图中有多少节点为此分配足够的位,然后在遍历期间,当访问节点时,设置相应的位并以此方式知道。

不幸的是,我可以想到它的一些问题。 - 首先,根据您执行遍历的方式,您可能会发现很难知道何时根据您的回溯跟踪方案将节点标记为“未访问”。 - 其次,它不允许您跟踪节点被访问的次数。 - 如果图形非常大,则第三个数组的内存可能会变得非常大,但除非你以某种方式将节点结构降低到每个节点一点,否则与图形相比它会很小。 - 第四,它没有保留访问节点的顺序,尽管这有点与第一篇文章有​​关。

最后,除非你有一些罕见的情况,这个解决方案有效,我猜你可能会遇到一个更好的方案,比如std :: vector会做得很好,你可以推送并弹出结束,但你也可以遍历整个事情。