图
图的题笔试爱出,今年也不多,考察就数组就行
图难的地方在于,你已经会了的算法,但是由于图的表达方式一变,你的算法也得改变,因此得有一个万能的格式来表达图。
图的表达方式
- 邻接表
如果有路径长度,则在节点上添加一个数据项即可, 一行一个链表形状 - 邻接矩阵
自己到自己0,节点不通就是∞,节点相通就是路径长度
public class Graph {
public HashMap<Integer,Node> nodes;//(编号,具体点)点集
public HashSet<Edge> edges;//边集
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
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 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<>();
}
}
广度优先遍历(BFS)
只需要点
1,利用队列实现
2,从源节点开始依次按照宽度进队列,然后弹出
3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
4,直到队列变空
//从node出发,BFS整个图
public static void bfs(Node node) {
if (node == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> set = new HashSet<>();//防止重复进入队列
queue.add(node);
set.add(node);//进入一次队列,就得在set里注册一遍
//可换成数组,1~1000值代表节点城市
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.value);
for (Node next : cur.nexts) {
if (!set.contains(next)) {
set.add(next);
queue.add(next);
}
}
}
}
深度优先遍历(DFS)
只需要点
1,利用栈实现
2,从源节点开始把节点按照深度放入栈,然后弹出
3,每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
4,直到栈变空
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)) {//未访问过连同cur都重新放回去,再在set里注册
stack.push(cur);//重新压栈是为了等这条路走尽了,回来时看当前节点还有没有其他路
stack.push(next);
set.add(next);
System.out.println(next.value);
break;//先逮着一条路走到黑
}
}
}
}
拓扑排序算法
需要图了
要求:有向图,且有入度为0的节点,且没有环
(在config里常用,依赖包编译的顺序)
B依赖E,C,D; A依赖B,C,D.
所以,E,C,D编译完成才能编译B
分析:找入度为零的点,删除它及其它的影响,再找入度为零的点,循环
// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
//key:某一个node value:其剩余的入度
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);//原始入度为零 入队列
}
}
//拓扑排序的结果,依次加入result
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算法
图解:以边的角度来
并查集 结构实现算法(并查集后续再看,本文使用其他方法替代)
分析: 把最小的边加上,无环加上,有环放弃(重点是如何知到其有无环)
各自为一个集合,新加进来的看两个人在不在一个集合里,不在合并(无环),在就放弃(就会产生环)
public static class MySet{//并查集比这个快, 同样实现三个方法
//当前节点, 它所在的集合
public HashMap<Node, List<Node>> setMap;
//构造函数,每个节点各自为一个集合
public MySet(List<Node> nodes){
for(Node cur : nodes){
List<Node> set = new ArrayList<Node>();//创建它自己的集合
set.add(cur);
setMap.put(cur, set);//记录
}
}
//是否在同一个集合
public boolean isSameSet(Node from, Node to){
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
return fromSet == toSet;
}
//合并到同一个集合
public void union(Node from, Node to){
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
for(Node toNode : toSet){
fromSet.add(toNode);//toSet里的元素加入formSet里
setMap.put(toNode, fromSet);//同时录入setMap
}
}
}
//--------------------kruskalMST--------------------------------//
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) {//M条边
priorityQueue.add(edge);//O(logM)
}
Set<Edge> result = new HashSet<>();
while (!priorityQueue.isEmpty()) {//M条边
Edge edge = priorityQueue.poll();//O(logM)
if (!unionFind.isSameSet(edge.from, edge.to)) {//O(1)
result.add(edge);
unionFind.union(edge.from, edge.to);
}
}
return result;
}
prim算法
图解:选个起始点不断把其他的拉进来
HashSet就行,一个一个加进来, 不用一下合并
public static Set<Edge> primMST(Graph graph) {
//解锁的边进入小根堆
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
HashSet<Node> set = new HashSet<>();//考察过的点就在这,一个set就替代集合结构
Set<Edge> result = new HashSet<>();//依次挑选的边在result里
for (Node node : graph.nodes.values()) {//森林;处理多个图各自不连通,让其各自成为最小生成树(1个图可以不要for,随便拿一个点,)
if (!set.contains(node)) {
//node是起始点
set.add(node);
for (Edge edge : node.edges) {//由一个点,解锁其相连的所有的边,全放入小根堆
priorityQueue.add(edge);
}
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();//弹出解锁的边中的最小边
Node toNode = edge.to;//看下最小边的to节点,是否是新节点
if (!set.contains(toNode)) {//不含,就是新的点
set.add(toNode);
result.add(edge);
for (Edge nextEdge : toNode.edges) {//把to节点的所有边,放入优先级队列
priorityQueue.add(nextEdge);//边会重复,不影响; 因为重复的边的to节点已经访问过了不是新的节点,最终重复的边会弹出,不会影响结果 }
}
}
}
}
return result;
}
Dijkstra算法
图解:
适用范围:环的权值累加和不能为负数,越转越小(floyd),不存在负权边
找最小边的通路
改进: 用自定义改进堆实现, 如果进入堆之后,不许要改值,只让他弹顶-用系统堆。
否则,用自己写的堆
//从head出发到所有节点的最小距离
public static HashMap<Node, Integer> dijkstra1(Node head) {
//key: 从head出发到达key
//value: 从head出发到达key的最小距离
//如果表中无T的记录, 即head和T的距离为 ∞
HashMap<Node, Integer> distanceMap = new HashMap<>();
distanceMap.put(head, 0);//自己个儿
HashSet<Node> selectedNodes = new HashSet<>();//已经求过距离的节点, 存入selectedNodes, 再也不碰
Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//distanceMap找最小且未选择过的节点,找不到return null
while (minNode != null) {
int distance = distanceMap.get(minNode);
for (Edge edge : minNode.edges) {
Node toNode = edge.to;
if (!distanceMap.containsKey(toNode)) {//Map没有记录的边放进去
distanceMap.put(toNode, distance + edge.weight);
}
distanceMap.put(edge.to,
Math.min(distanceMap.get(toNode), distance + edge.weight));//然后,更新路径最小值(之前的路径,和经过当前节点得到的路径,Ec(先)和Ed(后))
}
selectedNodes.add(minNode);//这节点访问过了
minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//换下一个节点
}
return distanceMap;
}
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();
if (!touchedNodes.contains(node) && distance < minDistance) {
minNode = node;
minDistance = distance;
}
}
return minNode;
}