547.力扣LeetCode_ 省份数量
目录
方法1:并查集
直接统计结果
间接统计结果
两种统计方法对比
方法2:dfs 深度优先
邻接矩阵转链式邻接表
dfs核心代码
dfs主函数
方法3:bfs广度优先
bfs核心代码
bfs主函数
DFS与BFS对比
核心差异
主函数调用差异
访问节点的时机

-
方法1:并查集
-
直接统计结果
#define MaxSize 200
int parent[MaxSize],rank[MaxSize];void Initialize(int n){for(int i=0;i<n;i++){parent[i]=i;rank[i]=1;}
}int find(int x){if(parent[x]!=x){parent[x]=find(parent[x]);}return parent[x];
}void Union(int x,int y){int x_root=find(x);int y_root=find(y);if(x_root!=y_root){if(rank[x_root]>rank[y_root]){parent[y_root]=x_root;}else if(rank[x_root]<rank[y_root]){parent[x_root]=y_root;}else{parent[y_root]=x_root;rank[x_root]++;}}
}int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize) {int n=isConnectedSize;Initialize(n);for(int i=0;i<n;i++){for(int j=i+1;j<n;j++){if(isConnected[i][j]){Union(i,j);}}}int count=0;for(int i=0;i<n;i++){if(parent[i]==i) count++;}return count;
}
-
间接统计结果
int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize) {int n=isConnectedSize;Initialize(n);int count=0;for(int i=0;i<n;i++){for(int j=i+1;j<n;j++){if(isConnected[i][j]){if(find(i)!=find(j)){Union(i,j);count++;}}}}return n-count;
}
-
两种统计方法对比
直接统计法
核心逻辑:通过遍历所有节点,统计根节点( parent[i] == i )的数量,以此直接得到连通分量的个数。
间接统计法
核心逻辑:通过计算有效合并的次数来反推连通分量个数。初始时为 n 个分量,每发生一次有效合并,分量数减 1,最终结果为 n - count 。
为何间接统计法必须增加判断:
该方法的正确性依赖于计数器 count 的精确性,它必须严格等于有效合并的次数。若缺少 if(find(i) != find(j)) 判断,则每次遇到边 (i,j) 都会执行 count++ ,即使 i 和 j 已连通。如果原图中存在环,会导致count重复计数,最终结果 n - count 偏小。
-
方法2:dfs 深度优先
-
邻接矩阵转链式邻接表
typedef struct LinkNode{int data;struct LinkNode* next;
}LinkNode;LinkNode** Get_Adj(int** isConnected,int n){LinkNode **adj=(LinkNode **)calloc(n,sizeof(LinkNode*));int adjColSize[n];memset(adjColSize,0,n*sizeof(int));for(int i=0;i<n;i++){for(int j=i+1;j<n;j++){if(isConnected[i][j]){LinkNode *new_i=(LinkNode *)malloc(sizeof(LinkNode));new_i->data=j;new_i->next=adj[i];adj[i]=new_i;LinkNode *new_j=(LinkNode *)malloc(sizeof(LinkNode));new_j->data=i;new_j->next=adj[j];adj[j]=new_j; }}}return adj;
}
-
dfs核心代码
void dfs(int cur,LinkNode** adj,bool visited[]){if(visited[cur]) return;visited[cur]=true;LinkNode* p=adj[cur];while(p){int cur_neighbor=p->data;if(!visited[cur_neighbor]){dfs(cur_neighbor,adj,visited);}p=p->next;}
}
-
dfs主函数
int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize) {int n=isConnectedSize;LinkNode **adj=Get_Adj(isConnected,n);bool visited[n];memset(visited,false,n*sizeof(bool));int count=0;for(int i=0;i<n;i++){if(!visited[i]){count++;dfs(i,adj,visited);}}return count;
}
-
方法3:bfs广度优先
-
bfs核心代码
void bfs(int cur,LinkNode** adj,bool visited[],int n){int queue[n],front=0,rear=0;queue[rear++]=cur;visited[cur]=true;while(front!=rear){int x=queue[front++];LinkNode *p=adj[x];while(p){int x_neighbor=p->data;if(!visited[x_neighbor]){queue[rear++]=x_neighbor;visited[x_neighbor]=true;}p=p->next;}}
}
-
bfs主函数
int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize) {int n=isConnectedSize;LinkNode **adj=Get_Adj(isConnected,n);bool visited[n];memset(visited,false,n*sizeof(bool));int count=0;for(int i=0;i<n;i++){if(!visited[i]){count++;bfs(i,adj,visited,n);}}return count;
}
-
DFS与BFS对比
-
核心差异
DFS 的空间管理依赖于系统的递归调用栈。每次调用 dfs() 函数,系统都会在栈上为其创建一个新的栈帧,用于存储函数参数和局部变量。当函数执行完毕返回时,这个栈帧就会被自动销毁。这种方式的优点是代码简洁,不需要我们手动管理栈结构。缺点是当图的深度很大时,可能会导致栈溢出
BFS 的空间管理则依赖于我们手动维护的队列。我们需要自己创建一个数据结构(数组或链表)来存放所有待访问的节点。代码中传入 n 参数,就是为了在 BFS 函数内部创建一个大小为 n 的数组作为队列。这种方式的优点是避免了递归深度过大导致的栈溢出问题,空间使用更加可控。缺点是需要显式地管理队列的入队和出队操作,代码相对复杂一些。
-
主函数调用差异
- DFS: dfs(i, adj, visited);
只需要当前起始节点 i 、邻接表 adj 和访问标记数组 visited 。
递归栈由系统自动管理,不需要知道图的总节点数 n 。
- BFS: bfs(i, adj, visited, n);
必须多传一个 n 参数。因为函数内部要创建一个能容纳所有节点的队列数组。
-
访问节点的时机
DFS:在递归函数开始时就标记 visited[cur] = true 。
这确保了节点在被访问之初就被标记,防止被重复处理。
BFS:在节点被加入队列时就立即标记 visited[cur] = true 。
