2021-12-20 每日打卡:难题精刷
写在前面
“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。
1192. 查找集群内的「关键连接」
这个题表达一下,就是寻找联通无向图的桥。顺便吐槽一嘴,其实我的集合论图论还记着,近世代数是真·一塌糊涂,烦,为了考试疲于求生了属于是。哎,到时候抽出时间来再复习得了。这个题使用Tarjan算法,我们先复习一下,集合论的相关知识啊。
- 割点:去掉点后支数变多(至少大1)
- 充要条件:v是割点 <-> 存在与v不同的两个顶点w和u,使得v在每一条uw路上 <-> 存在一个二划分{U,W},uw每条路都经过v
- 性质:最长路的两个端点/叶子不是割点
- 桥:去掉边后支数变多(1)
- 充要条件:x是桥 <-> x不在G的任一圈上 <-> 存在不同顶点每条路都经过x <-> 存在二划分
- 割集:去掉边,能分开,且最少
- k个支的图去掉割集后变成k+1个支(连通图去掉割集后分成两个支)
- 割集没有割集子集
- 连通图中,每个割集至少包含生成树的一条边(从连通->不连通)
- 连通图中,每个圈与G的任一割集有偶数条公共边(进圈出圈)
- 相对树的最小割集:即所有相对生成树的桥生成的集合
当然,这里边涉及的还有:
- 通道和闭通道:顶点和边的的交替序列;起始顶点和结束顶点相同的通道
- 迹(简单通路)和闭迹(简单回路):边不重复的通道和闭通道
- 路(初级通路)和圈(初级回路/闭路):顶点不重复的通道和闭通道。路和圈一定属于迹和闭迹。
总结一下!圈就是我们通常看到的一个环啊,不在环上的边就是桥!
桥就是我们这里要求的东西!
然后我们就用代码来表达一下,取一个点然后开始dfs,如果碰到已经访问过的点,那就是环!我们怎么区分是第几次来到这个环呢,就是标记一下,最后把每个环上最小的标记作为整个环上的标记。在回溯的过程中,就统一了整个环的标记。
class Solution:
def criticalConnections(self, n: int, connections: List[List[int]]) -> List[List[int]]:
# 建图
graph = collections.defaultdict(list)
for conn in connections:
graph[conn[0]].append(conn[1])
graph[conn[1]].append(conn[0])
# 建id和返回值
ids, res = [-1] * n, []
# 定义深度优先
def dfs(cur_node, cur_id, par):
# 在探索之前,给你个id
# 而我的id最终应该取决于所有和我相邻的点的关系
# 这里给的id只是默认不是环的情况
ids[cur_node] = cur_id
# 探索相邻节点
for next_node in graph[cur_node]:
# 忽略顺着找到自己的那个点
if next_node == par:
continue
# 相邻的点,第一次访问,取最小
# 子结点的id取决于dfs的结果
elif ids[next_node] == -1:
ids[cur_node] = min(dfs(next_node,cur_id+1,cur_node),ids[cur_node])
# 有相邻的点,不是第一次访问,取最小,同化自己就可以
# 也是递归的尽头,找到相邻的点已经被访问过咯
else:
ids[cur_node] = min(ids[cur_node],ids[next_node])
# 整理完的最终id == cur_id(不是子结点同化过来的,而是父节点分配的)
# 是环的脑袋
# 另外不要忘记 排除特殊(0,0)
if ids[cur_node] == cur_id and cur_node != 0:
res.append((par,cur_node))
return ids[cur_node]
dfs(0,0,0)
return res