首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >图实现,用于查找两个节点之间的循环、路径、最近路径等。

图实现,用于查找两个节点之间的循环、路径、最近路径等。
EN

Code Review用户
提问于 2015-08-06 01:24:38
回答 2查看 629关注 0票数 1

我正在重新学习图论,我想编写一个Graph类,允许我实现这些方法。是否有更好的实现通用Graph类的方法?

主要职能:

代码语言:javascript
运行
复制
int main() {

    using namespace GraphUtils;

    Graph<char> graph;

    Node<char> a('a');
    Node<char> b('b');
    Node<char> c('c');
    Node<char> d('d');
    Node<char> e('e');
    Node<char> f('f');

    graph.AddNode(a).AddNode(b).AddNode(c).AddNode(d).AddNode(e).AddNode(f);

    a.AddChild(f).AddChild(d);
    b.AddChild(c).AddChild(a);
    c.AddChild(d);
    e.AddChild(f);
    f.AddChild(b);

    graph.Print();


    if( !PathExist(graph, a, b)) {
        cout << "ERR: a -> b exists" << endl;
    }

    if( !PathExist(graph, a, c)) {
        cout << "ERR: a -> c exists" << endl;
    }

    if( PathExist(graph, a, e)) {
        cout << "ERR: a -> c doesn't exist" << endl;
    }

    if( PathExist(graph, c, e)) {
        cout << "ERR: c -> e doesn't exist" << endl;
    }
    if( !PathExist(graph, e, c)) {
        cout << "ERR: e -> c exists" << endl;
    }
}

类实现,它严重地面向指针:

代码语言:javascript
运行
复制
#include <iostream>
#include <map>
#include <vector>
#include <queue>


using std::map;
using std::vector;
using std::cout;
using std::endl;
using std::queue;

namespace GraphUtils {

template <typename T>
struct Node {
    T data;
    vector<Node*> children;

    Node() {}
    Node(T input): data(input) {}


    Node& AddChild(Node& input) {
        children.push_back(&input);
        return *this;
    }

};


template <typename T>
bool operator<(Node<T>& lhs, Node<T>& rhs) {
    return (lhs.data < rhs.data);
}


template <typename T>
bool operator==(Node<T>& lhs, Node<T>& rhs) {
    return !(lhs.data < rhs.data || lhs.data > rhs.data);
}


template<typename T>
struct Graph {
    vector<const Node<T>* > nodes;

    Graph& AddNode(const Node<T>& input) {
        nodes.push_back(&input);
        return *this;
    }

    void Print() {

        for(typename vector<const Node<T>* >::const_iterator iter = nodes.begin();
                iter != nodes.end();
                iter++)
        {
            cout << (*iter)->data << ":";

            for(typename vector<Node<T>* >::const_iterator child = (*iter)->children.begin();
                    child != (*iter)->children.end();
                    child++)
            {
                cout << (*child)->data << ", ";

            }

            cout << endl;
        }
    }
};


// using BFS to determine if path exists
template<typename T>
bool PathExist(Graph<T>& graph, Node<T>& a, Node<T>& b) {

    map<const Node<T>* , bool> t_visited;
    queue<Node<T>* > t_queue;

    for(typename vector<const Node<T>* >::const_iterator iter = graph.nodes.begin();
            iter != graph.nodes.end(); iter++)
    {
        t_visited[*iter] = false;
    }

    t_queue.push(&a);

    Node<T>* t_node;
    while(!t_queue.empty()) {

        // get the top object
        t_node = t_queue.front();
        t_queue.pop();

        // set visited
        t_visited[t_node] = true;

        // node doesn't have the == operator
        if(*t_node == b) return true;

        // for each ot it's children
        for(typename vector<Node<T>* >::iterator iter = t_node->children.begin();
                iter !=  t_node->children.end(); iter++)
        {
            // if item has not been visited
            if(!t_visited[(*iter)]) {
                t_queue.push((*iter));
            }
        }
    }

    return false;
}





};
EN

回答 2

Code Review用户

回答已采纳

发布于 2015-08-06 02:52:54

这在某种意义上取决于什么是好的。这是一个非常标准的邻接列表实现,这是没有错的。它是否好取决于您将询问哪个查询的数据结构。如果对稀疏图进行路径搜索或最小生成树,那是很好的。如果你想做一些聚类,那么也许邻接矩阵更好。

在API级别上,我喜欢链式添加方法。它很好,而且很容易使用。存储指向别名的指针避免了内存泄漏的问题,但它使程序员觉得为每个节点声明一个变量有点傻,其中只有一个字符。如果节点的数量动态增长,这是使用邻接列表的优势之一,那该怎么办?为什么我不能喜欢graph.AddNode('a')?然后用graph.get('a').AddChild('b')或其他什么方式添加子元素。

此外,如果节点局部变量超出作用域,则该图形必须死掉或面临分割错误的危险,甚至是一些安全问题。不太灵活。

票数 3
EN

Code Review用户

发布于 2015-08-09 21:01:57

公共实现:糟糕的

不是的。您基本上允许入侵实现细节来实现任何事情。

代码语言:javascript
运行
复制
struct Graph {
    vector<const Node<T>* > nodes;

基本上,您将给自己带来麻烦,因为您已经公开了图形如何存储其数据的实现细节。现在人们不得不用这个来编写他们的函数。

锁定成员变量,并通过添加节点和边缘的方法提供对类的访问。

访问者模式

您还应该查找访问者模式。这允许人们编写图遍历算法,而不知道图的细节。

停止使用using

代码语言:javascript
运行
复制
using std::map;
using std::vector;
using std::cout;
using std::endl;
using std::queue;

您将通过将其放入头文件来破坏其他人的代码。我不喜欢你在源文件中使用这个(除非你仔细观察它)。但将其放入头文件是一个定义不-否。如果我将头文件包含在源文件中,则可能会导致代码中几乎无法检测到的破坏。因此,您将发现大多数项目将拒绝使用您的代码,因为他们的代码库潜在的危险。

如果你不介意把你自己的代码搞乱(显然你不介意),把它放在你自己的命名空间里,那么至少它不会影响我的代码。

参数

正确地使用它们。

代码语言:javascript
运行
复制
Graph& AddNode(const Node<T>& input) {
    nodes.push_back(&input);
    return *this;
}

因此,您可以通过引用传递节点。这意味着你会在内部复制一份。这使得这是多余的:

代码语言:javascript
运行
复制
Node<char> a('a');
Node<char> b('b');
Node<char> c('c');
Node<char> d('d');
Node<char> e('e');
Node<char> f('f');

graph.AddNode(a).AddNode(b).AddNode(c).AddNode(d).AddNode(e).AddNode(f);

现在每个节点都有两个副本。

这样写会更容易:

代码语言:javascript
运行
复制
graph.AddNode(Node<char>('a')).AddNode(Node<char>('b')).AddNode(Node<char>('c')).AddNode(Node<char>('d')).AddNode(Node<char>('e')).AddNode(Node<char>('f'));

但这似乎有点冗长。因此,您可以编写一个帮助函数,为您进行类型推断。

代码语言:javascript
运行
复制
template<typename T>
Node<T> mkNode(T const& v) {return Node<T>(v);}

  graph.AddNode(mkNode('a')).AddNode(mkNode('b')).AddNode(mkNode('c')).AddNode(mkNode('d')).AddNode(mkNode('e')).AddNode(mkNode('f'));

或者更好的是,只需编写一个AddNode(),它需要一个T,internall就可以创建一个正确类型的Node

代码语言:javascript
运行
复制
graph.AddNode('a').AddNode('b').AddNode('c').AddNode('d').AddNode('e').AddNode('f');
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/99153

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档