class TopSort {
// 邻接表
private readonly map: Set<number>[]
// 所有节点的入度
private readonly rd: number[]
// 节点数量k,边集合arr,是否使用序号0
constructor(k: number, arr?: number[][], hasZero: boolean = false) {
if (!arr) return this
// 初始化map和rd
this.rd = new Array(k + 1)
this.rd.fill(0)
this.map = new Array(k + 1)
for (let i = 0; i < k + 1; i++)
this.map[i] = new Set<number>()
// 构建邻接表及统计入度
for (const [a, b] of arr) {
// 连接ab
this.connect(a, b)
}
// 如果不从0开始,将0的入度设为-1
if (!hasZero) this.rd[0] = -1
}
// 连接ab
connect(a: number, b: number): void {
// 避免重复录边对入度产生影响
if (this.map[a].has(b)) return
this.map[a].add(b)
// b的入度+1
this.rd[b]++
}
// 计算拓扑排序,仅当res.length === k 时无环(排序有效)
getTopSort(): number[] {
let res: number[] = [], queue: number[] = []
// 将入度为0的点加入queue
for (let i = 0; i < this.rd.length; i++) {
if (!this.rd[i]) {
queue.push(i)
// 排过的点标记为-1,避免重复访问
this.rd[i] = -1
}
}
// 拓扑排序
while (queue.length) {
let size = queue.length
for (let i = 0; i < size; i++) {
let top = queue.shift()
res.push(top)
let topNext = this.map[top]
// 遍历当前节点邻接表,找到当前节点指向的其它节点,将入度-1
topNext.forEach((v) => {
this.rd[v]--
// 当该点入度为0时,加入队列,标记为-1
if (!this.rd[v]) {
queue.push(v)
this.rd[v] = -1
}
})
}
}
return res
}
}
/* 测试样例 */
~function test() {
let ts = new TopSort(3, [[1, 2], [3, 2]])
console.log(ts.getTopSort()) // [1, 3, 2]
ts = new TopSort(3, [[3, 2], [2, 1], [1, 3]])// 环图
console.log(ts.getTopSort()) // [] 注:结果长度不为k,表明该图有环,无法拓扑排序
}()