合根植物(深搜与并查集)
1. 问题描述:
问题描述
w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?输入格式
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20样例输入
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17样例输出
5
样例说明
其合根情况参考下图
2. 可以使用下面两种思路进行解决
① 可以使用深搜的方法来解决
使用深搜的方法需要把所有合在一起的植物标记出来才可以进行深搜,像上面的1--5--9--...与2---3这些连在一起的植物是不一样的,怎么样区分这些植物的不同呢?
这里可以使用建立一个边的集合,把所有同一个根的植物连接在一起,边我们使用私有的内部类来表示,相应的属性有:
private static class Edge{
int u;
int v;
int next;
}
u表示起点,v表示的是终点,对应着控制台输入的起点与终点,next用来记录与下一个与当前节点相连的边,使用一个数组来记录上一个的next
因为一个开始的节点为起点只能往下或者往右,像以9开头的边为9-->10,9-->13,所以在对边进行初始化的时候采用下面的方法:
private static void addEdge(int u, int v){
edge[count].u = u;
edge[count].v = v;
edge[count].next = head[u];
head[u] = count++;
}
使用一个计数变量来计算当前的边是第几条边,每一次edge[count].next记录的都是上一次与u相连的节点是谁
进行搜索的时候采用下面的方法:
private static void dfs(int u) {
visit[u] = 0;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].v;
if(visit[v] == 0) continue;
dfs(v);
}
}
向上面的9----10,9----13深搜的时候调用dfs(9),而head(9)记录的是输入9--13的时候存储在边集中的第几条边,这里好像是输入的是第13条边,而edge[i].next存储的是上一次输入9--->10存储的是第几条边,这样把以9开头的边都连接了起来
Java代码如下:
package 历届试题;
import java.util.Arrays;
import java.util.Scanner;
public class 合根植物dfs解法 {
//深搜
static int head[];
static int count = 0;
//记录调用dfs的次数
static int ans = 0;
static int visit[];
static Edge edge[];
private static class Edge{
//存储边
int u;
int v;
//记录下一个与当前有关系的节点
int next;
}
private static void addEdge(int u, int v){
edge[count].u = u;
edge[count].v = v;
edge[count].next = head[u];
head[u] = count++;
}
//使用深搜来解决每调用完一次dfs说明把所有连在一起的节点都搜索完了
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int row = sc.nextInt();
int col = sc.nextInt();
head = new int[row * col + 1];
edge = new Edge[(row * col + 1) * 2];
for(int i = 0; i < (row * col + 1) * 2; i++){
edge[i] = new Edge();
}
visit = new int[row * col + 1];
Arrays.fill(visit, -1);
Arrays.fill(head, -1);
int times = sc.nextInt();
for(int i = 0; i < times; i++){
int u = sc.nextInt();
int v = sc.nextInt();
//添加边因为是无向图所以需要两边进行添加
addEdge(u, v);
addEdge(v, u);
}
for(int i = 1; i <= row; i++){
for(int j = 1; j <= col; j++){
if(visit[j + (i - 1) * col] != 0){
/*System.out.println("****");*/
dfs(j + (i - 1) * col);
ans++;
}
}
}
System.out.println(ans);
sc.close();
}
private static void dfs(int u) {
visit[u] = 0;
//存在着两个平行的状态
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].v;
if(visit[v] == 0) continue;
dfs(v);
}
}
}
C++代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
int head[maxn];
int cnt;
bool vis[maxn];
struct Edge
{
int u,v,next;
}edge[maxn*2];
void addEdge(int u,int v)
{
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void init()
{
memset(head,-1,sizeof(head));
memset(vis,1,sizeof(vis));
cnt=0;
}
void dfs(int u)
{
vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(vis[v]==0) {
continue;
}
dfs(v);
}
}
int main()
{
int n,m,k;
init();
scanf("%d%d%d",&n,&m,&k);
for(int i=0,u,v;i<k;i++)
{
scanf("%d%d",&u,&v);
addEdge(u,v);
addEdge(v,u);
}
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(vis[j+(i-1)*m])
{
dfs(j+(i-1)*m);
ans++;
}
}
}
cout<<ans<<endl;
return 0;
}
② 可以使用并查集的方法来解决
并查集的方法比深搜要简单处理起来更方便
可以使用一个数组来记录当前节点的父节点,在控制台输入起点与终点的时候,先查询两个节点的父节点是否相同,假如相同不能够合并两个节点,假如不同合并当前两个节点
而且使用并查集的方法在查询的时候使用递归的方法会把当前节点下的所有节点指向当前节点的父节点:当前节点为零直接返回下标,否则继续递归往上寻找
private static int find(int x) {
if(arr[x] == 0) return x;
return arr[x] = find(arr[x]);
}
代码如下:
import java.util.Scanner;
public class Main {
private static int arr[];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int row = sc.nextInt();
int col = sc.nextInt();
int times = sc.nextInt();
arr = new int[row * col + 1];
for(int i = 0; i < times; i++){
int x = sc.nextInt();
int y = sc.nextInt();
//并查集
int x1 = find(x);
int y1 = find(y);
if(x1 != y1){
union(x, y);
}
}
int count = 0;
for(int i = 1; i < row * col + 1; i++){
if(arr[i] != 0)continue;
count++;
}
System.out.println(count);
sc.close();
}
private static void union(int x, int y) {
arr[find(y)] = find(x);
}
private static int find(int x) {
if(arr[x] == 0) return x;
return arr[x] = find(arr[x]);
}
}
总的来说使用并查集的算法效率比较高,耗时比较短
- WINDOW 安装mysql5.7数据库,并设置密码及相关报错
- go channel 通信通道
- SQl 语句(常见) 新建,删除,修改表,新增字段,修改默认值
- SQL处理表结构的基本方法整理(创建表,关联表,复制表)
- Go web之旅(路由篇)
- Golang插入排序
- Golang写的并行排序算法
- Go中调用dll示例
- python 序列化数据:pickle与json ,dumps与loads
- golang继承,和多态
- python 利用random生成验证码与MD5码加密过程
- Java内部类的继承
- Java继承类中static成员函数的重写
- Search for a range寻找上下界-Leetcode
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 最近三个月记录
- 近期vue开发相关问题
- 打印日志时 Logback 内部都做了些什么
- nginx配置https
- 近期问题: jq循环中异步请求问题
- 启用HSTS并加入HSTS Preload List-附删除HSTS方法
- 聊聊UI标准化
- puppeteer学习----登录人才网并截图
- MySQL 案例:用户鉴权与 Host 优先级
- Http Post 快速使用
- 有赞Flutter插件开发与发布
- 国标GB28181协议客户端EasyGBS国标视频平台级联EasyNVR:EasyGBS如何实现调阅EasyNVR的视频通道?
- gitlab CI/CD 相关问题
- 微信jssdk分享接口
- 有赞 Flutter 混编方案