7-加权无向图的遍历(DFS、BFS)和最小生成树(Prim、Kruskal)

无向图的遍历

DFS
  • 算法思想

深度优先搜索思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

  • 算法特点:栈

深度优先搜索是一个递归的过程。首先,选定一个出发点后进行遍历,如果有邻接的未被访问过的节点则继续前进。若不能继续前进,则回退一步再前进,若回退一步仍然不能前进,则连续回退至可以前进的位置为止。重复此过程,直到所有与选定点相通的所有顶点都被遍历。

深度优先搜索是递归过程,带有回退操作,因此需要使用存储访问的路径信息。当访问到的当前顶点没有可以前进的邻接顶点时,需要进行出栈操作,将当前位置回退至出栈元素位置,直到栈为空。

BFS
  • 算法思想

广度优先搜索思想:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

  • 算法特点: 队列

广度优先搜索类似于树的层次遍历,是按照一种由近及远的方式访问图的顶点。在进行广度优先搜索时需要使用队列存储顶点信息,每出队一个顶点,则将它的所有邻接的未入队的点放入队列,直到队列为空。

最小生成树

  • 生成树是加权图中包含所有顶点的无环连通子图,显然生成树只存在于连通图中,最小生成树(MST)是权值最小的生成树;
  • Prim 和 Kruskal 只能用于无向加权图中,用于生成MST,当存在相同权重的边时,MST可能不唯一。
Prim算法

Prim算法的基本思想是贪心算法,通过不断搜索最小横切边实现,算法实现的关键是用“优先队列”这种数据结构来表示横切边,其中又分为以下两种:

1. 延时实现 – 优先队列
2. 即时实现 – 索引优先队列
Kruskal算法

Kruskal算法的基本思想是先对边按照权值升序排序,每次取出最短的(借助于优先队列)然后借助UF算法不断加边并合并,直至形成生成树;E条边算法时间复杂度为ElogE

实现

/*
加权无向图,示例和代码不做特殊说明都是在连通图下,不适用于包含多个极大连通子图的图
 */

import java.util.*;

/**
 * 无向图的遍历
 */
public class UndirectedGraph {
   
   
    // 这里为了简单,用0开始连续的数字代表顶点(顶点ID),实际的实现中应该设计成类 Node 的形式
    private final int V;      // 顶点数目
    private int E;            // 边的数目
    private boolean[] marked; // 用于遍历时标记用
    private HashMap<Integer, TreeSet<Edge>> adj; // 邻接表用Hash表实现,key=Node_ID, value=相邻的边组成的链表(这里的实现是红黑二叉树)

    public UndirectedGraph(int v) {
   
   
        this.V = v;
        this.E = 0;
        this.adj = new HashMap<>();
        // 初始化哈希表
        for (int i = 0; i < V; i++) {
   
   
            adj.put(i,new TreeSet<>());
        }
    }

    public int getV() {
   
   
        return V;
    }

    public int getE() {
   
   
        return E;
    }

    public void addEdge(Edge e) {
   
   
        int from = e.from;
        int to = e.to;
        adj.get(from).add(e);
        adj.get(to).add(e);
        E++;
    }

    // 返回 v 点所有的连接边,Iterable<Edge>方便遍历
    Iterable<Edge> adj(int v) {
   
   
        return adj.get(v);
    }

    // 返回加权无向图中所有的边
    public Iterable<Edge> edges() {
   
   
        TreeSet<Edge> edges = new TreeSet<>();
        for (Map.Entry<Integer,TreeSet<Edge>> entry : adj.entrySet())
            edges.addAll(entry.getValue());
        return edges;
    }

    /*
    无向图的连通性
     */
    // DFS
    public Queue<Integer> depthFirstSearch() {
   
   
        marked = new boolean[V]; // 每次遍历之前懒惰初始化,每次都保证初始全是false
        LinkedList<Integer> outQueue = new LinkedList<>(); // 这个队列是为了方便输出

        dfs(0, outQueue);

        return outQueue;
    }

    private void dfs(int root, LinkedList<Integer> outQueue) {
   
   
        outQueue.offer(root); // offer <-> poll
        marked[root] = true;
        for (Edge edge : this.adj(root)) {
   
   
            int other = edge.getOther(root);
            if (!marked[other])
                dfs(other, outQueue);
        }
    }

    // BFS
    public Queue<Integer> breadthFirstSearch() {
   
   
        marked = new boolean[V]; // 每次遍历之前懒惰初始化,每次都保证初始全是false
        LinkedList<Integer> outQueue = new LinkedList<>();   // 这个队列是为了方便输出
        LinkedList<Integer> levelQueue = new LinkedList<>(); // 这个队列为了执行层次遍历逻辑

        levelQueue.offer(0); // 加入起点
        marked[0] = true;       // 标记起点为已入队点

        while (!levelQueue.isEmpty()) {
   
   
            Integer node = levelQueue.pop();
            outQueue.offer(node);      // 从层次队列中删除,并添加到输出队列中
            for (Edge edge : this.adj(node)) {
   
   
                int other = edge.getOther(node);
                if (!marked[other
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值