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

Tracking visited nodes in a graph visitor

本文关键字:节点 访问 图形 访问者 跟踪      更新时间:2023-10-16

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

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

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

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

它有点复杂,我觉得它可能会泄漏,但我没有更好的想法。。。

想法?

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

我的意思是有两个目的-

  1. 我不想经常将相同的数据多次转储到屏幕上
  2. 我想要一种直观地指示节点在哪里只是引用现有图的另一部分的方法

因此,在遍历每个节点时设置/清除bool会破坏目的。在根节点遍历完成(即遍历的最后一步)之前,我不想清除任何布尔。当然,到那时,问题就变成了,我如何在不重新访问整个图的情况下让所有这些标志重新设置?

无论如何,我宁愿不遍历图两次(一次用于完成工作,另一次用于清除标志),也不愿每次访问节点时不断迭代列表,以确定我以前是否访问过它。这个图不大,但它是渲染子系统的一部分,遍历发生在帧之间,所以我希望它能确保快速运行。。。

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

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

图节点中的一个简单bool标志就可以了。第一次访问节点时设置它,如果已经设置,则跳过该节点。在整个遍历完成后,在单独的遍历中重置所有标志。

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

这可能是一个愚蠢的想法,我没有太多地处理图,但可能是一点数组?

你会发现图中有多少节点,你会为此分配足够的位,然后在遍历过程中,当访问节点时,会设置适当的位,这样你就知道了。

不幸的是,我能从脑海中想到几个问题。-首先,根据您执行遍历的方式,您可能会发现很难知道何时将节点标记为"未访问"(取决于您的反向跟踪方案)。-其次,它不允许您跟踪节点被访问的次数。-第三,如果图非常非常大,数组的内存可能会变得非常大,尽管除非你以某种方式将节点结构降低到每个节点一位,否则与图相比,它会很小。-第四,它没有保留访问节点的顺序,尽管这在某种程度上与第一篇文章有关。

最后,除非你有这种解决方案有效的罕见情况,否则我想你可能已经找到了一个更好的方案,比如std::vector会做得很好,你可以推到最后,但你也可以迭代整个过程。