持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第七天,点击查看活动详情。
最近在看左神的数据结构与算法,考虑到视频讲的内容和给出的资料的PDF有些出入,不方便去复习,打算出一个左神的数据结构与算法的笔记系列供大家复习,同时也可以加深自己对于这些知识的掌握,该系列以视频的集数为分隔,今天是第六篇:P9|详解前缀树和贪心算法
一、图的表示方式
图的存储方式有两种: <1>邻接表 <2>邻接矩阵 下面的所有表示图的方法都是邻接表法,而对于邻接矩阵,下面会有函把邻接矩阵转化为邻接表。
1、邻接表
- 邻接表把所有点与点之间的关系都记录在表中了,即有相邻的点就记录在表中
- 例如上图中:3结点 与 1结点 和 2结点相邻,因此第三行记录了结点1和结点2
2、邻接矩阵
- 这里使用布尔值0和1表示,有边和无边,1表示右连接,0表示无连接
- 特殊的:此处还能表示边的长度,那样就可以数值表示长度,0表示无连接
3、图的表示方法
<1> Node
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<>();
}
}
value:结点的值in:指向该结点的线的个数out:该结点指出的线的个数nexts:该节点指向的结点edges:与该结点有关的线段
<2> Edge
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;
}
}
weight:权重,一般不使用,也可以表示为线的长度from:起始结点to:终点结点,与起始结点一起用于表明结点之间的指向
<3> Graph
public class Graph {
public HashMap<Integer,Node> nodes;
public HashSet<Edge> edges;
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
nodes:所有的结点edges:所有的边
<4> GraphGenerator
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++;
toNode.in++;
fromNode.edges.add(newEdge);
graph.edges.add(newEdge);
}
return graph;
}
matrix:邻接矩阵,三个变量分别表示:权重、起始结点、终点结点- 转换步骤:
- 首先判断
from结点和to结点是否存在,如果不存在首先加到Graph.nodes中 - 然后拿到
from结点和to结点,构造Edge - 让
from结点out++;to结点in++ - 让
from结点nexts加上to结点,edges加上edge - 让
Graph.edges加上edge
- 首先判断
二、图的宽度优先遍历
1、实现思路
- 从根节点开始遍历,首先拿到根节点相连的结点
- 然后依次进入队列
- 然后任意结点在出队列的时候,就要把与该结点相邻的结点放入队列
- 但是可能会存在重复的结点入队的情况
- 因此在入队的时候,我们同时使用 HashSet 去记录每个入队的结点,在入队之前做判断是否入队过,这样就能避免结点重复入队
2、实现代码
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) {
if (!map.contains(next)) {
map.add(next);
queue.add(next);
}
}
}
}
三、图的深度优先遍历
1、实现思路
- 在选定出发点后,随机找一个相邻的结点前进
- 一直这样遍历,知道没有相邻的结点可以前进
- 就后退一步,查看是否有相邻的结点(而且没有去过)可以前进
- 如果还是没有,就继续倒回去
- 重复这个过程,知道所有的结点都被遍历到
2、图解
3、实现代码
public static void dfs(Node node) {
if (node == null) {
return;
}
Stack<Node> stack = new Stack<>();
HashSet<Node> set = new HashSet<>();
stack.add(node);
set.add(node);
System.out.println(node.value);
while (!stack.isEmpty()) {
Node cur = stack.pop();
for (Node next : cur.nexts) {
if (!set.contains(next)) {
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;
}
}
}
}
四、拓扑排序算法
适用范围:要求是有向图、且有入度
in为0的结点,而且没有环
1、实现思路
- 首先找到第一个入度为0的点,然后擦掉与入度为0的这个结点有关的所有线
- 此时就出现了第二个入度为0的点,同样的擦掉与第二个入度为0的点有关的所有线
- 依次类推进行输出,就是拓扑排序
2、实现代码
public static List<Node> sortedTopology(Graph graph) {
HashMap<Node, Integer> inMap = new HashMap<>();
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()) {
Node cur = zeroInQueue.poll();
result.add(cur);
for (Node next : cur.nexts) {
inMap.put(next, inMap.get(next) - 1);
if (inMap.get(next) == 0) {
zeroInQueue.add(next);
}
}
}
return result;
}
五、Kruskal算法
1、最小生成树
一个图中可能存在多条相连的边,假设图中有N个结点,最小生成树,就是在图中找(N-1)条边,把所有的结点连接起来,同时要保证边的权重和最小
2、实现思路
- 把所有边按权重排序,并且认为所有的点都是孤立的集合
- 然后连接权重最小的边,同时把两个点并为一个集合
- 然后找到权重第二小的边,先判断起始点和终点是否都存在于集合中,如果存在代表会成环,就找权重第三小的边
- 依次类推…………
- 这里使用到了并查集,老师没有展开讲解,但下面的代码使用的是并查集
3、代码实现
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;
}