基于python的Graph图的表示

标签: Python  数据结构  算法  python  

图(Graph)的表示

1.图的概念

图是一种重要的数据结构,在解决实际问题中也经常用到这种数据结构,其基本表示为G=(V, E),V(vectex)是图的顶点,E(edge)表示图的边。我们的地图在计算机中就可以表示成一个Graph,不同的标志性地点为vectex,地点之间的路表示为edge,这样就可以方便操作实际中的地图去解决一些复杂的问题,比如找两个地点之间的最短路径,旅行商问题等等。

图分为有向图和无向图,有向图指的是各个顶点之间有一定的方向,一个顶点到另一个顶点需要按照给定的方向进行移动;而无向图指的是顶点之间没有固定方向。如下图所示,第一幅图为无向图,第二幅图为有向图。在有向图中,顶点1指向顶点2,说明1可以移向2,但是2不能移向1。
在这里插入图片描述
在这里插入图片描述

2.图的表示

图的表示就是表示清楚顶点信息和边的信息。有两种表示方法,邻接链表和邻接矩阵。如上面两张图可分别表示如下:

在这里插入图片描述
在这里插入图片描述

在计算机中,我们可以使用一些数据结构来简化图的表达,比如在Python中,我们可以用下面代码来表示一个图:

graph = {'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])}

graph是一个字典,其中keys是所有的vertices,values是与该顶点连接的其他顶点。如果我们需要获取所有的顶点,那么可以利用字典的keys()方法:

graph.keys()

output: dict_keys(['A', 'B', 'C', 'D', 'E', 'F'])

获取与某个顶点相连的其他顶点,就可直接利用字典的取值方法:

graph['A']

output: {'B', 'C'}

但是如果图中的每一个边有权,我们称之为有权图时,利用上述的表示方法不能很方便地得到权重信息,为此我们可以封装一个类,定义获取边,权重等相关信息的函数。

# Fall 2012 6.034 Lab 2: Search
from functools import reduce

try:
    set()
except NameError:
    from sets import Set as set, ImmutableSet as frozenset

NAME="NAME"
NODE1="NODE1"
NODE2="NODE2"
VAL="LENGTH"

class Edge:
    def __init__(self, name, node1, node2, length):
        self.name = name
        self.node1 = node1
        self.node2 = node2
        self.length = length
    def __repr__(self):
        return 'Edge ' + self.name + \
               ' from ' + self.node1 + ' to ' + self.node2 + \
               ' with length ' + str(self.length)

class Graph:
    def __init__(self, nodes=None, edgesdict=None, heuristic=None,
                 edges=None):
        '''specify EITHER edgesdict OR edges'''

        if edges:
            self.edges = edges
        elif edgesdict:
            try:
                self.edges = [Edge(e['NAME'], e['NODE1'], e['NODE2'], e['LENGTH'])\
                              for e in edgesdict]
            except KeyError:
                self.edges = [Edge(e['name'], e['node1'], e['node2'], e['length'])\
                              for e in edgesdict]
        else:
            self.edges = []
        self.nodes = nodes
        if not nodes:
            self.nodes = list(set([edge.node1 for edge in self.edges] + 
                                  [edge.node2 for edge in self.edges]))
        # heuristic is a dictionary where heuristic[G][S] is the
        #  heuristic distance from S to G
        self.heuristic = heuristic
        if not heuristic:
            self.heuristic = {}
        self.validate()
    
    def validate(self):
        for name in self.nodes:
            assert isinstance(name, str), str(type(name))+": "+str(name)
        assert len(self.nodes) == len(set(self.nodes)), "no duplicate nodes"
        edgenames = [edge.name for edge in self.edges]
        assert len(edgenames) == len(set(edgenames)), "no duplicate edges"
        for edge in self.edges:
            assert isinstance(edge.name, str), type(edge.name)
            assert edge.node1 in self.nodes
            assert edge.node2 in self.nodes
            assert edge.length > 0, "positive edges only today"
        for start in self.nodes:
            for end in self.nodes:
                assert self.get_heuristic(start,end) >= 0

    def get_connected_nodes(self, node):
        """
        gets a list of all node id values connected to a given node.
        'node' should be a node name, not a dictionary.
        The return value is a list of node names.
        """
        assert node in self.nodes, "No node "+str(node)+" in graph "+str(self)
        result = [x.node2 for x in self.edges if x.node1 == node]
        result += [x.node1 for x in self.edges if x.node2 == node]
        return sorted(result)

    def get_edge(self, node1, node2):
        """
        checks the list of edges and returns an edge if
        both connected nodes are part of the edge, or 'None' otherwise.
        'node1' and 'node2' are names of nodes, not 'NODE' dictionaries.
        """
        assert node1 in self.nodes, "No node "+str(node1)+" in graph "+str(self)
        assert node2 in self.nodes, "No node "+str(node2)+" in graph "+str(self)
        node_names = ( node1, node2 )
        for edge in self.edges:
            if ((edge.node1, edge.node2) == node_names or 
                (edge.node2, edge.node1) == node_names):
                return edge
        return None

    def are_connected(self, node1, node2):
        """
        checks if two edges are connected.
        'node1' and 'node2' are names of nodes, not 'NODE' dictionaries.
        """
        return bool(self.get_edge(node1, node2) )

    def get_heuristic(self, start, goal):
        """ Return the value of the heuristic from the start to the goal"""
        assert start in self.nodes, "No node "+str(start)+" in graph "+str(self)
        assert goal in self.nodes, "No node "+str(goal)+" in graph "+str(self)
        if goal in self.heuristic:
            if start in self.heuristic[goal]:
                return self.heuristic[goal][start]
            else:
                return 0 # we have checked that everything is positive
        else: 
            return 0 # we have checked that everything is positive
    
    def is_valid_path(self, path):
        def is_valid_path_reducer(elt_a, elt_b):
            if elt_a == False or not self.are_connected(elt_a, elt_b):
                return False
            else:
                return elt_b
        return (reduce(is_valid_path_reducer, path) != False)

    def add_edge(self, node1, node2, length, name=None):
        if node1 not in self.nodes:
            self.nodes.append(node1)
        if node2 not in self.nodes:
            self.nodes.append(node2)
        if name == None:
            name = ("%s %s" % (node1, node2))
        self.edges.append(Edge(name, node1, node2, length))

    def set_heuristic(self, start, goal, value):
        if goal not in self.heuristic:
            self.heuristic[goal] = {}
        self.heuristic[goal][start] = value

    def __str__(self):
        return "Graph: \n  edges="+str(self.edges)+"\n  heuristic="+str(self.heuristic)


根据init方法的定义,一个图可以如下定义:

AGRAPH = Graph(nodes = ['S', 'A', 'B', 'C', 'G'],
               edgesdict = [{'NAME': 'eSA', 'LENGTH': 3, 'NODE1': 'S', 'NODE2': 'A'},
                            {'NAME': 'eSB', 'LENGTH': 1, 'NODE1': 'S', 'NODE2': 'B'},
                            {'NAME': 'eAB', 'LENGTH': 1, 'NODE1': 'A', 'NODE2': 'B'},
                            {'NAME': 'eAC', 'LENGTH': 1, 'NODE1': 'A', 'NODE2': 'C'},
                            {'NAME': 'eCG', 'LENGTH': 10, 'NODE1': 'C', 'NODE2': 'G'}],
               heuristic = {'G':{'S': 12,
                                 'A': 9,
                                 'B': 12,
                                 'C': 8,
                                 'G': 0}})

heuristic是启发式的距离,在A*算法中会使用到这个属性,这里不做过多的描述。我们只需明白图的表示可以很灵活多样,但关键在于把需要的信息表达清楚即可,特别是顶点和边的信息。对于不同问题,我们可以使用不同的数据结构和算法对图进行表示。

参考资料:

1.算法导论第三版

2.MIT Artificial Intelligence open course Assignment lab2 code

版权声明:本文为zhongbing1234原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhongbing1234/article/details/108966067

智能推荐

图算法基础:图的表示方法

关于图的表示 对于图G=(V,E),可以用两种标准的表示方法表示,一种表示方法将图作为邻接链表的组合,另一种表示法则将图作为邻接矩阵来表示。两种表示方法都可以表示无向图和无向图。当图为稀疏图时(|E|<<|V2|),通常采用邻接链表,如此节省空间开销;反之,当图为稠密图时(|E|接近于 |V2|),则采用邻接矩阵。此外,如果需要直接判断任意两个结点之间是否有边连接,可能也要用邻接矩阵。...

冒泡排序,及改进方式,性能优化400%>>>附图解加源码

首先源码附上,源码中带有注释,看不懂没关系,源码后面附带图解,最后附上代码效率提升图 源码如下: 方案一:其实实现很简单,两层循环,每次内层迭代出最大的一个值,将其放入数组最后一个位置,外层循环的末端便往前移一位。其原理如下图 方案一代码块: 方案二:优化改进 仔细观察上面的图,我们不难发现当迭代到图下这样的情况时,其实已经全排序好了,但是我们还是需要对它进行1,2,3,4,5,6的迭代,这些情况...

css3常用选择器

先介绍一下基本选择器,如下几种 *:通配符。选择页面的所有元素 E:元素选择器。对页面的一些元素进行选择,如p,div,li等 .class:类选择器 #id:id选择器。一个Id在一个页面中只能选择,这是和类选择器的区别 E F:后代选择器。如ul li。选中ul下的li E>F:子元素选择器。如下,可以用 h1 > strong {color:red;}使h1的子元素strong变...

python is not set from command line or npm configuration

问题 猜测原因  python版本冲突 解决方案  去官网找到最新版本python 点击下载,注意记住你下载的目录,下载好以后,打开这个目录下的文件夹,Python310, 整个文件夹复制一下 在项目里运行npm config list --json 找到python的路径   我的是 C:\\Python27\\python.exe 说明项目里用的还是py...

Java8 Stream流操作

前言 我们常常需要将一个容器转化成另一个容器,或是对这个容器中的数据进行批量处理,这时使用Stream流可以大大减少我们的工作量。 1 Stream概述 Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。 那么什么是Stream? Stream将要处理的元素集合看作一种流,在流的过程中,借助St...

猜你喜欢

带你玩转Visual Studio——带你跳出坑爹的Runtime Library坑

在Windows下进行C++的开发,不可避免的要与Windows的底层库进行交互,然而VS下的一项设置MT、MTd、MD和MDd却经常让人搞迷糊,相信不少人都被他坑过,特别是你工程使用了很多第三库的时候,及容易出现各种链接问题。看一下下面这个错误提示:  LIBCMT.lib(_file.obj) : error LNK2005: ___initstdio already defined...

git 设置 gitignore 忽略 __pycache__

清除git缓存中的pycache 直接删掉硬盘上的文件 如果我想保留硬盘上的这个文件,而只删除版本管理中的文件,就需要加入--cached参数。 切换分支出现问题 尽管我已经删除了__pycache__,硬盘也没有了,但是切换分支的时候依然是会提示本地重写的情况。 提示:需要我在切换分支之前,提交一次更新。 提交了更新之后,再来尝试切换分支,如下: 成功了,说明当做了任何变更之后,切换分支前需要执...

人生苦短,我用Python(二)— 爬取会议网站 EasyChair Smart CFP

寒假留校帮学长写了个爬虫,抓取会议网站上一些CFP信息。想着把一些知识点、坑点记下来,一来做个小总结给工作收收尾,二是以后再遇到好从容应对。 这是我写的第二个比较完善的爬虫了,比第一个要简单许多,完全过程化的代码,而且easychair这个网站页面布局比较友好,适合python新手、前端小白入门练习。但这个网站反爬比较厉害,写爬虫的过程中就被ban了好几次…… 制定抓取...

(三)Single Threaded Execution模式

一、定义 一个线程执行。有时也称为Critical Section(临界区)。   二、模式案例 案例: 假设有三个人,频繁地通过一扇门,规定每次只能通过一个人,当通过一个人时,程序会将通过的总人次加1,同时记录该次通过人的姓名和出生地。   门的定义:   人的定义     测试类     多次执行后发现可能出现以下结果: &...

Linux服务管理之系统管理员需要掌握的命令

为什么80%的码农都做不了架构师?>>>    systemd的主要命令行工具是systemctl。大多数Linux系统管理员应该都已经非常熟悉系统服务和init系统管理,比如service,chkconfig和telinit命令的使用。systemd也完成同样的管理任务,只是命令工具systemctl的语法有所不同而已。 1. sysvinit和system...