题目描述
树可以看成是一个图(拥有 n 个节点和 n - 1 条边的连通无环无向图)。
现给定一个拥有 n 个节点(节点标号是从 1 到 n)和 n 条边的连通无向图,请找出一条可以删除的边,删除后图可以变成一棵树。
输入描述
第一行包含一个整数 N,表示图的节点个数和边的个数。
后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。
输出描述
输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。
输入示例
3 1 2 2 3 1 3
输出示例
1 3
图中的 1 2,2 3,1 3 等三条边在删除后都能使原图变为一棵合法的树。但是 1 3 由于是标准输出里最后出现的那条边,所以输出结果为 1 3
数据范围:
1 <= N <= 1000.
[KamaCoder] 108. 冗余连接
自己看到题目的第一想法
无.
看完代码随想录之后的想法
并查集的基础理论的基础应用, 基础篇中的基础应用篇.
import java.util.Scanner;
public class Main {
private static int[] father;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
init(scanner.nextInt());
int fatherNode;
int childNode;
for (int i = 1; i < father.length; i++) {
fatherNode = scanner.nextInt();
childNode = scanner.nextInt();
if (isSame(fatherNode, childNode)) {
System.out.println(fatherNode + " " + childNode);
return;
}
join(fatherNode, childNode);
}
System.out.println();
}
}
private static void init(int nodeCount) {
father = new int[nodeCount + 1];
for (int i = 1; i <= nodeCount; i++) {
father[i] = i;
}
}
private static boolean isSame(int u, int v) {
return find(u) == find(v);
}
private static int find(int u) {
if (u == father[u]) {
return u;
}
return father[u] = find(father[u]);
}
private static void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) {
return;
}
father[v] = u;
}
}
自己实现过程中遇到哪些困难
无.
题目描述
有向树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有根树拥有 n 个节点和 n - 1 条边。
输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。
输入描述
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表 s 节点有一条连接 t 节点的单向边
输出描述
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。
输入示例
3 1 2 1 3 2 3
输出示例
2 3
在删除 2 3 后有向图可以变为一棵合法的有向树,所以输出 2 3
数据范围:
1 <= N <= 1000.
[KamaCoder] 109. 冗余连接II
自己看到题目的第一想法
时隔两周, 几乎忘记了要怎么解决了. 一开始想, 不管有没有方向, 只要发现两个节点连接上了, 如果尝试再连接一次, 就是有问题. 做法就是, 假如有一条 s1 -》 t1 的边, 这时候就将 s1 和 t1 连接在同一个集合, 只要之后的 s -> t 没有在同一个集合就添加进来, 并判断一下在 father 数组里是否出现死循环. 然而判断死循环会比较麻烦一些. 当然这个想法我没有去验证行不行, 因为直接看了文章解释, 所以就跳过自己的想法了.
看完代码随想录之后的想法
这一题比我想象的复杂一些. 破坏树结构一共有三种方式, 一种是整棵树成一个环, 一种是环外还有其他的路连接到环上, 一种是没有环但是有某个节点入度超过1. 对于环外还有其他路连接到环上, 着也是入度超过1的情况.
因此:
1. 需要先记录下所有的边, 同时在记录所有边的过程中, 统计每条有向边对应的目标节点的入度.
2. 根据题意只有一条边造成了有向树被破坏, 因此如果存在入度为 2 的节点, 则指向这个节点的两条边中, 删除其中一条即可恢复有向树.
3. 第 “2” 步中, 如果存在入度为 2 的节点, 删除进入该节点的其中一条边后如果不存在环, 则说明删除有效, 否则则需要删除另外一条边. 而判断环需要使用到并查集. 因为如果存在环的话, 连接尾首的那条边, 在并查集中就会发现首尾两个节点已经在集合中, 并返回发现环.
4. 第 “2” 步中, 如果不存在入度为 2 的节点, 则存在环了, 这时候需要通过并查集找出形成环的最后一条边, 删除这条边即可.
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
public class Main {
private static List<int[]> edges = new ArrayList<>();
private static int[] indegree;
// ====> 并查集部分 开始 <====
private static int nodeCount;
private static int[] father;
// ====> 并查集部分 结束 <====
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
nodeCount = scanner.nextInt();
indegree = new int[nodeCount + 1];
int fromNode;
int toNode;
for (int i = 0; i < nodeCount; i++) {
fromNode = scanner.nextInt();
toNode = scanner.nextInt();
edges.add(new int[]{fromNode, toNode});
indegree[toNode]++;
}
}
// 保存的是边的索引, 后加入的边在前
// 根据题意, 最多只有一个节点入度为 2, 且入度最大为 2
List<Integer> twoDegress = new ArrayList<>();
int[] edge = null;
for (int i = edges.size() - 1; i >= 0; i--) {
edge = edges.get(i);
if (indegree[edge[1]] != 2) {
continue;
}
twoDegress.add(i);
}
if (twoDegress.size() > 0) {
if (isTreeAfterRemoveEdge(twoDegress.get(0))) {
edge = edges.get(twoDegress.get(0));
} else {
edge = edges.get(twoDegress.get(1));
}
System.out.println(edge[0] + " " + edge[1]);
return;
}
getRemoveEdge();
}
private static boolean isTreeAfterRemoveEdge(int edgeIndex) {
init();
int[] edge = null;
for (int i = 0; i < edges.size(); i++) {
if (edgeIndex == i) {
continue;
}
edge = edges.get(i);
if (isSame(edge[0], edge[1])) {
return false;
}
join(edge[1], edge[0]);
}
return true;
}
private static void getRemoveEdge() {
init();
int[] edge;
for (int i = 0; i < edges.size(); i++) {
edge = edges.get(i);
if (isSame(edge[0], edge[1])) {
// System.out.println("222 " + edge[0] + " " + edge[1]);
return;
}
join(edge[1], edge[0]);
}
}
private static void init() {
// 并查集数据初始化
if (father == null) {
father = new int[nodeCount + 1];
}
for (int i = 1; i <= nodeCount; i++) {
father[i] = i;
}
}
private static boolean isSame(int u, int v) {
return find(u) == find(v);
}
private static int find(int u) {
if (u == father[u]) {
return u;
}
return father[u] = find(father[u]);
}
private static void join(int u, int v){
u = find(u);
v = find(v);
if (u == v) {
return;
}
father[v] = u;
}
}
自己实现过程中遇到哪些困难
无. 尝试着想一些其他的解决方案, 比如一旦发现成环, 则开始删除边, 但是对于具体要删除哪条边, 变得不容易判断.