图的一些相关算法

拓扑排序算法

举列子:
例如编译顺序,一般会先编译依赖,如果出现循环依赖(两个文件互相调用),则无法知道它们之间的编译顺序,于是会报错。
一个项目的项目结构就是一个无环的有向图(方向取决于依赖关系),而用拓扑排序排列图节点 就是其编译的顺序。
每一个文件所依赖的文件数,好比一个节点的入度,那么入度为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;
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值