算法和数据结构-图

1. 图的实现

Graph.java

public class Graph {
    public HashMap<Integer, Node> nodes;  // 图中节点符号和实际节点的对应关系,节点可以由0、1、2指代,也可能由A、B、C指代
    public HashSet<Edge> edges;  // 图中所有的边
    public Graph() {
        this.nodes = new HashMap<>();
        this.edges = new HashSet<>();
    }
}

Edge.java

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;
    }
}

Node.java


```java
public class Node {
    public int value; // 图中节点的代表符号,如果用A、B等表示节点,这里需要改为String类型
    public int in;  // 入度
    public int out; // 出度
    public ArrayList<Node> nexts; // 对于无向图,存储的是邻接点;对于有向图,存储的是出去的边连接的点
    public ArrayList<Edge> edges; // 对于无向图,存储的是连接在该点上的边;对于有向图,存储的是出去的边
    public Node(int value) {
        this.value = value;
        this.in = 0;
        this.out = 0;
        this.nexts = new ArrayList<>();
        this.edges = new ArrayList<>();
    }
}
  • 创建了Graph类以及Graph中的Edge类和Node类后,可能会遇到许多表示图的方式,但是包含的信息全部都在Graph类中。不管表达方式怎么变,将这些方式中的信息填入到创建的Graph类中,就可在后续写图算法时更加方便。
  • 以其中一种图表达方式为例进行转换:N*3的矩阵,每一个数组表示[from节点的符号值,to节点的符号值,连接两个点的边的权重]
public static Graph createGraph(Integer[][] matrix) {
        Graph graph = new Graph();
        for (int i = 0; i < matrix.length; i++) {
            Integer from = matrix[i][0];
            Integer to = matrix[i][1];
            Integer weight = matrix[i][2];
            // 如果graph中没有存储该点
            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++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
	 return graph;
}

2. 图的宽度优先遍历

public class BFS {
    public static void bfs(Node node){
        if (node == null){
            return;
        }

        Queue<Node> queue = new LinkedList<>();
        // 假设在无向图中A点邻接点有B、C,将B、C放入队列后弹出A。继续往下遍历B点,由于A是B的邻接点,所以会将A再次放入队列中,导致无法跳出循环
        // 需要使用Set结构去避免上述情况
        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){
                // 避免重复放入
                if (!map.contains(next)){
                    map.add(next);
                    queue.add(next);
                }
            }
        }

    }
}

3. 图的深度优先遍历

public class DFS {
    public static void dfs(Node node) {
        if (node == null) {
            return;
        }
        // 栈保存的是bfs的路径
        Stack<Node> stack = new Stack<>();
        HashSet<Node> map = new HashSet<>();

        stack.add(node);
        map.add(node);

        System.out.println(node);

        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            for (Node next : cur.nexts) {
                if (!map.contains(next)) {
                    // 为了保证栈储存的是bfs的路径,每一个路径上的点需要重新压入栈中
                    stack.push(cur);
                    stack.push(next);
                    map.add(next);
                    System.out.println(next.value);
                    // 找到一个当前节点没有遍历过的邻接点后,直接break去再次执行while循环
                    // 假设当前节点为A,A的邻接点有B和C,下一次的while循环就是从B开始处理,不再管A的另一个邻接点C
                    break;
                }
            }
        }
    }
}

4. 拓扑排序

 拓扑排序:以项目中的依赖包为例,假设B包依赖A包,C包依赖A包和B包,D包依赖B包和C包,如何确定包的编译顺序?
 下图为一个有向无环图
          A --> B --> C --> D
          |     |     ↑     ↑
          ------|------     |
                -------------
      1. 找到入度为0的点,图中为A
      2.A点和A相关的边全部移除,A就称为第一个编译的包
      3. 再次重复1-2直到所有节点排好序
public class TopologySort {
    public static List<Node> sortedTopology(Graph graph){
        // key为node节点,value为该节点当前的入度
        HashMap<Node, Integer> inMap = new HashMap<>();

        // 存储入度为0的点
        Queue<Node> zeroInqueue = new LinkedList<>();

        // 1. 构建inMap并存储第一个入度为0的点
        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()){
            Node cur = zeroInqueue.poll();
            result.add(cur);
            // 2. 将和入度为0点相关的边全部移除
            for (Node next : cur.nexts){
                inMap.put(next, inMap.get(next)-1);
                if (inMap.get(next) == 0){
                    zeroInqueue.add(next);
                }
            }
        }

        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值