发广播_200分_A/B卷复用_并查集/dfs

发广播

题目描述:

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

并查集其它题目

城市聚集度_200分_A/B卷复用_并查集
快递业务站_100分_2023A卷_并查集/dfs

#include "driverlib.h" #define TIMER_PERIOD 8192 void Timer_A0_PWM_Init(void) { Timer_A_outputPWMParam htim = {0}; //P1.2复用输出 GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN2); //时钟源选为SMCLK = 1048576 HZ htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //频系数设为32 32768HZ htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_32; //装载值设为8192 - 1 ,周期为0.25s htim.timerPeriod = TIMER_PERIOD - 1; //P1.2 对应 TA0.1 故设为TIMER_A_CAPTURECOMPARE_REGISTER_1 定时器为0 htim.compareRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1; //选择复位置位模式 htim.compareOutputMode = TIMER_A_OUTPUTMODE_RESET_SET; //设置占空比,为5% htim.dutyCycle = TIMER_PERIOD / 20 ; //P1.2 对应 TA0.1 为TIMER_A0_BASE Timer_A_outputPWM(TIMER_A0_BASE, &htim); } void Timer_A1_PWM_Init(void) { Timer_A_outputPWMParam htim = {0}; //P1.3复用输出 GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN3); //时钟源选为SMCLK = 1048576 HZ htim.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //频系数设为32 32768HZ htim.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_32; //装载值设为8192 - 1 ,周期为0.25s htim.timerPeriod = TIMER_PERIOD - 1; //P1.3 对应 TA1.1 故设为TIMER_A_CAPTURECOMPARE_REGISTER_1 定时器为1 htim.compareRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1; //选择复位置位模式 htim.compareOutputMode = TIMER_A_OUTPUTMODE_RESET_SET; //设置占空比,为10% htim.dutyCycle = TIMER_PERIOD / 10 ; //P1.3 对应 TA1.1 为TIMER_A1_BASE Timer_A_outputPWM(TIMER_A1_BASE, &htim); }
07-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值