【问题标题】:Using pointers and references, can't figure out why DFS is recurring infinitely使用指针和引用,无法弄清楚为什么 DFS 会无限循环
【发布时间】:2018-02-03 01:11:08
【问题描述】:

我正在尝试使用自制数据结构(HashMap 和 LinkedList)在有向图上实现一个简单的 DFS 来学习 C++,但由于某种原因,DFS 方法无限重复。

我认为它是无限循环的,因为由于某种原因,存储在哈希图(图表)中的节点实际上并未在 DFS 期间被标记为已访问。我以为我理解指针和引用,但显然我不理解。如果有人能帮我看看我做错了什么,我将不胜感激。

这是无限循环的 DFS 方法:

template <class T>
bool Graph<T>::DFS(const T& v1, const T& v2) {
    if(v1 == v2)
        return true;

    Graph<T>::Node * node = *(map->find(v1));
    node->visited = true;

    for(int i = 0; i < node->adjacent->size(); i++) 
        if(node->adjacent->get(i).visited == false) 
            return DFS(node->adjacent->get(i).data, v2);

    return false;
}

这里是 HashMap 类的 find() 方法

template <class K, class V>
V* HashMap<K, V>::find(const K& key) const {
    int bucket = (int) hash_fn(key) % arrLength;

    HashMap<K, V>::Node * temp = buckets[bucket];
    while(temp != NULL && temp->key != key)
        temp = temp->next;
    if(temp == NULL)
        return NULL;
    else
        return &(temp->value);
}

这是 Graph 类

template <class T>
class Graph {
    struct Node {
        T data;
        bool visited;
        LinkedList<Node> * adjacent;
        Node() {
            adjacent = nullptr;
            visited = false;
        }
        Node(T data) {
            this->data = data;
            adjacent = new LinkedList<Node>();
            visited = false;
        }
    };
    public:
        Graph();
        ~Graph();
        void addEdge(const T& v1, const T& v2);
        bool DFS(const T& v1, const T& v2);

    private:
        HashMap<T, Graph<T>::Node*> * map;
};

template <class T>
Graph<T>::Graph() {
    map = new HashMap<T, Graph<T>::Node*>();
}

template <class T>
Graph<T>::~Graph() {
    map->~Map<T, Graph<T>::Node*>();
}

template <class T>                                  // directed graph
void Graph<T>::addEdge(const T& v1, const T& v2) {  // add edge from v1 to v2

    if(map->find(v1) == NULL)   
        map->insert(v1, new Graph<T>::Node(v1));
    if(map->find(v2) == NULL)
        map->insert(v2, new Graph<T>::Node(v2));

    (*map->find(v1))->adjacent->append( **map->find(v2) ); // oh god
}

这是我构造和填充图形的 Main 方法,然后调用 DFS 方法。

int main() {

    Graph<int> * graph1 = new Graph<int>(); 
    graph1->addEdge(1, 5);
    graph1->addEdge(5, 9);
    graph1->addEdge(9, 20);

    graph1->DFS(1, 20);

    return 0;
}

在此先感谢您提供任何帮助或见解。 -鲍勃

【问题讨论】:

  • HashMap&lt;T, Graph&lt;T&gt;::Node*&gt; 这对我来说看起来很奇怪,你最好使用HashMap&lt;T, Graph&lt;T&gt;::Node&gt; 并让 HashMap 在有意义的地方添加指针限定符。
  • also: map-&gt;~Map&lt;T, Graph&lt;T&gt;::Node*&gt;(); 这看起来像是纯粹的邪恶,而且肯定会发生内存泄漏。有什么理由不打电话给delete map;
  • 更多与您的实际问题相关:LinkedList&lt;Node&gt; 我不知道该模板是如何实现的,但如果它类似于标准容器,这将在每个边引用内创建图形副本引用节点,导致一个巨大的 DAG,如果你在一段时间后暂停执行,它可能看起来无限大。这是LinkedList&lt;Node*&gt; 更有意义的情况。
  • @Frank 谢谢,我将LinkedList&lt;Node&gt; 更改为LinkedList&lt;Node*&gt; 并修复了它。但老实说,我不明白为什么要修复它。
  • @Frank 我不想只调用delete map,因为我认为我需要调用析构函数来删除邻接列表中的所有节点。

标签: c++ pointers reference pass-by-reference depth-first-search


【解决方案1】:

如果你对指针的使用看起来几乎是随机和任意的。你有没有意义的指针:HashMap&lt;T, Graph&lt;T&gt;::Node*&gt;,而在应该有的地方缺少指针:LinkedList&lt;Node&gt; * adjacent

您还可以在没有意义的地方使用动态分配,例如 Nodeadjacent 成员,并且您的清理要么不存在(Graph::Node 没有析构函数,但它应该有),或完全损坏(Graph&lt;T&gt;::~Graph() 将导致内存泄漏)。

此外,没有任何东西可以清除您的 visited 标志,因此只有一次调用 DFS 会起作用。

坦率地说,您的代码存在很多问题,无论是设计方面还是实现方面。

您的特定 DFS 问题可能来自于使用 LinkedList&lt;Node&gt; 而不是 LinkedList&lt;Node*&gt; 作为邻接列表,因为这可能会导致图成为一个巨大的 DAG,其中子图复制到邻接列表中,但在不知道的情况下很难判断LinkedList&lt;&gt; 是如何实现的。

编辑我觉得只是直截了当地说“这在所有方面都很糟糕”,这是我正确实现代码的方法(使用 stl 而不是自定义容器):

#include <unordered_map>
#include <vector>

template<typename T>
class Graph {
  struct Node {
    T data;
    bool visited;
    std::vector<Node*> adjacent;

    Node(T const& d) : data(d), visited(false) {}
  };
public:
  inline void addEdge(T const& v1, T const& v2);
  inline bool DFS(T const& v1, T const& v2);

private:
  inline bool DFS_recur(T const& v1, T const& v2);
  std::unordered_map<T, Node> map;
};

template<typename T>
void Graph<T>::addEdge(T const& v1, T const& v2) {
  auto v1_found = map.emplace(v1, v1).first;
  auto v2_found = map.emplace(v2, v2).first;

  v1_found->second.adjacent.emplace_back(&v2_found->second);
}

template<typename T>
bool Graph<T>::DFS(T const& v1, T const& v2) {
  auto result = DFS_recur(v1, v2);

  // Return to the invariant state.
  for(auto & n : map) {
    n.second.visited = false;
  }
  return result;
}

template<typename T>
bool Graph<T>::DFS_recur(T const& v1, T const& v2) {
  if(v1 == v2) return true;

  auto v1_found = map.find(v1);
  // If v1 is not in the map, we'll be in trouble.
  if(v1_found == map.end()) return false;

  v1_found->second.visited = true;

  for(auto const & neighbour : v1_found->second.adjacent) {
    if(!neighbour->visited) {
      return DFS_recur(neighbour->data, v2);
    }
  }

  return false;
}

int main() {

    Graph<int> graph1;
    graph1.addEdge(1, 5);
    graph1.addEdge(5, 9);
    graph1.addEdge(9, 20);

    graph1.DFS(1, 20);

    return 0;
}

注意所有内存管理是如何通过 RAII 处理的,而不是 newdelete

【讨论】:

  • 您说的完全正确,非常感谢您的洞察力。我根本没有考虑为什么以及在哪里使用指针,我只是没有特别原因地使用它们。我现在将尝试更加了解何时何地使用指针和动态内存分配。
  • 静态分配是完全不同的事情。我认为您的意思是使用值语义进行分配,在这种情况下通常是的,除非您正在处理多态性或共享所有权。请记住,像vectormap 这样的数据结构将在堆中分配它们的内容,因此容器本身实际上非常小。添加间接级别只会使代码更难阅读、执行更慢并且更容易出错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-08
  • 1970-01-01
相关资源
最近更新 更多