系列博客目录
文章目录
理论基础
图(Graph)是计算机科学中一种非常重要的数据结构,用于表示对象之间的关系。图由**节点(Vertex)和边(Edge)**组成,广泛应用于社交网络、网页链接、路径查找、推荐系统等领域。
1. 图的基本定义
-
图(Graph):由一组节点和一组边组成。每条边连接两个节点,表示节点之间的关系。
图可以分为:
- 无向图(Undirected Graph):边没有方向,边的两端节点是对称的(即边从节点 A 到节点 B 和从节点 B 到节点 A 是等价的)。
- 有向图(Directed Graph 或 Digraph):边有方向,边是从一个节点指向另一个节点的(即边从节点 A 到节点 B 与从节点 B 到节点 A 是不同的)。
2. 图的基本术语
- 节点(Vertex 或 Node):图中的基本元素,表示对象或实体。
- 边(Edge 或 Arc):连接两个节点的关系,表示节点之间的连接或关联。
- 邻接节点(Adjacent Vertex):如果存在一条边连接节点
A和节点B,则节点A和节点B是邻接的。 - 度(Degree):一个节点的度是与该节点相连的边的数量。对于无向图,度数是边的数量;对于有向图,分为入度(指向该节点的边数)和出度(从该节点出发的边数)。
- 路径(Path):从一个节点到另一个节点的边的序列。路径可以是简单的,也可以包含重复的节点和边。
- 环(Cycle):一个从节点出发,并最终回到自身的路径。
- 连通性:
- 连通图(Connected Graph):对于无向图,图中的任意两个节点都有一条路径相连。
- 强连通图(Strongly Connected Graph):对于有向图,任意两个节点之间都有路径可以相互到达。
3. 图的表示方式
图的表示通常有以下几种方式:
1. 邻接矩阵(Adjacency Matrix)
- 使用一个二维数组(矩阵)来表示图,其中行和列对应于节点,矩阵的元素表示边的存在性。
- 时间复杂度:
- 查找边:
O(1) - 添加边:
O(1) - 遍历邻接节点:
O(V),其中V是节点的数量。
- 查找边:
例如,假设有一个图 G,其包含 4 个节点(0, 1, 2, 3):
0 -- 1
| |
3 -- 2
该图的邻接矩阵表示如下:
0 1 2 3
0 [ 0 1 0 1 ]
1 [ 1 0 1 0 ]
2 [ 0 1 0 1 ]
3 [ 1 0 1 0 ]
2. 邻接列表(Adjacency List)
- 使用一个数组或列表,其中每个元素是一个列表,存储与该节点直接相连的所有节点。
- 时间复杂度:
- 查找边:
O(V),最坏情况下需要遍历所有邻接节点。 - 添加边:
O(1) - 遍历邻接节点:
O(E),其中E是边的数量。
- 查找边:
例如,以上图的邻接列表表示如下:
0 -> [1, 3]
1 -> [0, 2]
2 -> [1, 3]
3 -> [0, 2]
4. 图的类型
-
加权图(Weighted Graph):每条边有一个权重(或值),表示边的成本或距离等。
- 有向加权图和无向加权图的边也可以具有方向和权重。
-
有向无环图(DAG, Directed Acyclic Graph):一种特殊的有向图,没有环存在,广泛应用于拓扑排序和任务调度等场景。
-
树(Tree):一种特殊的图,具有以下特征:
- 只有一个连通分量(即连通图),并且没有环。
- 每个节点都有一个父节点,除了根节点。
5. 常见的图的算法
-
广度优先搜索(BFS, Breadth-First Search):
- 从某个节点开始,访问所有相邻的节点,逐层访问节点,直到所有可达的节点都被访问。
- 用途:查找最短路径、图的遍历、网络流等。
-
深度优先搜索(DFS, Depth-First Search):
- 从某个节点开始,沿着边尽可能深入访问,直到没有未访问的邻接节点,再回溯到上一个节点。
- 用途:拓扑排序、图的遍历、环检测、连通分量等。
-
Dijkstra 算法:一种经典的单源最短路径算法,用于计算从起点到其他所有节点的最短路径(适用于加权图,边的权重为非负数)。
-
Bellman-Ford 算法:另一种单源最短路径算法,能处理图中有负权边的情况。
-
Floyd-Warshall 算法:一种用于计算所有节点对之间最短路径的算法(适用于加权图)。
-
拓扑排序(Topological Sort):用于有向无环图(DAG)的排序,节点按依赖关系的顺序排列。
-
最小生成树(MST, Minimum Spanning Tree):
- Kruskal 算法:通过边的排序和并查集方法构建最小生成树。
- Prim 算法:通过不断选择与当前树最小权重边的方式构建最小生成树。
6. 图的应用
- 社交网络分析:社交网络中,用户之间的关系可以通过图表示,常见问题包括找出最短路径、群体检测等。
- 网络流问题:如最大流最小割问题、最短路径问题。
- 网页链接分析:搜索引擎通过有向图分析网页之间的链接关系来排名。
- 任务调度与依赖:任务的执行顺序可以通过有向无环图(DAG)来表示,任务的依赖关系可通过图的拓扑排序来处理。
- 推荐系统:利用图来表示用户与商品之间的关系,进行推荐。
图是非常重要且应用广泛的数据结构,理解图的基本理论和算法对于解决复杂的关系问题非常有帮助。
例题
200.岛屿数量
链接
思路:没检测到一块岛屿,我们要把构成这块岛屿的所有小块都标记成已探索过,防止重复计算。
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
infect(grid, i, j);
res ++;
}
}
}
return res;
}
private void infect(char[][] grid,int y, int x){
int m = grid.length;
int n = grid[0].length;
if(x<0|| x>=n) return;
if(y<0 || y>=m) return;
if(grid[y][x] == '1'){
grid[y][x] = '.';
}else {
return;//注意再检测到不为陆地小块后要暂定
}
infect(grid, y+1, x);
infect(grid, y-1, x);
infect(grid, y, x+1);
infect(grid, y, x-1);
}
}
130.被围绕的区域
链接

使用深度优先遍历如下:
class Solution {
int n, m;
public void solve(char[][] board) {
n = board.length;
if (n == 0) {
return;
}
m = board[0].length;
for (int i = 0; i < n; i++) {
dfs(board, i, 0);
dfs(board, i, m - 1);
}
for (int i = 1; i < m - 1; i++) {
dfs(board, 0, i);
dfs(board, n - 1, i);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
public void dfs(char[][] board, int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
return;
}
board[x][y] = 'A';
dfs(board, x + 1, y);
dfs(board, x - 1, y);
dfs(board, x, y + 1);
dfs(board, x, y - 1);
}
}
133.克隆图
链接
注意:这个图是连通的,我们cloneNode.neighbors.add(cloneGraph(neighbor));这条语句可以帮我们递归的一层一层的往下把所有结点以及其neighbors都弄好。最深层的节点,应该是开始的节点,因为连通。自己递归到自己为什么没错?因为我们存入到List中的是个地址,这个地址指向一个实体,地址虽然不变,但地址所指向的内容也就是实体是会变化的,比如这个道题,比如B作为A的邻居,我们在把B添加到A的neighbors中时,可能B自己的neighbors并没有完整。
class Solution {
private HashMap <Node, Node> visited = new HashMap <> ();
public Node cloneGraph(Node node) {
if (node == null) {
return node;
}
// 如果该节点已经被访问过了,则直接从哈希表中取出对应的克隆节点返回
if (visited.containsKey(node)) {
return visited.get(node);
}
// 克隆节点,注意到为了深拷贝我们不会克隆它的邻居的列表
Node cloneNode = new Node(node.val, new ArrayList());
// 哈希表存储
visited.put(node, cloneNode);
// 遍历该节点的邻居并更新克隆节点的邻居列表
for (Node neighbor: node.neighbors) {
cloneNode.neighbors.add(cloneGraph(neighbor));
}
return cloneNode;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/clone-graph/solutions/370663/ke-long-tu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
207.课程表
链接
思路:我们先学习可以学习的课(即不需要前置课程的课),学完后,解锁新的可以学习(即前置课程学完)的课程,再继续学习。
蜜糖:我们先建立一个数组,存储每门课程所需前置课程的数量。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] courses = new int[numCourses];
Arrays.fill(courses, 0);
HashMap<Integer,List<Integer>> needUnlock = new HashMap<>();
for(int[] pre : prerequisites){
int target = pre[0];
int need = pre[1];
final List<Integer> orDefault = needUnlock.getOrDefault(need, new ArrayList<>());
orDefault.add(target);
needUnlock.put(need, orDefault);
courses[target] ++;
}
Deque<Integer> queue = new ArrayDeque<>();
for (int i = 0; i < courses.length; i++) {
if(courses[i] == 0){
queue.offerLast(i);
}
}
while(!queue.isEmpty()){
int course = queue.pollFirst();
if(needUnlock.containsKey(course)){
for(int num : needUnlock.get(course)){
if(courses[num] > 0) courses[num] --;
if(courses[num] == 0) queue.offerLast(num);
}
}
}
for (int i = 0; i < numCourses; i++) {
if(courses[i] > 0) return false;
}
return true;
}
}
210.课程表 II
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<Integer> res = new ArrayList<>();
int[] courses = new int[numCourses];
HashMap<Integer,List<Integer>> needUnlock = new HashMap<>();
for(int[] pre : prerequisites){
int target = pre[0];
int need = pre[1];
final List<Integer> orDefault = needUnlock.getOrDefault(need, new ArrayList<>());
orDefault.add(target);
needUnlock.put(need, orDefault);
courses[target] ++;
}
Deque<Integer> queue = new ArrayDeque<>();
for (int i = 0; i < numCourses; i++) {
if(courses[i] == 0){
queue.offerLast(i);
res.add(i);
}
}
while(!queue.isEmpty()){
int course = queue.pollFirst();
if(needUnlock.containsKey(course)){
for(int num : needUnlock.get(course)){
if(courses[num] > 0) {
courses[num] --;
}
if(courses[num] == 0){
queue.offerLast(num);
res.add(num);
}
}
}
}
for (int i = 0; i < numCourses; i++) {
if(courses[i] > 0) return new int[0];
}
int[] array = new int[res.size()];
// 使用 for 循环将 List 中的元素赋值到数组中
for (int i = 0; i < res.size(); i++) {
array[i] = res.get(i); // 将 List 中的 Integer 转换为 int 并赋值给数组
}
return array;
}
}
846

被折叠的 条评论
为什么被折叠?



