Datawhale编程学习之图(6)


任务6:10~12天

1. 学习目标

1.1 图

实现有向图、无向图、有权图、无权图的邻接矩阵和邻接表表示方法
实现图的深度优先搜索、广度优先搜索
实现 Dijkstra 算法、A* 算法
实现拓扑排序的 Kahn 算法、DFS 算法

1.2 对应的 LeetCode 练习题

Number of Islands(岛屿的个数)
英文版:Loading…
中文版:力扣

Valid Sudoku(有效的数独)
英文版:Loading…
中文版:力扣

2. 学习内容

2.1 图

实现有向图、无向图、有权图、无权图的邻接矩阵和邻接表表示方法

class Graph_Matrix:
    """
    Adjacency Matrix
    """
    def __init__(self, vertices=[], matrix=[]):
        """

        :param vertices:a dict with vertex id and index of matrix , such as {vertex:index}
        :param matrix: a matrix
        """
        self.matrix = matrix
        self.edges_dict = {}  # {(tail, head):weight}
        self.edges_array = []  # (tail, head, weight)
        self.vertices = vertices
        self.num_edges = 0

        # if provide adjacency matrix then create the edges list
        if len(matrix) > 0:
            if len(vertices) != len(matrix):
                raise IndexError
            self.edges = self.getAllEdges()
            self.num_edges = len(self.edges)

        # if do not provide a adjacency matrix, but provide the vertices list, build a matrix with 0
        elif len(vertices) > 0:
            self.matrix = [[0 for col in range(len(vertices))] for row in range(len(vertices))]

        self.num_vertices = len(self.matrix)

    def isOutRange(self, x):
        try:
            if x >= self.num_vertices or x <= 0:
                raise IndexError
        except IndexError:
            print("节点下标出界")

    def isEmpty(self):
        if self.num_vertices == 0:
            self.num_vertices = len(self.matrix)
        return self.num_vertices == 0

    def add_vertex(self, key):
        if key not in self.vertices:
            self.vertices[key] = len(self.vertices) + 1

        # add a vertex mean add a row and a column
        # add a column for every row
        for i in range(self.getVerticesNumbers()):
            self.matrix[i].append(0)

        self.num_vertices += 1

        nRow = [0] * self.num_vertices
        self.matrix.append(nRow)

    def getVertex(self, key):
        pass

    def add_edges_from_list(self, edges_list):  # edges_list : [(tail, head, weight),()]
        for i in range(len(edges_list)):
            self.add_edge(edges_list[i][0], edges_list[i][1], edges_list[i][2], )

    def add_edge(self, tail, head, cost=0):
        # if self.vertices.index(tail) >= 0:
        #   self.addVertex(tail)
        if tail not in self.vertices:
            self.add_vertex(tail)
        # if self.vertices.index(head) >= 0:
        #   self.addVertex(head)
        if head not in self.vertices:
            self.add_vertex(head)

        # for directory matrix
        self.matrix[self.vertices.index(tail)][self.vertices.index(head)] = cost
        # for non-directory matrix
        # self.matrix[self.vertices.index(fromV)][self.vertices.index(toV)] = \
        #   self.matrix[self.vertices.index(toV)][self.vertices.index(fromV)] = cost

        self.edges_dict[(tail, head)] = cost
        self.edges_array.append((tail, head, cost))
        self.num_edges = len(self.edges_dict)

    def getEdges(self, V):
        pass

    def getVerticesNumbers(self):
        if self.num_vertices == 0:
            self.num_vertices = len(self.matrix)
        return self.num_vertices

    def getAllVertices(self):
        return self.vertices

    def getAllEdges(self):
        for i in range(len(self.matrix)):
            for j in range(len(self.matrix)):
                if 0 < self.matrix[i][j] < float('inf'):
                    self.edges_dict[self.vertices[i], self.vertices[j]] = self.matrix[i][j]
                    self.edges_array.append([self.vertices[i], self.vertices[j], self.matrix[i][j]])

        return self.edges_array

    def __repr__(self):
        return str(''.join(str(i) for i in self.matrix))

    def to_do_vertex(self, i):
        print('vertex: %s' % (self.vertices[i]))

    def to_do_edge(self, w, k):
        print('edge tail: %s, edge head: %s, weight: %s' % (self.vertices[w], self.vertices[k], str(self.matrix[w][k])))
def create_undirected_matrix(my_graph):
	nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

	matrix = [[0, 1, 1, 1, 1, 1, 0, 0],  # a
			  [0, 0, 1, 0, 1, 0, 0, 0],  # b
			  [0, 0, 0, 1, 0, 0, 0, 0],  # c
			  [0, 0, 0, 0, 1, 0, 0, 0],  # d
			  [0, 0, 0, 0, 0, 1, 0, 0],  # e
			  [0, 0, 1, 0, 0, 0, 1, 1],  # f
			  [0, 0, 0, 0, 0, 1, 0, 1],  # g
			  [0, 0, 0, 0, 0, 1, 1, 0]]  # h

	my_graph = Graph_Matrix(nodes, matrix)
	print(my_graph)
	return my_graph

def create_directed_matrix(my_graph):
	nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
	inf = float('inf')
	matrix = [[0, 2, 1, 3, 9, 4, inf, inf],  # a
			  [inf, 0, 4, inf, 3, inf, inf, inf],  # b
			  [inf, inf, 0, 8, inf, inf, inf, inf],  # c
			  [inf, inf, inf, 0, 7, inf, inf, inf],  # d
			  [inf, inf, inf, inf, 0, 5, inf, inf],  # e
			  [inf, inf, 2, inf, inf, 0, 2, 2],  # f
			  [inf, inf, inf, inf, inf, 1, 0, 6],  # g
			  [inf, inf, inf, inf, inf, 9, 8, 0]]  # h

	my_graph = Graph_Matrix(nodes, matrix)
	print(my_graph)
	return my_graph

def create_directed_graph_from_edges(my_graph):
	nodes = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
	edge_list = [('A', 'F', 9), ('A', 'B', 10), ('A', 'G', 15), ('B', 'F', 2),
				 ('G', 'F', 3), ('G', 'E', 12), ('G', 'C', 10), ('C', 'E', 1),
				 ('E', 'D', 7)]

	my_graph = Graph_Matrix(nodes)
	my_graph.add_edges_from_list(edge_list)
	print(my_graph)

	# my_graph.DepthFirstSearch()
	#
	# draw_directed_graph(my_graph)

	return my_graph



def draw_undircted_graph(my_graph):
	G = nx.Graph()  # 建立一个空的无向图G
	for node in my_graph.vertices:
		G.add_node(str(node))
	for edge in my_graph.edges:
		G.add_edge(str(edge[0]), str(edge[1]))

	print("nodes:", G.nodes())  # 输出全部的节点
	print("edges:", G.edges())  # 输出全部的边
	print("number of edges:", G.number_of_edges())  # 输出边的数量
	nx.draw(G, with_labels=True)
	plt.savefig("undirected_graph.png")
	plt.show()


def draw_directed_graph(my_graph):
	G = nx.DiGraph()  # 建立一个空的无向图G
	for node in my_graph.vertices:
		G.add_node(str(node))
	# for edge in my_graph.edges:
	# G.add_edge(str(edge[0]), str(edge[1]))
	G.add_weighted_edges_from(my_graph.edges_array)

	print("nodes:", G.nodes())  # 输出全部的节点
	print("edges:", G.edges())  # 输出全部的边
	print("number of edges:", G.number_of_edges())  # 输出边的数量
	nx.draw(G, with_labels=True)
	plt.savefig("directed_graph.png")
	plt.show()


if __name__ == '__main__':
	my_graph = Graph_Matrix()
	# created_graph = create_undirected_matrix(my_graph)
	created_graph = create_directed_matrix(my_graph)
	# created_graph = create_directed_graph_from_edges(my_graph)

	# draw_undircted_graph(created_graph)
	draw_directed_graph(created_graph)

实现图的深度优先搜索、广度优先搜索
深度优先算法:
访问初始顶点v并标记顶点v已访问。
查找顶点v的第一个邻接顶点w。
若顶点v的邻接顶点w存在,则继续执行;否则回溯到v,再找v的另外一个未访问过的邻接点。
若顶点w尚未被访问,则访问顶点w并标记顶点w为已访问。
继续查找顶点w的下一个邻接顶点wi,如果v取值wi转到步骤(3)。直到连通图中所有顶点全部访问过为止。
广度优先算法:
顶点v入队列。
当队列非空时则继续执行,否则算法结束。
出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
查找顶点v的第一个邻接顶点col。
若v的邻接顶点col未被访问过的,则col入队列。
继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。

# -*- coding:utf-8 -*-\
# *args 把参数打包成tuple供函数调用。**kwargs把 x = a,y=b打包成字典{x:a,y:b}供函数调用
class Graph(object):
    def __init__(self, *args, **kwargs):
        self.node_neighbors = {}
        self.visited = {}
 
    def add_nodes(self, nodelist):
 
        for node in nodelist:
            self.add_node(node)
 
    def add_node(self, node):
        if not node in self.nodes():
            self.node_neighbors[node] = []
 
    def add_edge(self, edge):
        u, v = edge
        if (v not in self.node_neighbors[u]) and (u not in self.node_neighbors[v]):
            self.node_neighbors[u].append(v)
 
            if (u != v):
                self.node_neighbors[v].append(u)
 
    def nodes(self):
        return self.node_neighbors.keys()
 
    #递归DFS
    def depth_first_search(self,root=None):
        order=[]
        def dfs(node):
            self.visited[node] = True
            order.append(node)
            for n in self.node_neighbors[node]:
                if not n in self.visited:
                    dfs(n)
 
        if root:
            dfs(root)
 
        #对于不连通的结点(即dfs(root)完仍是没有visit过的单独处理,再做一次dfs
        for node in self.nodes():
            if not node in self.visited:
                dfs(node)
        self.visited = {}
        print order
        return order
 
    #非递归DFS
    def depth_first_search2(self,root=None):
        stack = []
        order = []
        #self.visited[root] = True
        def dfs():
            while stack:
                node = stack[-1]
                for n in self.node_neighbors[node]:
                    if not n in self.visited:
                        order.append(n)
                        stack.append(n)
                        self.visited[n] = True
                        break
                else:
                    stack.pop()
        if root:
            stack.append(root)
            order.append(root)
            self.visited[root]=True
            dfs()
 
        for node in self.nodes():
            if node not in self.visited:
                stack.append(node)
                order.append(node)
                self.visited[node]=True
                dfs()
 
        self.visited = {}
        print order
        return order
 
    def breadth_first_search(self,root=None):
        queue = []
        order = []
        def bfs():
            while len(queue)>0:
                node = queue.pop(0)
                self.visited[node] = True
                for n in self.node_neighbors[node]:
                    if (not n in self.visited) and (not n in queue):
                        queue.append(n)
                        order.append(n)
 
        if root:
            queue.append(root)
            order.append(root)
            bfs()
 
        for node in self.nodes():
            if not node in self.visited:
                queue.append(node)
                order.append(node)
                bfs()
 
        self.visited = {}
        print order
        return order
 
if __name__ == '__main__':
    g = Graph()
    g.add_nodes([i + 1 for i in range(8)])
    g.add_edge((1, 2))
    g.add_edge((1, 3))
    g.add_edge((2, 4))
    g.add_edge((2, 5))
    g.add_edge((4, 8))
    g.add_edge((5, 8))
    g.add_edge((3, 6))
    g.add_edge((3, 7))
    g.add_edge((6, 7))
    print "nodes:", g.nodes()
 
    print "BFS:"
    order = g.breadth_first_search(1)
    # self.visited 在经历了一次bfs之后已经有了值,如果dfs直接进行,就会发生只输出结点1的情况
    print "递归DFS:"
    order = g.depth_first_search(1)
    print "非递归DFS"
    order = g.depth_first_search2(1)

实现 Dijkstra 算法、A 算法*

import json
def dijkstra(graph,src):
    if graph ==None:
        return None
    # 定点集合
    nodes = [i for i in range(len(graph))] # 获取顶点列表,用邻接矩阵存储图
    # 顶点是否被访问
    visited = []
    visited.append(src)
    # 初始化dis
    dis = {src:0}# 源点到自身的距离为0
    for i in nodes:
        dis[i] = graph[src][i]
    path={src:{src:[]}}  # 记录源节点到每个节点的路径
    k=pre=src

    while nodes:
        temp_k = k
        mid_distance=float('inf') # 设置中间距离无穷大
        for v in visited:
            for d in nodes:
                if graph[src][v] != float('inf') and graph[v][d] != float('inf'):# 有边
                    new_distance = graph[src][v]+graph[v][d]
                    if new_distance <= mid_distance:
                        mid_distance=new_distance
                        graph[src][d]=new_distance  # 进行距离更新
                        k=d
                        pre=v
        if k!=src and temp_k==k:
            break
        dis[k]=mid_distance  # 最短路径
        path[src][k]=[i for i in path[src][pre]]
        path[src][k].append(k)

        visited.append(k)
        nodes.remove(k)
        print(nodes)
    return dis,path

if __name__ == '__main__':
    # 输入的有向图,有边存储的就是边的权值,无边就是float('inf'),顶点到自身就是0
    graph = [ 
        [0, float('inf'), 10, float('inf'), 30, 100],
        [float('inf'), 0, 5, float('inf'), float('inf'), float('inf')],
        [float('inf'), float('inf'), 0, 50, float('inf'), float('inf')],
        [float('inf'), float('inf'), float('inf'), 0, float('inf'), 10],
        [float('inf'), float('inf'), float('inf'), 20, 0, 60],
        [float('inf'), float('inf'), float('inf'), float('inf'), float('inf'), 0]]

    dis,path= dijkstra(graph, 0)  # 查找从源点0开始带其他节点的最短路径
    print(dis)
    print(json.dumps(path, indent=4))

实现拓扑排序的 Kahn 算法、DFS 算法

def topological_sort( graph ):
 
    is_visit = dict( ( node, False ) for node in graph )
    li = []
 
    def dfs( graph, start_node ):
        
        for end_node in graph[start_node]:
            if not is_visit[end_node]:
                is_visit[end_node] = True
                dfs( graph, end_node )
        li.append( start_node )
    
    for start_node in graph:
        if not is_visit[start_node]:
            is_visit[start_node] = True
            dfs( graph, start_node )
 
    li.reverse()
    return li
    
            
if __name__ == '__main__':
    graph = {
        'v1': ['v5'],
        'v2': ['v1'],
        'v3': ['v1', 'v5'],
        'v4': ['v2', 'v5'],
        'v5': [],
    }
    li = topological_sort( graph )
    print li
LeetCode
LeetCode200 Number of Islands(岛屿的个数)
class Solution(object):
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        if not grid:
        	return 0

        count = 0
        for row in range(len(grid)):
        	for col in range(len(grid[0])):
        		if grid[row][col] == '1':
        			count +=1
        			self.merge(grid, row, col)

        return count

    def merge(self, grid, row, col):
    	if 0 > row or row >= len(grid) or col < 0 or col >= len(grid[0]):
    		return

    	if grid[row][col] != '1':
    		return 

    	grid[row][col] = '#'
    	self.merge(grid, row+1, col)
    	self.merge(grid, row-1, col)
    	self.merge(grid, row, col+1)
    	self.merge(grid, row, col-1)
LeetCode36 Sudoku(有效的数独)
class Solution(object):
    def isValidSudoku(self, board):
        s=set()
        list=board
        for i in range(9):
            for j in range(9):
                if list[i][j] in s:
                    return False
                if list[i][j]!='.':
                    s.add(list[i][j])
            s = set()
        for i in range(9):
            for j in range(9):
                if list[j][i] in s:
                    return False
                if list[j][i]!='.':
                    s.add(list[j][i])
            s = set()
        for i in range(0, 9, 3):
            for j in range(0, 9, 3):
                for m in range(i, i + 3):
                    for n in range(j, j + 3):
                        if list[m][n] in s:
                            return False
                        if list[m][n] != '.':
                            s.add(list[m][n])
                s = set()
        return True

2.2 对应的 LeetCode 练习题

岛屿的个数

class Solution:
    def getResult(self, line: List[str]) -> bool:
        flag=[0 for i in range(10)]
        for c in line:
            if c=='.':
                continue
            if flag[int(c)]==1:
                return False
            flag[int(c)]=1
        return True
    
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        for i in range(9):
            line=[a for a in board[i]]
            if self.getResult(line)==False:
                return False
        for i in range(9):
            line=[b for a in board for b in a[i]]
            if self.getResult(line)==False:
                return False
        for i in range(3):
            for j in range(3):
                line=[b for a in board[i*3:(i+1)*3] for b in a[j*3:(j+1)*3]]
                if self.getResult(line)==False:
                    return False
        return True

有效地数独

class Solution:
    def dfs(self, grid: List[List[str]], i: int, j: int) -> bool:
        if i<0 or j<0 or i>len(grid[:])-1 or j>len(grid[0])-1 or grid[i][j]!='1':
            return False
        grid[i][j]='-1'
        self.dfs(grid, i-1, j)
        self.dfs(grid, i+1, j)
        self.dfs(grid, i, j-1)
        self.dfs(grid, i, j+1)
        return True
        
    
    def numIslands(self, grid: List[List[str]]) -> int:
        num=0
        for i in range(len(grid[:])):
            for j in range(len(grid[0])):
                num+=1 if self.dfs(grid, i, j)
        return num

3. 参考链接

https://github.com/wkh19960317/datawhale-program/blob/master/Task6.md
https://www.jianshu.com/p/9a5e8d0a1472

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值