图及其衍生算法(Graphs and graph algorithms)

1. 图的相关概念

    树是一种的图,相比树,图更能用来表示现实世界中的的实体,如路线图,网络节点图,课程体系图等,一旦能用图来描述实体,能模拟和解决一些非常复杂的任务。图的相关概念和词汇如下:

      顶点vertex:图的节点

      边Edge:顶点间的连线,若边具有方向时,组成有向图(directed graph)

      权重weight:从一个顶点到其他不同顶点的距离不一样,因此边具有权重,来表示不同的距离

      路径path:从一个顶点到另一个的所有边的集合

      回路cycle:在有向图中,从一个顶点开始,最后又回到起始顶点的路径为一个回路

      图可以表示为G={V,E},其中V为顶点的集合,E为边的集合,如下图所示:

   

2,图的实现

    图的相关操作如下:

Graph()                              #创建图
addVertex(vert)                      #添加顶点
addEdge(fromVert, toVert)            #添加边
addEdge(fromVert, toVert, weight)    #添加边及其权重
getVertex(vertKey)                   #获取某个顶点
getVertices()                        #获取所有顶点
in                                   #判断是否包括某个顶点

    可以通过邻接矩阵(adjacency matrix)或领接表(adjacency list)来实现图。邻接矩阵表示图的结构如下,图中的数字表示两个顶点相连,且边的权重为该值。可以发现邻接矩阵更加适合于边较多的图,不然会造成内存空间的浪费。

    邻接表表示图的结构如下,一个主列表中包含图的所有顶点,每个顶点又各包含列表记录与其相连的边。可以发现邻接表更适合边较少的图。

    python实现邻接表代码如下:

#coding:utf-8

class Vertex(object):
    def __init__(self,key):
        self.id=key
        self.connectedTo={}

    def __str__(self):
        return str(self.id) + "connected to" +str([x.id for x in self.connectedTo])

    #nbr为vertex对象
    def addNeighbor(self,nbr,weight=0):
        self.connectedTo[nbr]=weight

    def getConnections(self):
        return self.connectedTo.keys()

    def getId(self):
        return self.id

    def getWeight(self,nbr):
        return self.connectedTo[nbr]

class Graph(object):

    def __init__(self):
        self.vertList = {}
        self.numVertices = 0

    def addVertex(self,key):
        newVertex = Vertex(key)
        self.vertList[key]=newVertex
        self.numVertices +=1
        return newVertex

    def getVertex(self,key):
        if key in self.vertList:
            return self.vertList[key]
        else:
            return None

    def __contains__(self, key):
        return key in self.vertList

    #fromVert,toVert为起始和终止节点的key
    def addEdge(self,fromVert,toVert,weight=0):
        if fromVert not in self.vertList:
            self.addVertex(fromVert)
        if toVert not in self.vertList:
            self.addVertex(toVert)
        self.vertList[fromVert].addNeighbor(self.vertList[toVert],weight)

    def getVertices(self):
        return self.vertList.keys()

    def __iter__(self):
        return iter(self.vertList.values())

if __name__ == ‘__main__‘:
    g = Graph()
    for i in range(6):
        g.addVertex(i)
    g.addEdge(0, 1, 5)
    g.addEdge(0, 5, 2)
    g.addEdge(1, 2, 4)
    g.addEdge(2, 3, 9)
    g.addEdge(3, 4, 7)
    g.addEdge(3, 5, 3)
    g.addEdge(4, 0, 1)
    g.addEdge(5, 4, 8)
    g.addEdge(5, 2, 1)
    for v in g:
        for w in v.getConnections():
            print("( %s , %s )" % (v.getId(), w.getId()))

3.图的应用

  3.1 Word ladder problem

     word ladder规则:从单词‘FOOL’变为单词‘SAGE’,每次只能改变一个字母,且改变一个字母后的单词必须存在,求可能的变化路径。

     一个路径示意:FOOL POOL POLL POLE PALE SALE SAGE

     解决思路:

          1,创建图,以单词为顶点,若两个单词间只相差一个字母,则两个单词间有一条边(双向的边)

          2,对图进行宽度优先搜索,找到合适的路径

     创建图:

          下载四个字母的单词,构造成一行一个单词的文本文件(大概5200个单词)。按照下面的格式构造一个字典,方块中的_OPE为键,其对应的单词组成一个列表,为字典的值,对每一个单词,都可以构造如下四个这样的键值对。然后再根据字典创建图。

            

          上述过程,用python代码实现如下:(由于数据量较大,构建图过程中可能出现memory error)

#coding:utf-8

from graphDemo import Graph,Vertex

def buildGraph():
    with open(‘fourLetters.txt‘, ‘r‘) as f:
        lines = f.readlines()
    d={}
    for line in lines:
        word = line.strip() #删除换行符
        for i in range(len(word)):
            label = word[:i]+‘_‘+word[i+1:]
            if label in d:
                d[label].append(word)
            else:
                d[label]=[word]
    #print d.keys()
    g = Graph()
    for x in d.keys():
        for y in d.keys():
            if x!=y:
                g.addEdge(x,y)
    return g

      宽度优先搜索(breadth first search,BFS):即先搜索同级的顶点,再搜索下一级的顶点。代码中引入了队列Queue,并用三种颜色标记顶点的状态,白色表示顶点未被搜索,灰色表示顶点被搜索,且被放入了队列中,黑色表示顶点被搜索,且其所有下一级顶点都被加入了队列中,如下图所示。

       用python实现宽度优先搜索,并寻找从FOOL 到SAGE的最短路径,代码如下:

from graphDemo import Graph,Vertex
from queueDemo import Queue

#class Vertex(object):
#     def __init__(self,key):
#         self.id=key
#         self.connectedTo={}
#         self.color = ‘white‘
#         self.distance = None
#         self.predecessor = None
#
#     def __str__(self):
#         return str(self.id) + "connected to" +str([x.id for x in self.connectedTo])
#
#     #nbr为vertex对象
#     def addNeighbor(self,nbr,weight=0):
#         self.connectedTo[nbr]=weight
#
#     def getConnections(self):
#         return self.connectedTo.keys()
#
#     def getId(self):
#         return self.id
#
#     def getWeight(self,nbr):
#         return self.connectedTo[nbr]
#
#     def setColor(self,color):
#         self.color = color
#
#     def getColor(self):
#         return self.color
#
#     def setDistance(self,distance):
#         self.distance = distance
#
#     def getDistance(self):
#         return self.distance
#
#     def setPred(self,pred):
#         self.predecessor = pred
#
#     def getPred(self):
#         return self.predecessor

def buildGraph():
    with open(‘fourLetters.txt‘, ‘r‘) as f:
        lines = f.readlines()
    d={}
    for line in lines:
        word = line.strip() #删除换行符
        for i in range(len(word)):
            label = word[:i]+‘_‘+word[i+1:]
            if label in d:
                d[label].append(word)
            else:
                d[label]=[word]
    #print d.keys()
    g = Graph()
    for x in d.keys():
        for y in d.keys():
            if x!=y:
                g.addEdge(x,y)
    return g

def bfs(g,start):
    start.setDistance(0)
    start.setPred(None)
    vertQueue = Queue()
    vertQueue.enqueue(start)
    while vertQueue.size()>0:
        currentVert = vertQueue.dequeue()
        for vert in currentVert.getConnections():
            if (vert.getColor()==‘white‘):
                vert.setDistance(currentVert.getDistance()+1)
                vert.setColor(‘gray‘)
                vert.setPred(currentVert)
                vertQueue.enqueue(vert)
        currentVert.setColor(‘black‘)

def traverse(y):
    x = y
    while (x!=None):
        print x.getId()
        x = x.getPred()
if __name__ ==‘__main__‘:
    g = buildGraph()
    start = g.getVertex(‘FOOL‘)
    bfs(g,start)
    end =g.getVertex(‘SAGE‘)
    traverse(end)

      另外,上述代码中宽度优先搜索的复杂度为O(V+E),V为顶点的数量,E为边的数量

  3.2 Knight‘s tour problem (骑士跳马棋游历问题)

       knight‘s tour问题描述:在国际象棋棋盘上(8*8方格),骑士只能走“日字”路线(和象棋中马一样),骑士如何才能不重复的走遍整个棋盘?

       解决思路:

            1,创建图:将棋盘中的每个方格编号,以方格做为顶点,建立图。对于每个顶点n,骑士若能从n跳到下一个顶点m,则n和m间建立边。

            2,对图进行深度优先搜索(depth first search, DFS)

       创建图:如左图的5*5棋盘,编号0-24,骑士现在所在位置为12,右图中构建了顶点12所有的边(从12出发,骑士下一步能到达的顶点编号)

           

        用python实现创建图的代码如下:

#coding:utf-8

from graphDemo import Graph
def knightGraph(boardSize):
    g = Graph()
    for row in range(boardSize):
        for col in range(boardSize):
            nodeID = posToNodeId(row,col,boardSize)  #对棋盘每个方格编号
            nextMoves= genLegalMoves(row,col,boardSize) #获取骑士下一步能到达的顶点
            for pos in nextMoves:
                g.addEdge(nodeID,pos)
    return g

def posToNodeId(row,col,size):
    return row*size+col

def genLegalMoves(row,col,size):
    nextMoves=[]
    moveOffset = [(-1,2),(1,2),(-2,1),(2,1),(-2,-1),(2,-1),(-1,-2),(1,-2)]
    for i in moveOffset:
        x = row+i[0]
        y = col+i[1]
        if legalCoord(x,size) and legalCoord(y,size):  #判断该坐标是否在棋盘上
            nodeId = posToNodeId(x,y,size)
            nextMoves.append(nodeId)
    return nextMoves

def legalCoord(x,size):
    if x>=0 and x<size:
        return True
    else:
        return False

if __name__ == ‘__main__‘:
    g = knightGraph(5)
    print g.vertList[0].id
    for vert in g.vertList[0].getConnections():
        print vert.id

        深度优先搜索:先找下一级的顶点,再找同级的顶点。此处的问题中,总共有64个节点,若骑士能够依次不重复的访问64个节点,便找到了一条成功遍历的路径,实现代码如下:

def knightTour(n, path, u, limit):
    ‘‘‘
    :param n: 一遍历顶点数,初始为0
    :param path: 遍历的路径
    :param u: 当前顶点
    :param limit:限制访问顶点数
    :return:
    ‘‘‘
    u.setColor(‘gray‘)
    path.append(u)
    if n < limit:
        nextVerts = list(u.getConnections())
        i = 0
        done = False
        while i < len(nextVerts) and not done:
            if nextVerts[i].getColor() == ‘white‘:
                done = knightTour(n + 1, path, nextVerts[i], limit)
            i = i + 1
        if not done:
            path.pop()
            u.setColor(‘white‘)
    else:
        done = True
    return done

上述代码中的深度遍历中,复杂发为O(kN),K为常数,N为顶点个数,随着顶点数增加,算法复杂度指数级增长,对于5*5的方格能较快完成,而对于8*8的方格,得几小时才能完成算法。可以对深度优先算法进行轻微的改进,对于棋盘上所有顶点,其边的数量分布如下图所示,可以发现,棋盘边缘的顶点边数较少,棋盘中央的顶点边数多,若先访问棋盘边缘的顶点,再访问棋盘中央的顶点,能降低算法复杂度,相应代码如下:

def knightTour(n, path, u, limit):
    ‘‘‘
    :param n: 一遍历顶点数,初始为0
    :param path: 遍历的路径
    :param u: 当前顶点
    :param limit:限制访问顶点数
    :return:
    ‘‘‘
    u.setColor(‘gray‘)
    path.append(u)
    if n < limit:
        # nextVerts = list(u.getConnections())
        nextVerts = orderByAvail(u)
        i = 0
        done = False
        while i < len(nextVerts) and not done:
            if nextVerts[i].getColor() == ‘white‘:
                done = knightTour(n + 1, path, nextVerts[i], limit)
            i = i + 1
        if not done:
            path.pop()
            u.setColor(‘white‘)
    else:
        done = True
    return done

def orderByAvail(n):
    resList = []
    for v in n.getConnections():
        if v.getColor() == ‘white‘:
            c = 0
            for w in v.getConnections():
                if w.getColor() == ‘white‘:
                    c = c + 1
            resList.append((c,v))
    resList.sort(key=lambda x: x[0])
    return [y[1] for y in resList]

      上述骑士游历问题中的深度优先搜索算法是一种特殊的深度优先搜索,对于普通的问题,深度优先搜索的代码如下:

#coding:utf-8

from graphDemo import Graph

class DFSGraph(Graph):
    def __init__(self):
        super(DFSGraph,self).__init__()
        self.time = 0

    def dfs(self):
        for vert in self:
            self.dfsVisit(vert)

    def dfsVisit(self,startVert):
        startVert.setColor(‘gray‘)
        self.time += 1                  #注意self.time是DFSGraph的属性,不是Vertex的
        startVert.setDiscovery(self.time)
        for vert in startVert.getConnections():
            if vert.getColor()==‘white‘:
                vert.setPred(startVert)
                self.dfsVisit(vert)
        startVert.setColor(‘black‘)
        self.time += 1
        startVert.setFinish(self.time)

def traverse(y):
    x=y
    while x!=None:
        print x.getId()
        x = x.getPred()
if __name__ == ‘__main__‘:
    g = DFSGraph()
    for i in range(6):
        g.addVertex(i)
    g.addEdge(0, 1, 5)
    g.addEdge(0, 5, 2)
    g.addEdge(1, 2, 4)
    g.addEdge(2, 3, 9)
    g.addEdge(3, 4, 7)
    g.addEdge(3, 5, 3)
    g.addEdge(4, 0, 1)
    g.addEdge(5, 4, 8)
    g.addEdge(5, 2, 1)
    g.dfs()
    traverse(g.getVertex(1))

      上述DFS的复杂度和BFS一样,也为O(V+E)

4.拓扑排序(Topological sorting)

    拓扑排序适用于有向无环图(图中不存在回路),表示各个事件(顶点)的线性执行顺序,如图中若存在边(v,w),则事件v在w之前发生。

    拓扑排序的执行过程如下:

      1,先对图进行深度优先搜索,计算每个顶点的finish time

      2,根据finish time,对顶点进行降序排列,并存储在一个列表中,返回列表即为拓扑排序结果

    如下图是一张表示煎饼过程的有向无环图,箭头表示执行先后顺序:

    若要知道整个煎饼过程的执行流程,可以对上面的图进行拓扑排序,计算的时间(discovery time/ finish time)和最后得到执行流程如下,即越晚结束的事件(finish time越大),应先执行。

5.最短路径算法

  5.1迪杰斯特拉算法(Dijkstra‘s Algorithm)

     Dijkstra算法:寻找最短路径算法之一,即在边的权值不为负的有向图中,寻找任意一点到其他任意结点(在两点相互联通的情况下)之间的最小路径。

     python实现代码如下:

#coding:utf-8

from graphDemo import Graph
from binaryHeap import BinaryHeap
import sys

class PriorityQueue(BinaryHeap):
    def decreaseKey(self,item,cost):
        i=1
        done=False
        while i<=self.size and not done:
            if self.heapList[i][1]==item:  #先找到该元素,再将元素向上移
                self.heapList[i]=(cost,item)
                self._percUp(i)
                done = True
            i = i+1

def dijkstra(aGraph,start):
    for vert in aGraph:
        vert.setDistance(sys.maxint)
    start.setDistance(0)
    q = PriorityQueue()
    q.buildHeap([(v.getDistance(),v) for v in aGraph])  #元组做为最小堆(优先队列)的元素
    while q.size>0:                                     #元组进行比较时,先比较第一个元素,所以距离做为元素第一个值,距离相等时算法有问题?
        currentVert = q.delMin()[1]
        for nextVert in currentVert.getConnections():
            newDistance = currentVert.getDistance()+currentVert.getWeight(nextVert)
            if newDistance<nextVert.getDistance():
                nextVert.setDistance(newDistance)
                nextVert.setPred(currentVert)
                q.decreaseKey(nextVert,newDistance)

if __name__ == ‘__main__‘:
    g = Graph()
    for i in range(6):
        g.addVertex(i)
    g.addEdge(0, 1, 5)
    g.addEdge(0, 5, 2)
    g.addEdge(1, 2, 4)
    g.addEdge(2, 3, 9)
    g.addEdge(3, 4, 7)
    g.addEdge(3, 5, 3)
    g.addEdge(4, 0, 1)
    g.addEdge(5, 4, 8)
    g.addEdge(5, 2, 1)
    dijkstra(g, g.getVertex(5))
    for vert in g:
        print vert.getId(),vert.getDistance()

     Dijkstra算法的复杂度为O((V+E)*log V):delMin()复杂度为O(log V),共V个元素,因此复杂度为O(V*log V);decreaseKey()复杂度为O(log V),共E条边,复杂度为O(E*log V),两者相加O((V+E)*log V)。

  5.2 普里姆算法(Prim‘s Algorithm)

     Prim‘s Algorithm:又叫最小生成树算法(Minimum spanning tree algorithm)

      (步骤:在带权连通图g=(V,E),从图中某一顶点a开始,此时集合U={a},重复执行下述操作:在所有u∈U,w∈V-U的边(u,w)∈E中找到一条权值最小的边,将(u,w)这条边加入到已找到边的集合,并且将点w加入到集合U中,当U=V时,就找到了这颗最小生成树。)

     python实现代码如下:

#coding:utf-8

from graphDemo import Graph
from binaryHeap import BinaryHeap
import sys

class PriorityQueue(BinaryHeap):
    def decreaseKey(self,item,cost):
        i=1
        done=False
        while i<=self.size and not done:
            if self.heapList[i][1]==item:  #先找到该元素,再将元素向上移
                self.heapList[i]=(cost,item)
                self._percUp(i)
                done = True
            i = i+1
    def __contains__(self, item):
        i=1
        found = False
        while i <= self.size and not found:
            if self.heapList[i][1] == item:
                found=True
            i = i+1
        return found

def prim(aGraph,start):
    for vert in aGraph:
        vert.setDistance(sys.maxint)
    q = PriorityQueue()
    start.setDistance(0)
    q.buildHeap([(v.getDistance(), v) for v in aGraph])
    while q.size>0:
        currentVert = q.delMin()[1]
        for nextVert in currentVert.getConnections():
            newCost = currentVert.getWeight(nextVert)
            # 每次从队列中,挑取边cost最小的点
            if nextVert in q and newCost < nextVert.getDistance():
                nextVert.setDistance(newCost)
                nextVert.setPred(currentVert)
                q.decreaseKey(nextVert, newCost)

if __name__ == ‘__main__‘:
    g = Graph()
    for i in range(6):
        g.addVertex(i)
    g.addEdge(0, 1, 5)
    g.addEdge(0, 5, 2)
    g.addEdge(1, 2, 4)
    g.addEdge(2, 3, 9)
    g.addEdge(3, 4, 7)
    g.addEdge(3, 5, 3)
    g.addEdge(4, 0, 1)
    g.addEdge(5, 4, 8)
    g.addEdge(5, 2, 1)

    prim(g, g.getVertex(5))
    for vert in g:
        print vert.getId(), vert.getPred()

参考:http://interactivepython.org/runestone/static/pythonds/Graphs/toctree.html

原文地址:https://www.cnblogs.com/silence-cho/p/10089065.html

时间: 2024-08-14 16:18:30

图及其衍生算法(Graphs and graph algorithms)的相关文章

zw&#183;准专利&#183;高保真二值图细部切分算法

zw·准专利·高保真二值图细部切分算法     高保真二值图细部切分算法,是中国字体协会项目的衍生作品.     说准专利算法,是因为对于图像算法的标准不了解,虽然报过专利,但不是这方面的,需要咨询专业的专利顾问.     原型是用opencv+python实现的,因为Halcon,对于协会的设计师,门槛太高,所以,特意设计了一套opencv+python的live-cd,解压即可,无需配置. 中国传统书法,有很多飞白.泼墨的手法,产生了很多小孔.孤点,从图像学角度,这些都是细小的感染区. 传统

实验四 图的遍历算法设计与实现

一.实验名称:图的遍历算法设计与实现 二.实验目的: 1.掌握图的深度优先遍历的算法. 2.掌握图的广度优先遍历的算法. 3.实验章节:算法设计与分析 第四章 三.实验内容.实验问题和程序运行结果 第一部分 广度优先遍历算法 1. 分析Graph类,画出Graph类初始化以后的Graph对象的数据结构图. 2. 分析BFS函数,画出流程图. 3. 上述程序   int data[7][7]={{ 1,-1,-1,-1,-1,-1,-1}, { 6, 3, 2,-1,-1,-1,-1}, { 0,

数据结构与算法系列研究七——图、prim算法、dijkstra算法

图.prim算法.dijkstra算法 1. 图的定义 图(Graph)可以简单表示为G=<V, E>,其中V称为顶点(vertex)集合,E称为边(edge)集合.图论中的图(graph)表示的是顶点之间的邻接关系. (1) 无向图(undirect graph)      E中的每条边不带方向,称为无向图.(2) 有向图(direct graph)      E中的每条边具有方向,称为有向图.(3) 混合图       E中的一些边不带方向, 另一些边带有方向.(4) 图的阶      指

图的割点算法、图的割边算法

割点算法 • 在一个无向连通图中,如果删除某个顶点后,图不再连通(即任意两点之间不能相互到达),我们称这样的顶点为割点(或者称割顶). 判断一个顶点是不是割点除了从定义,还可以从DFS(深度优先遍历)的角度出发.我们先通过DFS定义两个概念. 假设DFS中我们从顶点U访问到了顶点V(此时顶点V还未被访问过),那么我们称顶点U为顶点V的父顶点,V为U的孩子顶点.在顶点U之前被访问过的顶点,我们就称之为U的祖先顶点. 显然如果顶点U的所有孩子顶点可以不通过父顶点U而访问到U的祖先顶点,那么说明此时去

ppt 图的基本算法 Bfs

输入: 8 91 21 32 42 53 63 74 85 86 7 // 图的BFS,使用C++队列#include <stdio.h>#include <string.h>#include <queue>using namespace std;#define N 10int g[N][N],bz[N],n,m;queue <int> q;void BFS(int cur){ int j; bz[cur]=1; q.push(cur); while (!q

ppt 图的基本算法 dfs

#include <stdio.h>#include<string.h>#define N 10int g[N][N];int bz[N];int n,m ;void DFS(int cur){ int j; bz[cur]=1; printf("V%d",cur); for(j=1;j<=n ;j++ ) if(g[cur][j] && !bz[j]) DFS(j); } void input(){ int i,j,f,t ; scanf

图形化排序算法比较:快速排序、插入排序、选择排序、冒泡排序

图形化排序算法比较:快速排序.插入排序.选择排序.冒泡排序

带权图的最短路径算法(Dijkstra)实现

一,介绍 本文实现带权图的最短路径算法.给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度.在决定写这篇文章之前,在网上找了很多关于Dijkstra算法实现,但大部分是不带权的.不带权的Dijkstra算法要简单得多(可参考我的另一篇:无向图的最短路径算法JAVA实现):而对于带权的Dijkstra算法,最关键的是如何“更新邻接点的权值”.本文采用最小堆主辅助,以重新构造堆的方式实现更新邻接点权值. 对于图而言,存在有向图和无向图.本算法只需要修改一行代码,即可同时实

图的遍历算法:DFS、BFS

在图的基本算法中,最初需要接触的就是图的遍历算法,根据访问节点的顺序,可分为深度优先搜索(DFS)和广度优先搜索(BFS). DFS(深度优先搜索)算法 Depth-First-Search 深度优先算法,是一种用于遍历或搜索树或图的算法.沿着树的深度遍历树的节点,尽可能深的搜索树的分支. 当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点. 这一过程一直进行到已发现从源节点可达的所有节点为止. 如果还存在未被发现的节点, 则选择其中一个作为源节点并重复以上过程,整个进程反复