图解图论介绍及应用(4):图的表示:结尾

点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”


作者:Vardan Grigoryan

编译:ronghuaiyang

导读

知识图谱是AI领域非常有用的一种工具,知识图谱的基础就是图论,从今天开始,给大家介绍一些图论的基础内容,今天是第4篇,图的表示:结尾

关于图的坏消息是,图表示没有一个单独的定义。这就是为什么你在库里找不到“std::graph”。我们已经有机会表示一个称为BST的“特殊”图。重点是,树是一个图,但图不是树。最后的插图向我们表明,我们在单个对象中有很多的树,“价格与房屋”和一些顶点是“不同”的类型,价格节点是图中只有价格的节点,指向具有特定的价格的整棵树的ID(home顶点)。它更像是一种混合数据结构,而不是我们在教科书例子中看到的简单的图。

这是图表示的关键点,图表示没有固定的结构(不像BSTs使用指定的基于节点的表示和左/右子指针,尽管你也可以使用单个数组表示BST)。你可以用你希望的最方便的方式来表示一个图(对于特定的问题最方便),最重要的是你可以将它“视为”一个图。我们所说的“看图”是指应用特定于图的算法。

那么n元树呢,它更像一个图。

640?wx_fmt=png

首先想到的表示一个n元树节点是这样的:

struct NTreeNode	
{	
  T value;	
  vector<NTreeNode*> children;	
};

这个结构只表示树的一个节点。完整的树看起来更像这样:

// almost pseudocode	
class NTree	
{	
public:	
  void Insert(const T&);	
  void Remove(const T&);	
  // lines of omitted code	
private:	
  NTreeNode* root_;	
};

这个类是围绕一个名为 root_的树节点的抽象。这就是我们构建任何大小的树所需要的。这是树的起点。要添加一个新的树节点,我们需要为它分配一个内存,并将该节点添加到树的根中。

图很像n元树,只是略有不同。试着去发现它。

640?wx_fmt=png

这是一个图表?不是,我的意思是,是的,但是它和之前的图中的n元树是一样的,只是稍微旋转了一下。根据经验,无论何时看到一棵树(即使是苹果树、柠檬树或二叉搜索树),都可以确定它也是一个图。因此,设计一个图节点(图顶点)的结构,我们可以得到相同的结构:

struct GraphNode	
{	
  T value;	
  vector<GraphNode*> adjacent_nodes;	
};

这足以构成一个图表吗?嗯,没有。这是为什么。看看这两张图从之前的插图,找到一个不同之处:

640?wx_fmt=png

640?wx_fmt=png

两个都是图

上面插图中的图没有一个点可以“enter”(它是一个森林而不是一棵树),相反,右边插图中的图没有不可到达的顶点。听起来很熟悉。

当每一对顶点之间都有一条路径时,图就是连通的

显然,对于“价格vs房屋”图,在每一对顶点之间并没有一条路径(如果从图中看不出来,就假设房价之间没有联系)。虽然这只是一个例子,说明我们无法使用单个GraphNode结构构造一个图,但在某些情况下,我们必须处理这样的断开连接的图。看看这个类:

class ConnectedGraph	
{	
public:	
  // API	
private:	
  GraphNode* root_;	
};

正如n元树是围绕单个节点(根节点)构建的一样,连通图也可以围绕根节点构建。树是有根的,也就是说它们有一个起点。一个连通图可以表示为一个有根树(还有几个属性),这已经很明显了,但是请记住,实际的表示可能会因算法而异,甚至对于一个连通图,也可能因问题而异。但是,考虑到图的节点属性,断开连接的图可以表示为:

class DisconnectedGraphOrJustAGraph	
{	
public:	
  // API	
private:	
  std::vector<GraphNode*> all_roots_;	
};

对于像DFS/BFS这样的图遍历,使用树状表示是很自然的。非常有用。但是,像高效路径跟踪这样的情况需要不同的表示。还记得欧拉图吗?要跟踪一个图的“欧拉值”,我们应该跟踪其中的欧拉路径。这意味着通过只遍历每条边一次来访问所有顶点,当跟踪完成并且我们有未遍历的边时,图就没有欧拉路径,因此不是欧拉图。

还有一种更快的方法,我们可以检查顶点的度数(假设每个顶点都存储它的度数),正如定义所说,如果一个图有奇数度的顶点,并且没有恰好两个,那么它就不是欧拉图。这种检查的复杂度是O(|V|),其中|V|是图顶点的数量。我们可以在插入新边时跟踪奇/偶度,从而将奇/偶度检查增加到O(1)。我们只需要跟踪一个图形。下面是图的表示形式和返回路径的Trace()函数。

// A representation of a graph with both vertex and edge tables	
// Vertex table is a hashtable of edges (mapped by label)	
// Edge table is a structure with 4 fields	
// VELO = Vertex Edge Label Only (e.g. no vertex payloads)	
class ConnectedVELOGraph {	
public:	
    struct Edge {	
        Edge(const std::string& f, const std::string& t)	
            : from(f)	
            , to(t)	
            , used(false)	
            , next(nullptr)	
        {}	
        std::string ToString() {	
            return (from + " - " + to + " [used:" + (used ? "true" : "false") + "]");	
        }	
        std::string from;	
        std::string to;	
        bool used;	
        Edge* next;	
    };	
    ConnectedVELOGraph() {}	
    ~ConnectedVELOGraph() {	
        vertices_.clear();	
        for (std::size_t ix = 0; ix < edges_.size(); ++ix) {	
            delete edges_[ix];	
        }	
    }	
public:	
    void InsertEdge(const std::string& from, const std::string& to) {	
        Edge* e = new Edge(from, to);	
        InsertVertexEdge_(from, e);	
        InsertVertexEdge_(to, e);	
        edges_.push_back(e);	
    }	
public:	
    void Print() {	
        for (auto elem : edges_) {	
            std::cout << elem->ToString() << std::endl;	
        }	
    }	
    std::vector<std::string> Trace(const std::string& v) {	
        std::vector<std::string> path;	
        Edge* e = vertices_[v];	
        while (e != nullptr) {	
            if (e->used) {	
                e = e->next;	
            } else {	
                e->used = true;	
                path.push_back(e->from + ":-:" + e->to);	
                e = vertices_[e->to];	
            }	
        }	
        return path;	
    }	
private:	
    void InsertVertexEdge_(const std::string& label, Edge* e) {	
        if (vertices_.count(label) == 0) {	
            vertices_[label] = e;	
        } else {	
            vertices_[label]->next = e;	
        }	
    }	
private:	
    std::unordered_map<std::string, Edge*> vertices_;	
    std::vector<Edge*> edges_;	
};

小心bugs,bugs无处不在。这段代码包含了很多假设,比如标签,通过一个顶点我们可以理解一个字符串标签。当然,你可以轻松地将其更新为你想要的任何内容。在本例的上下文中并不重要。接下来,命名。正如评论中提到的,VELOGraph只用于顶点边标签图(这是我编的)。重点是,这个图表示包含一个表,用于映射一个顶点标签,其中的边与该顶点关联,以及一个包含一对顶点(由特定的边连接)的边列表和一个仅由Trace()函数使用的标志。看一下Trace()函数的实现。它使用edge的标志来标记已经遍历的edge(应该在任何Trace()调用之后重置标志)。

(未完待续)

640?wx_fmt=png— END—

英文原文:https://medium.com/free-code-camp/i-dont-understand-graph-theory-1c96572a1401

640?wx_fmt=jpeg

请长按或扫描二维码关注本公众号

喜欢的话,请给我个好看吧640?wx_fmt=gif


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值