
我们想要不重复不遗漏的遍历图上的每一个节点,应该怎么做呢!

我们可以看到整个搜索过程需要几个信息:
因此我们可以概括主要步骤如下:
图结构通常会以一个列表的形式给出,如 edges。列表中每个元素存放一组节点的连接关系,即 edge[i] = [node1, node2](如果是有权图,则有权重信息;如果是有向图的话,一般是 node1 -> node2,这里我们先以无向图为例)
我们通常需要将这个“边信息”列表转成一个“节点信息”列表 graph,graph[i]也是一个列表,存储节点 i 的所有邻节点。
函数参数:
edges:图中的所有边信息,形式如 [node1, node2];如果是带权重的,则形式如 [node1, node2, weight]start:搜索起点;n:节点总数;输出每个遍历到的节点的编号
class Solution {
public void dfs(int[][] edges, int start, int n){
List<Integer>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1];
neighbors[node1].add(node2); // 边的两个端点互为彼此的邻节点
neighbors[node2].add(node1);
}
dfs(start, neighbors, visited); // 从start节点开始进行深度优先搜索
}
private void dfs(int node, List<Integer>[] neighbors, boolean[] visited){
System.out.print(node + " "); // 当前步骤的操作,可替换,这里输出这个节点的值
visited[node] = true; // 标记节点node已经搜索,避免重复
for(int nei: neighbors[node]){ // 根据邻接表,枚举节点node的所有邻节点
if(!visited[nei]){
dfs(nei, neighbors, visited); // 如果邻节点nei未访问过,递归访问
}
}
}
}输出每个遍历到的节点的编号和对应边的权重
class Solution {
private void dfs(int node, List<int[]>[] neighbors, boolean[] visited){
visited[node] = true; // 标记节点node已经搜索,避免重复
for(int[] nei: neighbors[node]){ // 根据邻接表,枚举节点node的所有邻节点
if(!visited[nei[0]]){
System.out.println(nei[0] + " " + nei[1]); // 当前步骤的操作,可替换,这里输出这个节点的值
dfs(nei[0], neighbors, visited); // 如果邻节点nei未访问过,递归访问
}
}
}
public void dfs(int[][] edges, int start, int n){
List<int[]>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点和对应权重
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点已访问
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1], weight = e[2];
neighbors[node1].add(new int[]{node2, weight}); // 边的两个端点互为彼此的邻节点
}
dfs(start, neighbors, visited); // 从start节点开始进行深度优先搜索
}
}树形图就是形状和树结构一样的图,即 n 个节点只有 n - 1 边,保证每个节点 至少 一条边和其他节点相连。

class Solution {
public void dfs(int[][] edges, int start, int n){
List<Integer>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1];
neighbors[node1].add(node2); // 边的两个端点互为彼此的邻节点
neighbors[node2].add(node1);
}
dfs(start, neighbors, -1); // 从start节点开始进行深度优先搜索,根节点没有父节点,用-1表示
}
private void dfs(int node, List<Integer>[] neighbors, int pa){
System.out.print(node + " "); // 当前步骤的操作,可替换,这里输出这个节点的值
for(int nei: neighbors[node]){ // 根据邻接表,枚举节点node的所有邻节点
if(nei != pa){
dfs(nei, neighbors, node); // nei是node的子节点,递归访问
}
}
}
}深度优先搜索(DFS, Depth-First Search)算法是一种用于遍历或搜索树或图的算法。
它的基本思想是尽可能深地搜索图的分支,直到达到叶子节点(即没有未访问的相邻节点的节点)或达到目标节点,然后回溯到上一个节点,继续探索其他未探索的分支,直到整个图都被探索完毕。
你可以理解成 DFS 它是一个愣头青,它 沿着一条路走到底 ,不撞南墙不回头。
我们想要不重复不遗漏的遍历图上的每一个节点,应该怎么做呢!

我们可以看到整个搜索过程需要几个信息:
因此我们可以概括主要步骤如下:

(**注意:**我们在将邻节点加入队列的同时就要将邻节点标记为已访问过,而不是等到处理到邻节点再标记;这样才不会导致邻节点重复加入队列:
比如访问到节点0的时候,将节点1和节点2都加入队列;如果此时不将节点2标记已访问,那么下一步访问到节点0时,节点2也是节点1的邻节点且未访问,就会重复加入队列;)
图结构通常会以一个列表的形式给出,如 edges。列表中每个元素存放一组节点的连接关系,即 edge[i] = [node1, node2](如果是有权图,则有权重信息;如果是有向图的话,一般是 node1 -> node2,这里我们先以无向图为例)
我们通常需要将这个“边信息”列表转成一个“节点信息”列表 graph,graph[i]也是一个列表,存储节点 i 的所有邻节点。
函数参数:
edges:图中的所有边信息,形式如 [node1, node2];如果是带权重的,则形式如 [node1, node2, weight]start:搜索起点;n:节点总数;输出每个遍历到的节点的编号
class Solution {
public void bfs(int[][] edges, int start, int n){
List<Integer>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1];
neighbors[node1].add(node2); // 边的两个端点互为彼此的邻节点
neighbors[node2].add(node1);
}
Queue<Integer> qu = new LinkedList<>(); // 初始化队列
qu.offer(start); // 将起点入队
visited[start] = true; // 起点入队即标记已访问
while(!qu.isEmpty()){
int node = qu.poll(); // 获取并弹出队首节点
System.out.print(node + " "); // 执行的操作
for(int nei: neighbors[node]){ // 获取当前节点的所有邻节点
if(!visited[nei]){
qu.offer(nei); // 将未访问的邻节点加入队列
visited[nei] = true; // 入队即为已访问
}
}
}
}
}输出每个遍历到的节点的编号和对应边的权重
class Solution {
public void bfs(int[][] edges, int start, int n){
List<int[]>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点和对应权重
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点已访问
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1], weight = e[2];
neighbors[node1].add(new int[]{node2, weight}); // 边的两个端点互为彼此的邻节点
}
Queue<int[]> qu = new LinkedList<>(); // 初始化队列
qu.offer(new int[]{start, 0}); // 将起点入队
visited[start] = true; // 起点入队即标记已访问
while(!qu.isEmpty()){
int[] node = qu.poll(); // 获取并弹出队首节点
System.out.println(node[0] + " " + node[1]); // 执行的操作
for(int[] nei: neighbors[node[0]]){ // 获取当前节点的所有邻节点
if(!visited[nei[0]]){
qu.offer(nei); // 将未访问的邻节点加入队列
visited[nei[0]] = true; // 入队即为已访问
}
}
}
}
}图的广度优先搜索(Breadth-First Search, BFS)是一种用于遍历或搜索树或图的算法。
它的基本思想是尽可能广地每个节点相邻范围;即从一个选定的源节点开始,探索所有邻近的节点,然后再从这些节点开始,继续探索它们的邻近节点,以此类推,直到访问了图中所有可达的节点。
BFS 使用队列(Queue)来实现这一过程,队列是一种先进先出(FIFO)的数据结构。

连通块只是图论中的一个概念,并没有什么新的算法。只是这个概念比较常用,因此单独列出来说明一下。主要是为了说明如何统计图中连通块个数
连通块是指图中由相互连接的顶点组成的子集,换句话说,连通块内部的所有顶点彼此之间都是连通可达的;
连通块的使用场景通常是统计图上的连通块个数。而统计连通块个数方法实际就是图搜索:我们需要去搜索每个节点所在的连通块。

代码还有一个前提是 n 个节点的编号为 [0, n-1]才能用列表存储邻接表和访问表,否则用哈希表。
class Solution{
public int countConnectBlocks(int[][] edges, int n){
List<Integer>[] neighbors = new ArrayList[n]; // 邻接表,neighbors[i]存储节点i的所有邻节点
for(int i = 0; i < n; i++){
neighbors[i] = new ArrayList<>(); // 初始化每个节点的邻接表neighbors[i]
}
boolean[] visited = new boolean[n]; // 访问表,visited[i]为true表示节点
for(int[] e: edges){ // 枚举每一条边edge的信息
int node1 = e[0], node2 = e[1];
neighbors[node1].add(node2); // 边的两个端点互为彼此的邻节点
neighbors[node2].add(node1);
}
int block = 0; // 统计连通块个数
for(int i = 0; i < n; i++){ // 枚举每个节点i作为起点进行深度优先搜索
if(visited[i])continue; // 已处理过的节点说明是在之前的某个连通块上,不重复处理
dfs(i, neighbors, visited); // 未处理的节点一定在一个新的连通块上,以其为起点搜索其所在的连通块
block++; // 累加一个连通块
}
return block;
}
private void dfs(int node, List<Integer>[] neighbors, boolean[] visited){
visited[node] = true; // 标记节点node已经搜索,避免重复
for(int nei: neighbors[node]){ // 根据邻接表,枚举节点node的所有邻节点
if(!visited[nei]){
dfs(nei, neighbors, visited); // 如果邻节点nei未访问过,递归访问
}
}
}
}