图之DFS、BFS、Dijkstra、Floyd、Prim、Kruskal算法

概要

对于DFS和BFS,如果遇到搜索和遍历,肯定要想到堆栈和队列,而遇到堆栈肯定就要想到是不是可以用递归来实现,因为递归程序其实就是函数在内存中的出栈入栈,DFS就是使用堆栈或者递归来实现,而类似层次遍历的BFS自然就可以使用队列来实现

DFS和BFS

在这里插入图片描述

graph = {
        'a' : ['b', 'c'],
        'b' : ['a', 'c', 'd'],
        'c' : ['a','b', 'd','e'],
        'd' : ['b' , 'c', 'e', 'f'],
        'e' : ['c', 'd'],
        'f' : ['d']
        }
def dfs(graph, s):
    stack = []
    stack.append(s)
    visited = set()
    visited.add(s)
    while len(stack) > 0:
        vertex = stack.pop()
        nodes = graph[vertex]
        for node in nodes:
            if node not in visited:
                stack.append(node)
                visited.add(node)
        print(vertex)
dfs(graph, 'a')
## 运行结果
a
c
e
d
f
b
def bfs(graph, s):
    queue = []
    queue.append(s)
    visited = set()
    visited.add(s)
    while len(queue) > 0:
        vertex = queue.pop(0)
        nodes = graph[vertex]
        for node in nodes:
            if node not in visited:
                queue.append(node)
                visited.add(node)
        print(vertex)
bfs(graph, 'a')
## 运行结果
a
b
c
d
e
f

最短路径算法:Dijkstra、Floyd算法

在这里插入图片描述

inf = float('inf')
matrix_distance = [[0,1,12,inf,inf,inf],
                   [inf,0,9,3,inf,inf],
                   [inf,inf,0,inf,5,inf],
                   [inf,inf,4,0,13,15],
                   [inf,inf,inf,inf,0,4],
                   [inf,inf,inf,inf,inf,0]]

Dijkstra算法

Dijkstra算法是求从某个源点到其余各个顶点的最短路径(单源最短路径),时间复杂度为 O(n^2) ,主要思想为每次在未确定的顶点中选取最短的路径,并把最短路径的顶点设为确定值,然后再由源点经该点出发来松弛其他顶点的路径的值,重复以上步骤最后得到就是最短路径了。

def dijkstra(matrix_dist, src_node):
    inf = float('inf')
    # init the source node distance to others
    dists = matrix_dist[src_node]
    node_nums = len(dists)
    flag = [0] * node_nums
    flag[src_node] = 1
    for i in range(node_nums - 1):
        mini = inf
        # find the min node from the source node
        for j in range(node_nums):
            if flag[j] == 0 and dists[j] < mini:
                mini = dists[j]
                u = j
        flag[u] = 1
		# update the dis 
        for v in range(node_nums):
            if flag[v] == 0 and matrix_dist[u][v] < inf:
                if dists[v] > dists[u] + matrix_dist[u][v]:
                    dists[v] = dists[u] + matrix_dist[u][v]
    return dists
print(dijkstra(matrix_distance, 0))
## 运行结果
[0, 1, 8, 4, 13, 17]

Floyd算法

Floyd算法针对的问题是求每对顶点之间的最短路径,相当于把Dijkstra算法执行了n遍(实际上并不是这样做),所以Floyd算法的时间复杂度为 O(n^3),主要用公式:
在这里插入图片描述
来不断优化带权邻接矩阵,最后得到矩阵就是每对顶点之间的最短距离了。

def floyd(dis):
    # min (Dis(i,j) , Dis(i,k) + Dis(k,j) )
    num_vertex = len(dis[0])
    for k in range(num_vertex):
        for i in range(num_vertex):
            for j in range(num_vertex):
                if dis[i][j] > dis[i][k] + dis[k][j]:
                    dis[i][j] = dis[i][k] + dis[k][j]
    return dis
print(floyd(matrix_distance))

最小代价生成树:Prim、Kruskal算法

Prim算法

Prim算法是从任意一个顶点开始,每次选择一个与当前顶点集最近的一个顶点,并将两顶点之间的边加入到树中,其实就是说在当前顶点集所可以辐射到的边中选择最小的一条边(需要判断该边是否已经在最小生成树中),其实就是一个排序问题,然后贪心选取最小值。
Prim算法的时间复杂度为O(n^2) ,n表示顶点数目,这跟它的初心还是蛮符合的,毕竟它是从顶点出发,可以从公式中看出Prim算法的时间复杂度与网络中的边无关,所以适合来求解边稠密的网的最小代价生成树.

from collections import defaultdict
from heapq import *
    
def Prim(vertexs, edges, start_node):
    adjacent_vertex = defaultdict(list)
    for v1, v2, length in edges:
        adjacent_vertex[v1].append((length, v1, v2))
        adjacent_vertex[v2].append((length, v2, v1))
        
    mst = []
    closed = set(start_node)
    
    adjacent_vertexs_edges = adjacent_vertex[start_node]
    heapify(adjacent_vertexs_edges)

    while adjacent_vertexs_edges:
        w, v1, v2 = heappop(adjacent_vertexs_edges)
        if v2 not in closed:
            closed.add(v2)
            mst.append((v1, v2, w))
            
            for next_vertex in adjacent_vertex[v2]:
                if next_vertex[2] not in closed:
                    heappush(adjacent_vertexs_edges, next_vertex)
                    
    return mst
    
    
vertexs = list("ABCDEFG")
edges = [ ("A", "B", 7), ("A", "D", 5),
          ("B", "C", 8), ("B", "D", 9), 
          ("B", "E", 7), ("C", "E", 5),
          ("D", "E", 15), ("D", "F", 6),
          ("E", "F", 8), ("E", "G", 9),
          ("F", "G", 11)]

print('prim:', Prim(vertexs, edges, 'A'))

Kruskal算法

Kruskal算法选择从边开始,把所有的边按照权值先从小到大排列,接着按照顺序选取每条边(贪心思想),如果这条边的两个端点不属于同一集合,那么就将它们合并,直到所有的点都属于同一个集合为止,其实就是基于并查集的贪心算法。
Kruskal算适合来求边稀疏的网的最小代价生成树时间复杂度为 O(eloge),e表示网络中的边数。

node = dict()
rank = dict()

def make_set(point):
    node[point] = point
    rank[point] = 0
    
def find(point):
    if node[point] != point:
        node[point] = find(node[point])
    return node[point]

def merge(point1, point2):
    root1 = find(point1)
    root2 = find(point2)
    if root1 != root2:
        if rank[root1] > rank[root2]:
            node[root2] = root1
        else:
            node[root1] = root2
            if rank[root1] == rank[root2] : rank[root2] += 1
            
            
def Kruskal(graph):
    for vertice in graph['vertices']:
        make_set(vertice)
    
    mst = set()
    
    edges = list(graph['edges'])
    edges.sort()
    for edge in edges:
        weight, v1, v2 = edge
        if find(v1) != find(v2):
            merge(v1 , v2)
            mst.add(edge)
    return mst

graph = {
    'vertices': ['A', 'B', 'C', 'D'],
    'edges': set([
        (1, 'A', 'B'),
        (5, 'A', 'C'),
        (3, 'A', 'D'),
        (4, 'B', 'C'),
        (2, 'B', 'D'),
        (1, 'C', 'D'),
        ])
    }

print(Kruskal(graph))
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页