Warshall算法求传递闭包


Warshall 算法求传递闭包

传递闭包

传递闭包、即在数学中,在集合 X 上的二元关系 R 的传递闭包是包含 R 的 X 上的最小的传递关系。 例如,如果 X 是(生或死)人的集合而 R 是关系“为父子”,则 R 的传递闭包是关系“x 是 y 的祖先”。再比如,如果 X 是空港的集合而关系 xRy 为“从空港 x 到空港 y 有直航”,则 R 的传递闭包是“可能经一次或多次航行从 x 飞到 y”。

设 R 是集合A上的关系。连通性关系 R* 由形如(a,b)的有序对构成,使得在关系R中,从 a 到 b 之间存在一条长度至少为1的路径

假设二元关系 R 代表了纽约所有地铁站之间的直接到达关系,如 (a,b),(b,c) 代表可以从 a 直达b , 从 b 可以直达 c , 从a 也能间接到达 c。 而连通性关系则包涵了 (a,b),(b,c),(a,c) 这样的关系,可以认为是关系的深度挖掘,找出所有节点通过某个路径可到达的所有节点(一种多对多关系)。传递闭包和连通性关系可以被认为是等价的,一旦我们计算出了连通性关系R*也就求出了传递闭包。

关系R的连通性关系 R* = R ∪ R^2 ∪ R^3 … ∪ R^n n = 关系集合中的元素个数

至于延伸到矩阵中的运算这里不做展开。

原始的计算方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
* @description 计算 A B 布尔矩阵的布尔积
* @param a 待计算的 A 矩阵
* @param b 待计算的 B 矩阵
* @return 返回结果矩阵
*/
function booleanMartixProduct(a: number[][], b: number[][]): number[][] {
let aRow = a.length
let bCol = b[0].length
if (aRow !== bCol) {
console.log("无法计算矩阵")
return [[]]
}

let res = []
for (let i = 0; i < aRow; i++) {
res[i] = []
for (let j = 0; j < bCol; j++) {
res[i][j] = booleanVectorProduct(a[i], b.map(v => v[j]))
}
}

return res
}

/**
* @description 计算两个布尔向量的最终结果 如果a[i] = b[i] = 1 那么结果为1 否则为0
* @param {[number]} a 待计算的向量 A
* @param {[number]} b 待计算的向量 B
* @returns {number} 0 or 1
*/
function booleanVectorProduct(a: number[], b: number[]): number {
const lenA = a.length
const lenB = b.length

if (lenA !== lenB) {
console.log("向量布尔积计算失败");
return null
}

for (let i = 0; i < lenA; i++) {
if (a[i] === 1 && b[i] === 1) {
return 1
}
}

return 0
}

/**
* @description 计算两个布尔矩阵的集合运算 传入高阶函数
*
* @param {number[][]} a
* @param {number[][]} b
* @param {(a: number, b: number) => boolean} cb
* @returns
*/
function booleanMartixCalc(a: number[][], b: number[][], cb: (a: number, b: number) => boolean) {
let ai = a.length
let bi = b.length
let aj = a[0].length
let bj = b[0].length

if (ai !== bi || aj !== bj) {
console.log("无法计算");
}


let res = []
for (let i = 0; i < ai; i++) {
res[i] = []
for (let j = 0; j < aj; j++) {
res[i][j] = Number(cb(a[i][j], b[i][j]))
}
}

return res
}

/**
* @description 计算布尔矩阵的并 (Join)
* @param a
* @param b
*/
function booleanMartixJoin(a: number[][], b: number[][]): number[][] {
return booleanMartixCalc(a, b, (a, b) => a == 1 || b == 1)
}

/**
* @description 计算布尔矩阵的交 (Meet)
* @param a
* @param b
*/
function booleanMartixMeet(a: number[][], b: number[][]): number[][] {
return booleanMartixCalc(a, b, (a, b) => a == 1 && b == 1)
}

/**
* 计算一个n*n的矩阵的传递闭包
* 复杂度 O(n ^ 4)
*
* @param m
*/
function calcTransitiveClosure(m: number[][]): number[][] {
let a = m
let b = a
let n = m.length

for (let i = 1; i < n; i++) {
a = booleanMartixProduct(a, m) // 不断累加布尔积
b = booleanMartixJoin(a, b) // 做join运算
}

return b
}

console.log(calcTransitiveClosure([[1, 0, 1], [0, 1, 0], [1, 1, 0]]));

Warshell算法

Floyd-Warshall算法(Floyd-Warshall algorithm),中文亦称弗洛伊德算法,是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。

一个非常简单的动态规划算法,其核心理念为内部顶点限制的扩张。有点类似与一种广度优先扩张算法,不断的放宽条件进行迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function warshall(m: number[][]): number[][] {
let n = m.length
// m的复制
let w = m.map(v => v.slice())

for (let k = 0; k < n; k++) { // 内部顶点的限制
for (let i = 0; i < n; i++) { // i
for (let j = 0; j < n; j++) { // j
// 最核心的递推公式
// 情况1 在不把节点k放入内部顶点集合中时, m[i][j] 本身已经是一条可达路径,所以可以直接返回 1
// 情况2 在不把节点k放入内部顶点集合中时 m[i][k] m[k][j] 都可到达,新增的这个k节点让 w[i][j] 成为一条可以到达的路径 也返回1
// w 矩阵为 Wk 矩阵 也就是当前准备计算的矩阵 m 为上一个矩阵
w[i][j] = m[i][j] || (m[i][k] && m[k][j])
}
}
m = w
}

return w
}

文章作者: sfc9982
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 sfc9982 !
  目录