PS:算法并非原创,仅作个人学习记录使用,侵删。
题目描述
算法分析
这道题很明显和图论相关,并且涉及到顶点之间的关联关系,所以首先想到的思路还是并查集、深度优先遍历、广度优先遍历三种常见的解题方式。根据已知的数组条件构造出图之后,存在一个冗余的边界条件,可以从后往前进行排查。
不过,这里让我不由想起了最小生成树的相关算法,也可在产生最小生成树的时候,有意识地按照数组排列顺序的优先级进行判断。这就和并查集的思想相差无几了。
那就每次选择排列靠前的边,来连接两个不同的连通分量,相同的连通分量内部的顶点不再进行连接,将整个图形联通为整个连通分量,势必会剩下一条没有用的边,这条边就是输出结果。
博客大佬们的算法更加精简,最常见的就是并查集思想,当出现环的时候,直接返回结果(由题目的条件决定)。
此外,成环的判断还可以使用DFS、BFS思想。
还有利用拓扑关系的解法,利用顶点的度数和环的关系,判断成环边的存在。
代码实现
【C】
/**
* 算法思想: 并查集
*/
int find(int *arr , int i){//查找老大
return i==arr[i] ? i : (arr[i] = find(arr, arr[i]));
}
void un(int *arr, int i, int j){//联系两个并查集
int x = find(arr, i);
int y = find(arr, j);
arr[y] = x;
}
#define LEN 0xffff
int* findRedundantConnection(int** eds, int len,
int* edgesColSize, int* returnSize){
int i, j, arr[LEN], *ret=(int*)malloc(sizeof(int) * 2), max=0;
for(i=1; i<LEN; i++){//单独的连通分量
arr[i] = i;
}
for(i=0; i<len; i++){//遍历边数组
if(find(arr, eds[i][0]) == find(arr, eds[i][1])) {//如果查找到形成环的边,那么不用后续遍历,这就是我们要找的元素,
ret[0] = eds[i][0];
ret[1] = eds[i][1];
break;
}
un(arr, eds[i][0], eds[i][1]);//如果尚未形成环,那么合并两个并查集
}
*returnSize = 2;
return ret;
}
C语言参考网址
【C++】
/*
算法思想:并查集
*/
class Solution {
public:
int per[10005];
void init(){//初始化图中元素。连通分量都是自己
for(int i=0;i<=10000;i++) per[i]=i;
}
int find(int x){//迭代查找这组连通分量的组长
if(x==per[x]) return x;
return per[x]=find(per[x]);
}
bool C(int x,int y){//判断x和y是不是属于一个连通分量,并且合并不同的连通分量。
int xx=find(x);
int yy=find(y);
if(xx!=yy){
per[xx]=yy;
return true;
}
return false;
}
vector<int> findRedundantConnection(vector<vector<int>>& a) {
int len=a.size();
init();//初始化图的连通分量
vector<int>v;
for(int i=0;i<len;i++){//遍历边集合
if(!C(a[i][0],a[i][1])){//如果一条边没有形成环,那么合并两个连通分量即可
//如果一条边形成了环,那么构造结果,不再遍历
v.push_back(min(a[i][0],a[i][1]));//为了满足题目要求
v.push_back(max(a[i][0],a[i][1]));
break;
}
}
return v;//最终结果
}
};
【java】
/*
算法思想:并查集
*/
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int[] parent = new int[edges.length + 1];//组长数组
for (int i = 0; i < parent.length; i++) {//初始化组长数组,起始时每个顶点连通分量组长是它自己。
parent[i] = i;
}
int[] res = null;//结果数组
for(int[] edge : edges){//遍历边集合
int x = edge[0];//左侧顶点编号
int y = edge[1];//右侧顶点编号
while(x != parent[x]){
x = parent[x];
}//得到左侧顶点所在的连通分量的组长顶点编号
while(y != parent[y]){
y = parent[y];
}//得到右侧顶点所在的连通分量的组长顶点编号
if(x == y){// 若两个组长是相同,说明是同一个连通分量
res = edge;//这个形成环的边就是结果
}
else{//两者的组长不相同,说明是两个不同的连通分量,进行合并
parent[x] = y;
}
}//遍历所有边的集合,不断更新成环边(最后结果)
return res;// 返回最后一个导致有环的边
/*
之所以C语言解法和C++解法不需要遍历全部的集合,
而是只需要在找到一个成环边就跳出循环,并且也可得
到正确答案,其原因在于:连通分量的合并是按照边排
列顺序依次进行的,并且只存在一个成环边,所以找到
结果之后中途跳出就可以满足题目要求。
*/
}
}
【python】
#算法思想:拓扑关系
#先把度为 1 的节点找出,然后,不断的删除度为1节点的连接边,在最终节点中,如果边的两个节点度都大于1,则该边为冗余边。
from collections import deque
class Solution:
def findRedundantConnection(self, edges):
N = len(edges)
graph = {}#存储顶点和邻接顶点
#1. 存储图
for i in range(N):
if edges[i][0] not in graph:
graph[edges[i][0]] = []
graph[edges[i][0]].append(edges[i][1])
if edges[i][1] not in graph:
graph[edges[i][1]] = []
graph[edges[i][1]].append(edges[i][0])#建立图
# 2. 统计度 == 1
indegree = {}
queue = deque()#存储度为1的顶点
for node in graph.keys():#遍历图中的顶点
indegree[node] = len(graph[node])#某个点的邻接顶点个数
if len(graph[node]) == 1:#如果某点只和一个顶点连接,
queue.append(node)
while len(queue) > 0:#当队列不为空时
node = queue.popleft()
for other_node in graph[node]:
indegree[other_node] -= 1
if indegree[other_node] == 1:
queue.append(other_node)
# 3. 度大于1的节点的任意组合都是可以删除的边
for i in range(N-1, -1, -1):
start, end = edges[i][0], edges[i][1]
if indegree[start] > 1 and indegree[end] > 1:#当边的两侧顶点的度都是大于1的时候
return edges[i]#只有一个特殊边,所有不用更新结果,直接返回
return None