发广播
题目描述:
某地有 N 个广播站,站点之间有些有连接,有些没有。
有连接的站点在接受到广播后会互相发送。
给定一个 N * N 的二维数组 matrix,数组的元素都是字符’0’或者’1’。
matrix[i][j]=‘1’,则代表i和j站点之间有连接,matrix[i][j] = ‘0’代表没连接,
现在要发一条广播,问初始最少给几个广播站发送,才能保证所有的广播站都收到消息。
输入输出描述:
输入描述:
输入为N行,每行有N个数字,为0或1,由空格分隔,构成N*N的数组,N的范围为 1<=N<=50
输出描述:
输出一个数字,为需要广播的服务器数量
示例1:
输入:
1 0 0
0 1 0
0 0 1
输出:
3
说明:3台服务器互不连接,所以需要分别广播这3台服务器
示例2:
输入:
1 1
1 1
输出:
1
说明:2台服务器相互连接,所以只需要广播其中一台服务器
解题思路1:
这种类别标记、统计的问题都可以使用并查集来解决:
两个点相连,就代表这两点属于同一类,将之合并成一个类。
一个点和一个类别中的某点相连,说明改点也属于该类别,将之合并成一个类。
有相连的点都会被合成到多个类别中,这些类别和孤岛(那些没有与任何点相连的独立点)的总数量就时需要的广播器数量
代码:
// 并查集的代表元数组
static int[] parent;
// 集群的个数。
static int island;
public static void main(String[] args) {
//处理输入
Scanner scanner = new Scanner(System.in);
String[] split = scanner.nextLine().split(" ");
int n = split.length;
int[][] matrix = new int[n][n];
// 第一行单独处理
for (int i = 0; i < n; i++) {
matrix[0][i] = Integer.parseInt(split[i]);
}
// 后面 n - 1 行
for (int i = 1; i < n; i++) {
split = scanner.nextLine().split(" ");
for (int j = 0; j < n; j++) {
matrix[i][j] = Integer.parseInt(split[j]);
}
}
// 初始化每个点都是独立的,没有与其他点相连接。故集群的个数为 n
island = n;
// 初始化并查集的代表元数组,让每个节点的代表元是自己
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
// 合并集群
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 如果两个点是相连的,则尝试将它们合并成一个集群
if (i != j && matrix[i][j] == 1) {
// 合并 i 和 j 所在的集群
union(i, j);
}
}
}
// 合并完成后,有多少个相互独立的集群就需要多少台广播机器
System.out.println("并查集的方法:" + island);
}
// 合并 i、j 这两个点所在的集合
private static void union(int i, int j) {
int rootI = find(i);
int rootJ = find(j);
if (rootI == rootJ) {
return;
}
// 合并两个集群,总的集群数量 - 1
island--;
parent[rootI] = rootJ;
}
// 查找点 i 所属的集群代表元
private static int find(int i) {
if (parent[i] == i) {
return i;
}
// 压缩路径
return parent[i] = find(parent[i]);
}
解题思路2:
采用递归的方式,统计访问完所有点的话,需要发出多少次广播
发出一次广播:
从点 i 发出一次广播,可以传播到所有与点 i 相连的点 j,
同时可以传播到所有与 点j 相连的 点x ,
同时可传播到所有与 点x 相连的 点y
…
即递归的思想
// 标记某个点是否被访问过
static Set<Integer> visited;
// a[i]表示: 与点 i 相连的所有点
static List<Integer>[] all;
public static void main(String[] args) {
//处理输入
Scanner scanner = new Scanner(System.in);
String[] split = scanner.nextLine().split(" ");
int n = split.length;
int[][] matrix = new int[n][n];
// 第一行单独处理
for (int i = 0; i < n; i++) {
matrix[0][i] = Integer.parseInt(split[i]);
}
// 后面 n - 1 行
for (int i = 1; i < n; i++) {
split = scanner.nextLine().split(" ");
for (int j = 0; j < n; j++) {
matrix[i][j] = Integer.parseInt(split[j]);
}
}
// 可达点
all = new List[n];
visited = new HashSet<>();
// 采用 dfs 方法
System.out.println("dfs方法输出");
dfsMethod(n, matrix);
}
// ------------------------------dfs 的方法-------------------------------------
private static void dfsMethod(int n, int[][] matrix) {
for (int i = 0; i < n; i++) {
// 记录与点 i 相连的所有点
all[i] = new ArrayList<>();
for (int j = 0; j < n; j++) {
if (i != j && matrix[i][j] == 1) {
// i 点可直接达到 j
all[i].add(j);
}
}
}
// 访问
int count = 0;
for (int i = 0; i < n; i++) {
// 如果没有被访问过,就访问,表示发出一次新的广播信号,同时标记着广播器数量增加一个
if (!visited.contains(i)) {
count++;
dfs(i);
}
}
System.out.println(count);
}
private static void dfs(int i) {
// 访问过了
if (visited.contains(i)) {
return;
}
// 将改点标记为已经访问过
visited.add(i);
// 在看通过当前点,可以继续广播到哪些点
for (Integer cur : all[i]) {
dfs(cur);
}
}
并查集模板代码:
// 路径压缩的加权quick-union算法模板
static class UF {
// 记录集群代表元
int[] parent;
// 记录该集群中的元素个数
int[] size;
// 标记最大的一个集群的所包含节点数量
int maxUFCount;
// 标记一共有多少个相互独立的集群
int unionCount;
private UF (int n) {
parent = new int[n + 1];
size = new int[n + 1];
maxUFCount = 1;
unionCount = n;
for (int i = 0; i <= n; i++) {
parent[i] = i;
size[i] = 1;
}
}
// 获取最大集群包含的节点个数
public int getMaxUFCount() {
return maxUFCount;
}
// 获取相互独立的集群数量
public int getUnionCount() {
return unionCount;
}
// 合并两个点所在的集群
public void union (int a, int b) {
int rootA = find(a);
int rootB = find(b);
if (rootA != rootB) {
if (size[rootA] < size[rootB]) {
parent[rootA] = rootB;
size[rootB] += size[rootA];
// 更新最大个集群包含节点数量
maxUFCount = Math.max(maxUFCount, size[rootB]);
} else {
parent[rootB] = rootA;
size[rootA] += size[rootB];
// 更新最大个集群包含节点数量
maxUFCount = Math.max(maxUFCount, size[rootA]);
}
// 合并了两个集群,则集群数量 -1
unionCount--;
}
}
// 查找某个点的代表元是哪个
private int find (int p) {
if (parent[p] == p) {
return p;
}
// 路径压缩
return parent[p] = find(parent[p]);
}
}