拓扑排序算法
举列子:
例如编译顺序,一般会先编译依赖,如果出现循环依赖(两个文件互相调用),则无法知道它们之间的编译顺序,于是会报错。
一个项目的项目结构就是一个无环的有向图(方向取决于依赖关系),而用拓扑排序排列图节点 就是其编译的顺序。
每一个文件所依赖的文件数,好比一个节点的入度,那么入度为0的节点 就排在最前面。
/**
* 拓扑排序算法
* 举列子:
* 例如编译顺序,一般会先编译依赖,如果出现循环依赖(两个文件互相调用),则无法知道它们之间的编译顺序,于是会报错。
* 其实整个项目结构就是一个无环的有向图(方向取决于依赖关系),而用拓扑排序排列图节点 就是其编译的顺序。
* 每一个文件所依赖的文件数,好比一个节点的入度,那么入度为0的节点 就排在最前面。
*/
topologySort(graph: Graph) {
let inMap = new Map<GNode, number>();
let zeroQuene = [];
let result = [];
for (let node of graph.nodes.values()) {
inMap.set(node, node.in);
if (node.in == 0) {
zeroQuene.unshift(node);
}
}
while (zeroQuene.length == 0) {
let node = zeroQuene.pop();
result.push(node);
for (let next of node.nexts) {
inMap.set(next, inMap.get(next) - 1);
if (inMap.get(next) == 0 && zeroQuene.indexOf(next) == -1)
zeroQuene.unshift(next);
}
}
return result;
}
无向图算法之
求图的最小生成树(保证连通图上每个节点,同时边的权值之和最小)
/**
* 无向图算法之 求图的最小生成树(保证连通图上每个节点,同时边的权值之和最小)
* 方法一:K算法,从最小的边开始考虑,如果加上该边后,生成了环,则表示该边多余,最小生成树不包括该边。
*/
kruskalMST(graph: Graph): Edge[] {
let mySets = new MySets(graph.nodes.values());
let priorityQuene = new PriorityQuene(new EdgeComparator());
let result = [];
for (let edge of graph.edges) {
priorityQuene.unshift(edge);//将图的所有边放入优先队列中(小根堆)
}
while (priorityQuene.length == 0) {
let edge = priorityQuene.pop(); //弹出权值最小的边
if (!mySets.isSameSet(edge.from, edge.to)) { //判断该边的起点和终点是否在一个集合内
result.push(edge); //不在一个集合内,该边可用
mySets.union(edge.from, edge.to); //将终点和起点的集合合并。
}
}
return result;
}
/**
* 方法二:P算法,遍历每个节点的边集,操作为:
* 1、将边集中的边放入优先队列中(根据权值大小形成小根堆),
* 2、将一个边T出列,判断list中是否存在边的终点,如果不存在,该边可以用。
* 3、由边T的to点解锁新的边集(to点的边集),继续将边集中的边放入优先队列中。
* 重复上述1、2、3操作,直到优先队列为空。
*/
primMST(graph: Graph): Edge[] {
let priorityQuene = new PriorityQuene(new EdgeComparator());
let result = [];
let list = [];
for (let node of graph.nodes.values()) {
//如果保证整个图是连通的,即可去掉上面的for循环。
//但是如果图是呈森林形态,即多个连通图互不连通,则必须用for,最后的结果也是多个连通图的各自最小树。
if (list.indexOf(node) == -1) list.push(node);
for (let edge of node.edges) { //由一个点,解锁所有与之相连的边
priorityQuene.unshift(edge); //放入优先队列中(小根堆)
}
while (priorityQuene.length != 0) {
let edge = priorityQuene.pop(); //弹出解锁的边中,权值最小的边
if (list.indexOf(edge.to) == -1) { //判断list中是否存在边的终点
result.push(edge); //如果不存在,该边可以用
list.push(edge.to);
}
for (let nextE of edge.to.edges) { //由该边的终点,继续解锁新的边
priorityQuene.unshift(nextE);
}
}
}
return result;
}
此处优先队列的类与方法如下,优先队列的入列使用的是小根堆的创建方式,优先队列出列用的堆排序。可以在往期的O(N*logN)排序那篇文章中看到。
/**
* 优先队列
*/
export class PriorityQuene {
quene: any[];
comparator: Comparator;
public get length() {
return this.quene.length;
}
constructor(comparator: Comparator) {
this.quene = [];
this.comparator = comparator;
}
unshift(any) {
this.quene.unshift(any);
this.heapify(this.quene, 0, this.quene.length, this.comparator.compare);
}
heapify(arr: any[], index: number, heapSize: number, comparator: Function) {
while ((index * 2 + 1) < heapSize) {
let miner = comparator(arr[index * 2 + 1], arr[index * 2 + 2]) || index * 2 + 2 >= heapSize ? index * 2 + 1 : index * 2 + 2;
if (comparator(arr[index], arr[miner])) break;
else {
this.swap(arr, index, miner);
index = miner;
}
}
}
pop() {
let heapSize = this.quene.length;
this.heapify(this.quene, 0, heapSize, this.comparator.compare); //O(logN)
this.swap(this.quene, 0, --heapSize);
return this.quene.pop();
}
swap(arr: any[], a: number, b: number) {
if (arr[b] == arr[a]) return;
arr[a] = arr[a] ^ arr[b];
arr[b] = arr[a] ^ arr[b];
arr[a] = arr[a] ^ arr[b];
}
}
另外k方法中的MySet类和其中的方法具体是这样的,其实是将所有的点作为键,将已连通的一片点放在同一个数组中作为值。当A片连通的点区与B片连通的点区中有点互相连通,则用union方法将A和B数组合并成一个。
export class MySets {
public map: Map<GNode, GNode[]>;
constructor(list: any) {
for (let node of list) {
let aSet = [];
aSet.push(node);
this.map.set(node, aSet);
}
}
public isSameSet(from: GNode, to: GNode) {
let fromSet = this.map.get(from);
let toSet = this.map.get(to);
return fromSet == toSet;
}
public union(from: GNode, to: GNode) {
let fromSet = this.map.get(from);
let toSet = this.map.get(to);
for (let node of toSet) {
fromSet.push(node); //将to点集合中的元素都放入from点集合中
this.map.set(node, fromSet); //将to点集合的指针指向from点集合
}
}
}
此外edge的比较器大致如下,比较的是两个edge的权值哪个更小
export abstract class Comparator {
abstract compare(o1: any, o2: any): boolean;
}
export class EdgeComparator extends Comparator {
public compare(o1: Edge, o2: Edge) {
return (o1.weight - o2.weight) < 0 ? true : false;
}
}
dijkstra单元最短路径算法
/**
* dijkstra单元最短路径算法
* 给定一个起点,求该点到所有点的最短距离
*/
dijkstra(head: GNode) {
//记录起点head到每个点的距离
//key:从head出发到达key
//value:从head出发到达key的最小距离
//如果在distMap中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
let distMap = new Map<GNode, number>();
distMap.set(head, 0) //head到自身的距离为0
//已经检查过自身到其他点的边 的节点,存入selectedNodes中,之后再也不碰
let selectedNodes = [];
let minNode = this.getMinDistAndUnselected(distMap, selectedNodes);
while (minNode != null) {
let dist = distMap.get(minNode);
for (let edge of minNode.edges) {
let toNode = edge.to;
if (!distMap.get(toNode)) {
distMap.set(toNode, dist + edge.weight);
}
distMap.set(toNode, Math.min(distMap.get(toNode), dist + edge.weight))
}
selectedNodes.push(minNode);
minNode = this.getMinDistAndUnselected(distMap, selectedNodes);
}
return distMap;
}
getMinDistAndUnselected(distMap: Map<GNode, number>, selectedNodes: GNode[]) {
let min = Number.MAX_VALUE;
let minNode;
for (let node of distMap.keys()) {
if (selectedNodes.indexOf(node) == -1) {
if (distMap.get(node) < min) {
min = distMap.get(node);
minNode = node;
}
}
}
return minNode;
}