DATA STRUCTURE

python数据结构教程 Day16

2020-08-03  本文已影响0人  XaviSong

本章内容

  1. 通用深度优先DFS算法
  2. 单源最短路径问题
  3. 最小生成树

一、通用深度优先DFS算法

一般的深度优先搜索目标是在图上进行尽量深的搜索,连接尽量多的顶点,必要时可以进行分支(创建了树) 有时候深度优先搜索会创建多棵树,称为“深度优先森林”。深度优先搜索同样要用到顶点的“前驱” 属性,来构建树或森林,另外要设置“发现时间”和“结束时间”属性。

与BFS的区别在于,在添加兄弟节点之前,先添加层次

代码实现:
class DFSGraph(Graph):
    def __init__(self):
        super().__init__()
        self.time = 0

    def dfs(self):
        '''
        可能会生成多个深度优先搜索森林
        '''
        #颜色初始化
        for aVertex in self:#父类已经实现了iter()方法
            aVertex.setColor('White')
            aVertex.setPred(-1)
        #如果还有未包括的顶点,则建森林
        for aVertex in self:
            if aVertex.getColor() == 'White':
                self.dfsvisit(aVertex)

    def dfsvisit(self, rootVertex):
        '''
        不同于BTS中的队列,这里的递归隐式使用了栈
        '''
        rootVertex.setColor('Gray')
        self.time += 1
        rootVertex.setDiscovery(self.time)
        for nextVertex in rootVertex.getconnections():
            if nextVertex.getColor == 'White':
                nextVertex.setPred(rootVertex)
                self.dfsvisit(nextVertex)
        rootVertex.setColor('Black')
        self.time += 1
        rootVertex.setFinish(self.time)

即一个顶点的“发现时间”总小于所有子顶点的“发现时间” 而“结束时间”则大于所有子顶点“结束时间” 比子顶点更早被发现,更晚被结束探索,类比括号和子括号的关系。

DFS算法分析:

dfs函数中有两个循环,每个都是|V|次,所以 是O(|V|)。而dfsvisit函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一 步,所以是O(|E|) 加起来就是和BFS一样的O(|V|+|E|)。

DFS算法的应用:
1、拓扑排序

拓扑排序处理一个DAG,输出顶点的线性序列,使得两个顶点v,w,如果G中有(v,w)边,在线性序列中v就出现在w之前。

在如上所示的DFS之中,进行拓扑排序非常方便按照vertex中finish从大到小的顺序,即可快速完成拓扑排序。

2、强连通分支
定义:

图G的一个子集C,C中的任意两个顶点v,w之间都有路径来回,即 (v,w)(w,v)都是C的路径, 而且C是具有这样性质的最大子集。

在图中发现高度聚集节点群的算法,即寻找“强连通分支Strongly Connected Components”算法。一旦找到强连通分支,可以据此对图的顶点进行分类,并对图进行化简。

示例:
先熟悉一个概念:Transposition转置

一个有向图G的转置GT,定义为将图G的所有边的顶点交换次序,如将(v,w)转换为(w,v) 可以观察到图和转置图在强连通分支的数量和划分上,是相同的。

Kosaraju算法:
对G进行DFS,获得每个vertex的finish
G进行转置得到GT
对GT使用DFS(注意在每个顶点的搜索循环中,要以顶点的结束时间倒序顺序进行搜索)得到深度优先森林,那么其中的每一棵树都是一个强连通分支
示例:

第一趟:

第二趟:

结果:

二、单源最短路径问题

目标:获得从一个节点出发到达图中任意顶点的最短距离。

解决带权图的最短路径问题,与使用BFS解决词梯问题非常相似,只不过边中带了权重。这里使用Dijkstra算法,迭代得出一个顶点到所有顶点的最短路径。实现上在Vertex类中添加dist来存储起点到本结点的最短权重之和,顶点的访问次序由优先队列控制。队列中作为优先级的是dist,具有最小dist的结点出队,计算其它结点的权重和,引起堆的重排,随后根据更新dist优先级再依次出队。注意Dijkstra只能处理权值为正的带权图。

代码实现:
def dijkstra(graph, start):
    #对所有顶点建堆形成优先队列
    pq = PriorityQueue()
    start.setDistance(0)
    pq.buildHeap([(v.getDistance(), v) for v in graph])
    while not pq.empty():
        #优先队列出队
        currentVert = pq.delmin()
        #修改邻接顶点dist,并逐个重排队列
        for nextVertex in currentVert.getconnections():
            newDist = currentVert.getDistance() + currentVert.getweight(nextVertex)
            if newDist < nextVertex.getDistance():
                nextVertex.setDistance(newDist)
                nextVertex.setPred(currentVert)
                pq.decreaseKey(nextVertex, newDist)

Dijkstra算法需要具备整个图的数据,涉及巨大数据量的问题,同时动态变化的数据特性也使得保存全图缺乏现实性。

Dijkstra算法分析:

首先,将所有顶点加入优先队列并建堆,时间复杂度为O(|V|) 。其次,每个顶点仅出队1次,每次delMin 花费O(log|V|),一共就是O(|V|log|V|) 。另外,每个边关联到的顶点会做一次 decreaseKey操作(O(log|V|)),一共是O(|E|log|V|) 。上面三个加在一起,数量级就是O((|V|+|E|)log|V|)。

三、最小生成树

生成树:

拥有图中所有的顶点和最少数量的边, 以保持连通的子图。

图G(V,E)的最小生成树,指包含所有节点v,以及E的无圈子集,并且边的权重之和最小。

解决:

最小生成树问题的Prim算法,属于 “贪心算法” ,即每步都沿着最小权重的边向前搜索。

思路:
if T还不是生成树:
    则反复做:
        找到一条最小权重的可以安全添加的边
        将边添加到树T

“可以安全添加”的边:

定义为一端顶点在树中,另一端不在树中的边,以便保持树的无圈特性。

代码实现:
def prim(G,start):
    pq = PriorityQueue()
    # 节点初始化
    for v in G:
        v.setDistance(sys.maxsize)
        v.setPred(None)
    start.setDistance(0)
    pq.buildHeap([(v.getDistance(), v) for v in G])
    while not pq.empty():
        currentVert = pq.delmin()
        # distsance仅代表从current到next的距离,根据distance进行重排
        for nextVert in currentVert.getconnections():
            newCost = currentVert.getweight(nextVert)
            if nextVert in pq and newCost < nextVert.getDistance():
                nextVert.setPred(currentVert)
                nextVert.setDistance(newCost)
                pq.decreasekey(nextVert, newCost)
上一篇下一篇

猜你喜欢

热点阅读