拓扑排序
对于有向无环图G=(V, E),拓扑排序指的是图的顶点V的一个排序,使得对于E中的每一条边(u, v),顶点u在排序中都出现在顶点v之前。
使用深度优先搜索进行拓扑排序
下面是使用深度优先搜索的拓扑排序
def DFS(G, node):
visited[node] = True
for v in G.ad_list(node):
if not visited[v]:
DFS(G, v)
sorted.insert(0, node)
def TopologicalSort(G):
# G是一个代表图的类,G.vertexes顶点列表,G.ad_list(node)是node的邻接列表
visited = {}
sorted = []
for v in G.vertexes:
visited[v] = False
for v in G.vertexes:
if not visited[i]:
DFS(G, v)
return sorted
第一个函数使用了递归形式的深度优先搜索。
当其中的for循环结束时,从node出发的边指向的点已经被放入了sorted,因此实现了拓扑排序。
第二个函数的第二个for循环保证了搜索所有的点,即使G不是连通图。
根据点的入度实现拓扑排序
下面是根据点的入度实现的拓扑排序
from collections import Counter, deque
def TopologicalSort(G):
degree = Counter()
for e in G.edges:
dgree[e[1]] += 1 # 记录e[1]的入度
sorted = []
q = deque([v for v in G.vertexes if degree[v] == 0])
# 初始化先进先出队列q,入度为零的顶点应当放到所有顶点的前面
while q:
node = q.popleft()
sorted.append(node) # 指向q中的第一个顶点的点应当已经在sorted中
for v in G.ad_list(node):
degree[v] -= 1
if degree[v] == 0:
# 只有入度为零的顶点可以进入队列
q.append(v)
return sorted
简单说明一下
将入度为零的顶点放入sorted表中,然后删除该顶点以及发出的边,然后又会有新的入度为零的,重复此过程。
显然该过程在所有的点被放入sorted中之前不会停止,若是停止了,说明删去上一批点后没有诞生新的入度为零的点,说明图G有环,这与假设相悖。
Leetcode第1632题
给你一个 m x n 的矩阵 matrix ,请你返回一个新的矩阵 answer ,其中 answer[row][col] 是 matrix[row][col] 的秩。
每个元素的 秩 是一个整数,表示这个元素相对于其他元素的大小关系,它按照如下规则计算:
- 秩是从 1 开始的一个整数。
- 如果两个元素 p 和 q 在 同一行 或者 同一列 ,那么:
- 如果 p < q ,那么 rank(p) < rank(q)
- 如果 p == q ,那么 rank(p) == rank(q)
- 如果 p > q ,那么 rank(p) > rank(q)
- 秩 需要越 小 越好。
题目保证按照上面规则 answer 数组是唯一的。
可以将矩阵转化为一个图。
在同一行或者同一列中,将相等的元素合并为一个点,然后将其按矩阵元素大小排序,相邻的元素之间从小的发出边指向大的。
这样,这个图拓扑排序中,在前面的元素小,在后面的元素大,每个元素只与同行同列的元素关联,在初始化秩为1的元素后,同行同列元素的相对位置就是相应的秩。
来自官方的解答代码如下
class Solution:
def matrixRankTransform(self, matrix: List[List[int]]) -> List[List[int]]:
m, n = len(matrix), len(matrix[0])
uf = UnionFind(m, n)
for i, row in enumerate(matrix):
# 合并一行中相同的点
num2indexList = defaultdict(list)
for j, num in enumerate(row):
num2indexList[num].append([i, j])
for indexList in num2indexList.values():
i1, j1 = indexList[0]
for k in range(1, len(indexList)):
i2, j2 = indexList[k]
uf.union(i1, j1, i2, j2)
for j in range(n):
# 合并一列中相同的点
num2indexList = defaultdict(list)
for i in range(m):
num2indexList[matrix[i][j]].append([i, j])
for indexList in num2indexList.values():
i1, j1 = indexList[0]
for k in range(1, len(indexList)):
i2, j2 = indexList[k]
uf.union(i1, j1, i2, j2)
degree = Counter()
# 用来计数点的入度
adj = defaultdict(list)
# 用相邻列表表示有向图中的边
for i, row in enumerate(matrix):
# 构造一行中的边
num2index = {}
for j, num in enumerate(row):
num2index[num] = (i, j)
sortedArray = sorted(num2index.keys())
for k in range(1, len(sortedArray)):
i1, j1 = num2index[sortedArray[k - 1]]
i2, j2 = num2index[sortedArray[k]]
ri1, rj1 = uf.find(i1, j1)
ri2, rj2 = uf.find(i2, j2)
degree[(ri2, rj2)] += 1
adj[(ri1, rj1)].append([ri2, rj2])
for j in range(n):
# 构造一列中的边
num2index = {}
for i in range(m):
num = matrix[i][j]
num2index[num] = (i, j)
sortedArray = sorted(num2index.keys())
for k in range(1, len(sortedArray)):
i1, j1 = num2index[sortedArray[k - 1]]
i2, j2 = num2index[sortedArray[k]]
ri1, rj1 = uf.find(i1, j1)
ri2, rj2 = uf.find(i2, j2)
degree[(ri2, rj2)] += 1
adj[(ri1, rj1)].append([ri2, rj2])
rootSet = set()
# 用一个集合来记录合并后的点
ranks = {}
for i in range(m):
for j in range(n):
ri, rj = uf.find(i, j)
rootSet.add((ri, rj))
ranks[(ri, rj)] = 1
q = deque([[i, j] for i, j in rootSet if degree[(i, j)] == 0])
# 初始化q用作拓扑排序的队列,添加入度为零的点
while q:
i, j = q.popleft()
for ui, uj in adj[(i, j)]:
degree[(ui, uj)] -= 1
if degree[(ui, uj)] == 0:
q.append([ui, uj])
ranks[(ui, uj)] = ranks[(i, j)] + 1
# 官解给出的是max(ranks[ui, uj], ranks[i, j] + 1),
# 但显然ranks[ui, uj] <= ranks[i, j] + 1
res = [[1] * n for _ in range(m)]
for i in range(m):
for j in range(n):
# 计算秩
ri, rj = uf.find(i, j)
res[i][j] = ranks[(ri, rj)]
return res
class UnionFind:
'''
这个类模拟图中的点,可以合并同行同列的相等元素为一个点
'''
def __init__(self, m, n):
self.m = m
self.n = n
self.root = [[[i, j] for j in range(n)] for i in range(m)]
# 初始化矩阵中各个元素所指向的点
self.size = [[1] * n for _ in range(m)]
# 初始化各个点(集合)的大小
def find(self, i, j):
# 找到合并之后的结果
ri, rj = self.root[i][j]
if [ri, rj] == [i, j]:
return [i, j]
self.root[i][j] = self.find(ri, rj)
return self.root[i][j]
def union(self, i1, j1, i2, j2):
ri1, rj1 = self.find(i1, j1)
ri2, rj2 = self.find(i2, j2)
if [ri1, rj1] != [ri2, rj2]:
# 将小的向大的合并,保证同行同列的相等元素合并后所指向的是同一个点
if self.size[ri1][rj1] >= self.size[ri2][rj2]:
self.root[ri2][rj2] = [ri1, rj1]
self.size[ri1][rj1] += self.size[ri2][rj2]
else:
self.root[ri1][rj1] = [ri2, rj2]
self.size[ri2][rj2] += self.size[ri1][rj1]