基础知识补充
1.图分为有向图和无向图,有权图和无权图;
2.图的表示方法:邻接矩阵适合表示稠密图,邻接表适合表示稀疏图;
邻接矩阵:
邻接表:
基础操作补充
1.邻接矩阵:
class GraphAdjacencyMatrix: def __init__(self, num_vertices): self.num_vertices = num_vertices self.matrix = [[0] * num_vertices for _ in range(num_vertices)] def add_edge(self, start, end): # 无向图 self.matrix[start][end] = 1 self.matrix[end][start] = 1
2.邻接表:
from collections import defaultdict class GraphAdjacencyList: def __init__(self): self.graph = defaultdict(list) def add_edge(self, start, end): # 无向图 self.graph[start].append(end) self.graph[end].append(start)
3.图的遍历:
# 深度优先搜索(DFS): # 从上到下,递归或栈实现 def dfs(graph, start, visited=None): if visited is None: visited = set() visited.add(start) print(start, end=" ") for neighbor in graph[start]: if neighbor not in visited: dfs(graph, neighbor, visited) # 广度优先搜索(BFS): # 从左到右,队列实现 from collections import deque def bfs(graph, start): visited = set() queue = deque([start]) visited.add(start) while queue: current = queue.popleft() print(current, end=" ") for neighbor in graph[current]: if neighbor not in visited: queue.append(neighbor) visited.add(neighbor)
题目
200 岛屿数量
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
方法一:深度优先搜索 DFS
若当前点是岛屿时,向上下左右四个点做深度搜索;终止条件:越界;当前是水;class Solution(object): def numIslands(self, grid): """ :type grid: List[List[str]] :rtype: int """ def dfs(nums, x, y): if x<0 or x>len(nums)-1: return if y<0 or y>len(nums[0])-1: return if nums[x][y] =='0':return else: nums[x][y] = '0' # 必须先置0,否则会在两个'1'间连续递归至超过栈长 dfs(nums, x-1, y) dfs(nums, x+1, y) dfs(nums, x, y-1) dfs(nums, x, y+1) cnt = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': dfs(grid, i, j) cnt += 1 return cnt
方法二:广度优先搜索 BFS
若当前点是岛屿时,将其上下左右四个点都加入队列;终止条件:越界;当前是水;
class Solution(object): def numIslands(self, grid): """ :type grid: List[List[str]] :rtype: int """ def bfs(nums, x, y): queue = [(x, y)] while queue: (x, y) = queue.pop(0) if x<0 or x>len(nums)-1: continue elif y<0 or y>len(nums[0])-1: continue elif nums[x][y] =='0':continue else: nums[x][y] = '0' # 必须先置0,否则会在两个'1'间连续递归至超过栈长 queue.append((x-1, y)) queue.append((x+1, y)) queue.append((x, y-1)) queue.append((x, y+1)) cnt = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': bfs(grid, i, j) cnt += 1 return cnt
994 腐烂的橘子
在给定的 m x n
网格 grid
中,每个单元格可以有以下三个值之一:
- 值
0
代表空单元格; - 值
1
代表新鲜橘子; - 值
2
代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
。
第一次遍历将所有新鲜橘子腐烂,统计腐烂次数;第二次遍历统计是否还有剩余的新鲜橘子;(若初始就不含有新鲜橘子呢?)
一次遍历统计新鲜橘子数量的同时记录腐烂橘子的位置(队列);
遍历队列,若当前位置是腐烂橘子则将其上下左右四个点入队,若当前位置是新鲜橘子则将新鲜橘子数量-1再将其上下左右四个点入队;需要将处理过的位置的值置为0,代表不再处理;
class Solution(object): def orangesRotting(self, grid): """ :type grid: List[List[int]] :rtype: int """ cnt, queue = 0, [] m, n = len(grid), len(grid[0]) for i in range(m): for j in range(n): if grid[i][j] == 1: cnt += 1 elif grid[i][j] == 2: queue.append([i,j]) if cnt == 0: return 0 time, stack = -1, [] while queue: [x, y] = queue.pop(0) if -1<x<m and -1<y<n and grid[x][y]: if grid[x][y] == 1: cnt -= 1 grid[x][y] = 0 # 不再处理这个点 stack.append([x-1, y]) stack.append([x+1, y]) stack.append([x, y-1]) stack.append([x, y+1]) if not queue and stack: queue = stack time += 1 stack = [] return -1 if cnt else time
计算遍历深度用BFS
207 课程表
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
方法一:广度优先搜索
from collections import deque from collections import defaultdict class Solution(object): def canFinish(self, numCourses, prerequisites): """ :type numCourses: int :type prerequisites: List[List[int]] :rtype: bool """ degree = [0]*numCourses maps = defaultdict(list) queue = deque() for cur, pre in prerequisites: degree[cur] += 1 # 统计每门课的先修课程数 maps[pre].append(cur) # 记录基础课和对应的进阶课 for i in range(numCourses): if degree[i] == 0: queue.append(i) # 无先修课程(基础课)时入队 count = 0 while queue: course = queue.popleft() count += 1 for i in maps[course]: # 将以course为基础课的进阶课的先修课数-1 degree[i] -= 1 if degree[i] == 0: # 已修完全部基础课 queue.append(i) return count == numCourses
方法二:深度优先搜索
class Solution(object): def canFinish(self, numCourses, prerequisites): """ :type numCourses: int :type prerequisites: List[List[int]] :rtype: bool """ degree = [0]* numCourses maps = defaultdict(list) def dfs(i): if degree[i]==-1: return False # degree[i]==-1 表示会陷入循环 if degree[i]==1: return True # degree[i]==1 表示能完成课 degree[i]=-1 # 防止 1-0-1 转回来的情况 for pre in maps[i]: # 遍历每门基础课 if not dfs(pre): return False degree[i]=1 # 该门课可以完成 return True for cur, pre in prerequisites: # 记录先修课和其基础课程 maps[cur].append(pre) for i in range(numCourses): # 遍历每门课 dfs(i) return sum(degree) == numCourses # 若每门课都完成应该全为1
208 实现Trie(前缀树)
Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie()
初始化前缀树对象。void insert(String word)
向前缀树中插入字符串word
。boolean search(String word)
如果字符串word
在前缀树中,返回true
(即,在检索之前已经插入);否则,返回false
。boolean startsWith(String prefix)
如果之前已经插入的字符串word
的前缀之一为prefix
,返回true
;否则,返回false
。
核心:使用「边」来代表有无字符,使用「点」来记录是否为「单词结尾」以及「其后续字符串的字符是什么」
class TrieNode: def __init__(self): self.children = {} self.is_end = False class Trie(object): def __init__(self): self.root = TrieNode() def insert(self, word): """ :type word: str :rtype: None """ node = self.root for c in word: if c not in node.children: node.children[c] = TrieNode() node = node.children[c] node.is_end = True def searchPrefix(self, word): node = self.root for c in word: if c not in node.children: return None node = node.children[c] return node def search(self, word): """ :type word: str :rtype: bool """ node = self.searchPrefix(word) return node is not None and node.is_end def startsWith(self, prefix): """ :type prefix: str :rtype: bool """ node = self.searchPrefix(prefix) return node is not None
额外补充
flood fill 带你学习Flood Fill算法与最短路模型 - 时间最考验人 - 博客园 (cnblogs.com)