合根植物(深搜与并查集)

时间:2019-02-19
本文章向大家介绍合根植物(深搜与并查集),主要包括合根植物(深搜与并查集)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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]);
	}
}

总的来说使用并查集的算法效率比较高,耗时比较短