【问题标题】:Finding the number of edges and performing a topo sort in my graph implementation在我的图形实现中查找边数并执行拓扑排序
【发布时间】:2015-05-02 08:30:37
【问题描述】:

过去几天我一直在研究图形实现。所有这一切对我来说都是新的,我被困在我的实施的两个部分。我正在从输入文件实现课程的有向图。从文件中,我可以确定哪些课程是其他课程的先决条件。然后,我创建一个有向图,其中课程作为节点,边连接作为先决条件的课程。我还想找到节点和边的总数,并对图执行拓扑排序(稍后我将向边添加权重)。这是我的实现。

有向图.h

class vertex{
public:

    typedef std::pair<int, vertex*> ve;
    std::vector<ve> adjacency;
    std::string course;
    vertex(std::string c){
        course = c;
    }
};

class Digraph{
public:
    void addVertex(std::string&);
    void addEdge(std::string& from, std::string& to, int cost);
    typedef std::map<std::string, vertex *> vmap;
    vmap work;
    int getNumVertices();
    int getNumEdges();
    void getTopoSort(); 

};

有向图.cpp

void Digraph::addVertex(std::string& course){
    vmap::iterator iter = work.begin();
    iter = work.find(course);
    if(iter == work.end()){
        vertex *v;
        v = new vertex(course);
        work[course] = v;
        return;
    }
}

void Digraph::addEdge(std::string& from, std::string& to, int cost){
    vertex *f = (work.find(from)->second);
    vertex *t = (work.find(to)->second);
    std::pair<int, vertex *> edge = std::make_pair(cost, t);
    f->adjacency.push_back(edge);
}

返回work.size 即可轻松查找节点数。我已经确认这工作正常。我不知道如何返回图表中的边数。看起来这很简单,但我尝试的一切都不起作用。其次,我完全不知道如何在这个图上执行拓扑排序。感谢您提供任何帮助。

【问题讨论】:

  • 请注意,Digraph::addEdge 可以将新的边从 A 添加到 B,即使已经有边。

标签: c++ graph graph-algorithm topological-sort


【解决方案1】:

一种简单的方法是遍历图中的所有顶点,将它们的邻居计数相加,然后除以 2:

int Digraph::getNumEdges(){
     int count = 0;
     for (const auto & v : work) {
         count += v.second->adjacency.size();
     }
     return count / 2;
}

要使用基于范围的for循环,需要使用c++11。使用 g++ 在命令行上将是 --std=c++11

编辑: 我刚刚意识到你有一个有向图,你可能想为每个方向计算一个。在这种情况下:不要除以二!

int Digraph::getNumEdges(){
     int count = 0;
     for (const auto & v : work) {
         count += v.second->adjacency.size();
     }
     return count;
}

【讨论】:

  • 谢谢。它工作得很好,而且看起来很简单。你能给我一些关于如何处理拓扑排序的指导吗?
【解决方案2】:

首先,对于边的数量,在构建图形时直接计算它们会更简单(只需在 Digraph 类中添加一个计数器,并在每次添加边时递增它......)

对于拓扑排序,首先我有一个问题:您的优势是从先决条件到依赖课程?也就是说,如果 A 是 B 的先决条件,那么您有一个链接 A -> B 吗?如果不是这种情况,您需要反转您的图表。

您使用主要算法来构建拓扑排序:一个基于简单的 DFS (http://en.wikipedia.org/wiki/Depth-first_search),另一个依赖于顶点的入度 (http://en.wikipedia.org/wiki/Directed_graph#Indegree_and_outdegree)(在您的案例中是课程。)

通常,您需要验证您的图表不包含任何循环,如果您的数据是连贯的,通常就是这种情况。

让我们考虑基于 DFS 的算法:DFS 从给定的根开始沿着出现的边遍历每个顶点。我们可以很容易地证明一个顶点的最后相遇顺序形成了一个反向拓扑顺序。因此,我们只需要在调用其后继者之后将当前顶点压入堆栈。

我为你做了一个快速而肮脏的实现,再次使用 C++11。

首先,将以下内容添加到 Digraph 类中:

  typedef std::unordered_set<vertex*> marks_set;
  marks_set marks;
  typedef std::deque<vertex*> stack;
  stack topo;
  void dfs(vertex* vcur);

然后是代码:

void Digraph::dfs(vertex* vcur) {
  marks.insert(vcur);
  for (const auto & adj : vcur->adjacency) {
    vertex* suc = adj.second;
    if (marks.find(suc) == marks.end()) {
      this->dfs(suc);
    } // you can detect cycle in the else statement
  }
  topo.push_back(vcur);
}

void Digraph::getTopoSort() {
  // It should be a good idea to separate this algorithm from the graph itself
  // You probably don't want the inner state of it in your graph,
  // but that's your part.

  // Be sure marks and topo are empty
  marks.clear();
  topo.clear();
  // Run the DFS on all connected components
  for (const auto & v : work) {
    if (marks.find(v.second) == marks.end()) {
      this->dfs(v.second);
    }
  }
  // Display it
  for (const auto v : topo) {
    std::cout << v->course << "\n";
  }
}

代码可以编译,但我还没有测试过。如果出于任何原因您对递归算法(函数 Digraph::dfs)有疑问,可以使用包含目标顶点的父级和当前后继者的迭代器的堆栈derecursified,迭代器到达邻接表的末尾,可以将父节点推入拓扑排序中。

另一种算法几乎同样简单:对于每个顶点,您需要计算前导数(in-degree),这可以在构建图形时完成。为了计算拓扑排序,您要查找入度为 0 的第一个顶点(无前驱),然后减少其所有后继的入度并继续使用 0 的下一个顶点。如果图形有没有循环,总会有一个入度为 0 的顶点(当然是在开始时,但也在算法运行期间减少它),直到所有顶点都被看到。顶点遇到的顺序形成拓扑排序(这与贝尔曼最短路径算法有关。)

请注意,此处列出了这两种算法:http://en.wikipedia.org/wiki/Topological_sorting。使用入度的方法是根据移除边缘来描述的,我们只是通过降低入度来模拟(一种破坏性要小得多的方法……)

【讨论】:

    猜你喜欢
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多