【左神】一周刷爆LeetCode,直击BTAJ等一线大厂必问算法面试题真题详解 【第五弹】

目录

基础第六课题目一:图的存储方式

基础第六课题目二:优先遍历

基础第六课题目三:拓扑排序

基础第六课题目四:kruskal算法

基础第六课题目五:prim算法

基础第六课题目六:Dijkstra算法


基础第六课题目一:图的存储方式

邻接表法表示无向图:

矩阵表法:

有向无环图:

图的难点在于:图的算法不难,但是图的结构有多种表达,在这类题的技巧在于,先用一种图结构a实现所有算法,把其他遇到的图结构转换成图结构a,在代入算法。

左神常用图结构:

public class Graph {
    //integer 是索引
    public HashMap<Integer,Node> nodes;
    public HashSet<Edge> edges;
​
    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

节点结构:

public class Node {
    public int value;
    public int in;//有多少个节点指向这个点
    public int out;//这个点指向有多少个其他点
    public ArrayList<Node> nexts;
    public ArrayList<Edge> edges;
​
    public Node(int value) {
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

边的结构:无向边只要2个反向边一拼就可以了

public class Edge {
    public int weight;//边的长度
    public Node from;
    public Node to;
​
    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

图接口的实现:

public class GraphGenerator {
    //matrix的每一行结构为:源城市编号,目标城市编号,距离
    //将它转换为我的图结构表示
    public static Graph createGraph(Integer[][] matrix) {
        Graph graph = new Graph();
        for (int i = 0; i < matrix.length; i++) {
            Integer weight = matrix[i][0];//距离
            Integer from = matrix[i][1];//源城市编号
            Integer to = matrix[i][2];//目标城市编号
            if (!graph.nodes.containsKey(from)) {
                //如果源城市第一次出现,则加入到图中,创立新节点
                graph.nodes.put(from, new Node(from));
            }
            if (!graph.nodes.containsKey(to)) {
                //如果目标城市第一次出现,则加入到图中,创立新节点
                graph.nodes.put(to, new Node(to));
            }
            Node fromNode = graph.nodes.get(from);//源城市节点
            Node toNode = graph.nodes.get(to);//目标城市节点
            //创建源城市与目标城市之间的边
            Edge newEdge = new Edge(weight, fromNode, toNode);
            //完善源城市节点信息
            fromNode.nexts.add(toNode);
            fromNode.out++;
            fromNode.edges.add(newEdge);
            //完善目标城市节点信息
            toNode.in++;
            //完善图结构信息
            graph.edges.add(newEdge);
        }
        return graph;
    }
}

基础第六课题目二:优先遍历

图的宽度优先遍历

0:46:00附近

public static void bfs(Node node) {
    if (node == null) {
        return;
    }
    //用来储存所有节点
    Queue<Node> queue = new LinkedList<>();
    //确保上面的队列储存的节点不要重复
    HashSet<Node> map = new HashSet<>();
    queue.add(node);
    map.add(node);
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        System.out.println(cur.value);
        for (Node next : cur.nexts) {
            //不重复,则添加到队列和set中
            if (!map.contains(next)) {
                map.add(next);
                queue.add(next);
            }
        }
    }
}

广度优先遍历

基础第六课题目三:拓扑排序

解决依赖之间,有互相依赖的情况,哪一个优先编译

依次找到入度为0的点,把它和它的指向都擦掉,如此循环:

// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
    //记录节点,及其入度
    HashMap<Node, Integer> inMap = new HashMap<>();
    //收集当前入度为0的节点
    Queue<Node> zeroInQueue = new LinkedList<>();
    for (Node node : graph.nodes.values()) {
        inMap.put(node, node.in);
        if (node.in == 0) {
            zeroInQueue.add(node);
        }
    }
    //记录节点输出顺序
    List<Node> result = new ArrayList<>();
    while (!zeroInQueue.isEmpty()) {
        //弹出一个当前入度为0的节点
        Node cur = zeroInQueue.poll();
        //加入到输出中
        result.add(cur);
        //将此节点的指向全部擦去,指向的节点入度全部减一
        for (Node next : cur.nexts) {
            inMap.put(next, inMap.get(next) - 1);
            //若入度减一后变为入度为0的节点,加入到收集队列中
            if (inMap.get(next) == 0) {
                zeroInQueue.add(next);
            }
        }
    }
    return result;
}

基础第六课题目四:kruskal算法

和prim功能一样,但出发点不同,kruskal从边的角度出发

消除多余的边,同时保证连通性

从最小的边开始考虑,加上这条边,看有没有形成环,有环就不加,没环就保留。

查看有没有环:集合查询和集合合并的机制,使用并查集

1:37:00附近

// Union-Find Set
public static class UnionFind {
    private HashMap<Node, Node> fatherMap;
    private HashMap<Node, Integer> rankMap;
​
    public UnionFind() {
        fatherMap = new HashMap<Node, Node>();
        rankMap = new HashMap<Node, Integer>();
    }
​
    private Node findFather(Node n) {
        Node father = fatherMap.get(n);
        if (father != n) {
            father = findFather(father);
        }
        fatherMap.put(n, father);
        return father;
    }
​
    public void makeSets(Collection<Node> nodes) {
        fatherMap.clear();
        rankMap.clear();
        for (Node node : nodes) {
            fatherMap.put(node, node);
            rankMap.put(node, 1);
        }
    }
​
    public boolean isSameSet(Node a, Node b) {
        return findFather(a) == findFather(b);
    }
​
    public void union(Node a, Node b) {
        if (a == null || b == null) {
            return;
        }
        Node aFather = findFather(a);
        Node bFather = findFather(b);
        if (aFather != bFather) {
            int aFrank = rankMap.get(aFather);
            int bFrank = rankMap.get(bFather);
            if (aFrank <= bFrank) {
                fatherMap.put(aFather, bFather);
                rankMap.put(bFather, aFrank + bFrank);
            } else {
                fatherMap.put(bFather, aFather);
                rankMap.put(aFather, aFrank + bFrank);
            }
        }
    }
}
//用于比较边长大小
public static class EdgeComparator implements Comparator<Edge> {
​
    @Override
    public int compare(Edge o1, Edge o2) {
        return o1.weight - o2.weight;
    }
​
}
​
public static Set<Edge> kruskalMST(Graph graph) {
    UnionFind unionFind = new UnionFind();
    unionFind.makeSets(graph.nodes.values());
    //此队列的作用是收集所有边,让边从小到大弹出
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
    for (Edge edge : graph.edges) {
        priorityQueue.add(edge);
    }
    Set<Edge> result = new HashSet<>();
    while (!priorityQueue.isEmpty()) {
        Edge edge = priorityQueue.poll();
        //如果这两个节点不在一个集合中,即没有形成环
        if (!unionFind.isSameSet(edge.from, edge.to)) {
            result.add(edge);
            //把这两个节点放到一个集合中
            unionFind.union(edge.from, edge.to);
        }
    }
    return result;
}

基础第六课题目五:prim算法

和kruskal功能一样,但出发点不同,prim从node的角度出发

1:49:00附近

假设从A开始,解锁了3条边,然后选最小的,就到了C,再解锁4条边,选最小的,到F,解锁2条边,选最小,到D,没有解锁新边,从剩余已解锁的边选最小的,到B,解锁一条新的边,到E。

public static Set<Edge> primMST(Graph graph) {
    // 把解锁的边放入小根堆,小根堆会从小到大将元素弹出,
    // 定义个比较器,用于比较边的大小
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(
            new EdgeComparator());
    // 考察过的点都放入这个集合里面
    HashSet<Node> set = new HashSet<>();
    Set<Edge> result = new HashSet<>();
    // values(),返回一个collection迭代器
    // 这个for循环是为了解决多个连通区域不连通的问题
    for (Node node : graph.nodes.values()) {//随便挑一个点开始
        // 如果这个点没考察过,记录到集合中
        if (!set.contains(node)) {
            set.add(node);
            // 遍历这个点的所有边,放入小根堆
            for (Edge edge : node.edges) {
                priorityQueue.add(edge);
            }
            while (!priorityQueue.isEmpty()) {
                // 小根堆的边从小到大一次弹出
                Edge edge = priorityQueue.poll();
                Node toNode = edge.to;
                // 看边指向的点是否考察过
                if (!set.contains(toNode)) {
                    set.add(toNode);
                    result.add(edge);
                    for (Edge nextEdge : toNode.edges) {
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
    }
    return result;
}

基础第六课题目六:Dijkstra算法

用于记录一个点出发,到其他点的最小距离,可以使用小根堆的优化。

// 从head出发到所有点的最小距离
public static HashMap<Node, Integer> dijkstra1(Node head) {
    // key : 从head出发到达key
    // value : 从head出发到达key的最小距离
    HashMap<Node, Integer> distanceMap = new HashMap<>();
    // 头结点到自己的距离为0
    distanceMap.put(head, 0);
    // 求过距离的节点,存在selectedNodes中,以后再也不碰,锁住了
    // head到selectedNode的距离不会再更新
    HashSet<Node> selectedNodes = new HashSet<>();
    // 一开始,所有节点都没锁,所以返回的节点必然是head
    Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    while (minNode != null) {
        int distance = distanceMap.get(minNode);
        for (Edge edge : minNode.edges) {
            Node toNode = edge.to;
            // 如果head到tonode的距离从来没有计算过,那么距离就是head到
            // 当前节点距离,加上当前节点到tonode距离
            if (!distanceMap.containsKey(toNode)) {
                distanceMap.put(toNode, distance + edge.weight);
            }
            // 如果head到tonode的距离计算过,那么距离就是(head到当前节点距离加上
            // 当前节点到tonode距离),或者(head到tonode距离距离)之间较小的一个
            distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
        }
        // 并将当前节点锁住
        selectedNodes.add(minNode);
        // 在没有被锁死的点中,再次找到离head距离最小的点
        minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    }
    return distanceMap;
}
// 在没有被锁死的点中,找到离head距离最小的点
public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, 
        HashSet<Node> touchedNodes) {
    Node minNode = null;
    int minDistance = Integer.MAX_VALUE;// 距离正无穷
    for (Entry<Node, Integer> entry : distanceMap.entrySet()) {
        Node node = entry.getKey();
        int distance = entry.getValue();
        // 如果head节点到当前终点节点距离的计算没有被锁死,并且距离是最小
        // 的,把这个点选出来,作为下一轮距离计算的起点
        if (!touchedNodes.contains(node) && distance < minDistance) {
            minNode = node;
            minDistance = distance;
        }
    }
    return minNode;

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖虎不秃头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值