文章目录
1.图的存储方式
1)邻接表
2)邻接矩阵
如何表达图?生成图?
1.1 有向图与无向图
有向图:
无向图:
1.2 图的存储方式:邻接表与邻接矩阵
面试中经常见到的可能是一个矩阵的形式:
1.3 代码表示一张图(入度、出度概念也在此)
图:
public class Graph {
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<>();
}
}
边:
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 {
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];//from节点的序列
Integer to = matrix[i][2];//to节点的序列
if (!graph.nodes.containsKey(from)) {//先检查from节点存在否,不存在就建
graph.nodes.put(from, new Node(from));
}
if (!graph.nodes.containsKey(to)) {//再检查to节点存在否,不存在就建立
graph.nodes.put(to, new Node(to));
}
Node fromNode = graph.nodes.get(from);//拿出from点
Node toNode = graph.nodes.get(to);//拿出to点
Edge newEdge = new Edge(weight, fromNode, toNode);//建立新的边
fromNode.nexts.add(toNode);//from的邻接点增加了一个to节点
fromNode.out++;//from的出度加1
toNode.in++;//to节点的入度加1
fromNode.edges.add(newEdge);//from节点的边集增加
graph.edges.add(newEdge);//加到整个图的边集里
}
return graph;
}
}
2.宽度优先遍历
1,利用队列实现
2,从源节点开始依次按照宽度进队列,然后弹出
3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队 列
4,直到队列变空
代码:
public class Code_01_BFS {
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)) {//如果有某个邻接点已经在set里边了,就不再需要了
map.add(next);
queue.add(next);
}
}
}
}
}
3.深度优先遍历
1,利用栈实现
2,从源节点开始把节点按照深 度放入栈,然后弹出
3,每弹出一个点,把该节点下一个没有 进过栈的邻接点放入栈
4,直到栈变空
public class Code_02_DFS {
public 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)){//如果set中没有下一个的节点就继续
stack.push(cur);//先把当前的节点重新放进去栈中
stack.push(next);//再把下一个节点放进去栈中
set.add(next);//set集合中添加下一个节点
System.out.println(next.value);//打印下一个节点的值
break;//不再继续进行当前的这个循环了,重新回到while循环中,继续下一个节点的遍历先
}
}
}
}
}
4.拓扑排序算法
适用范围:要求有向图,且有入度为0的节点,且没有环
应用:
实现的逻辑就是:先找出入度为0的节点,然后把他们打印,并且打印完成之后就马上删除掉,然后继续搜索图,找出新的入度为0的节点,继续前边的操作,直到图被完全遍历。
public class Code_03_TopologySort {
// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
HashMap<Node, Integer> inMap = new HashMap<>();//记录所有入点
Queue<Node> zeroInQueue = new LinkedList<>();//记录所有入度为0的节点
for (Node node : graph.nodes.values()) {//values就是当前所有点的意思
inMap.put(node, node.in);//将入点全部登记
if (node.in == 0) {
zeroInQueue.add(node);//将所有入度为0的节点登记
}
}
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);//找出新的入度为0的点
if (inMap.get(next) == 0) {
zeroInQueue.add(next);
}
}
}
return result;
}
}
5.最小生成树以及相关算法
关于图的几个概念定义:
连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。
强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。
连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
下面介绍两种求最小生成树算法:
5.1 kruskal算法(K算法) 适用范围:要求无向图
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
- 把图中的所有边按代价从小到大排序;
- 把图中的n个顶点看成独立的n棵树组成的森林;
- 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
- 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
//undirected graph only
public class Code_04_Kruskal {
// 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;
}
}
5.2Prim算法(P算法)
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息,:
// undirected graph only
public class Code_05_Prim {
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set<Edge> primMST(Graph graph) {
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(
new EdgeComparator());
HashSet<Node> set = new HashSet<>();
Set<Edge> result = new HashSet<>();
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;
}
}
5.3 迪杰斯特拉算法
求解单元点的最短路径问题:给定带权有向图G和源点v,求v到G中其他顶点的最短路径
限制条件:图G中不存在负权值的边
迪杰斯特拉算法总共就干了两件事:
【1】不断运行广度优先算法找可见点,计算可见点到源点的距离长度
【2】从当前已知的路径中选择长度最短的将其顶点加入S作为确定找到的最短路径的顶点。
代码:
// no negative weight
public class Code_06_Dijkstra {
public static HashMap<Node, Integer> dijkstra1(Node head) {
HashMap<Node, Integer> distanceMap = new HashMap<>();
distanceMap.put(head, 0);
HashSet<Node> selectedNodes = new HashSet<>();
Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
while (minNode != null) {
int distance = distanceMap.get(minNode);
for (Edge edge : minNode.edges) {
Node toNode = edge.to;
if (!distanceMap.containsKey(toNode)) {
distanceMap.put(toNode, distance + edge.weight);
}
distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
}
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;
}
public static class NodeRecord {
public Node node;
public int distance;
public NodeRecord(Node node, int distance) {
this.node = node;
this.distance = distance;
}
}
public static class NodeHeap {
private Node[] nodes;
private HashMap<Node, Integer> heapIndexMap;
private HashMap<Node, Integer> distanceMap;
private int size;
public NodeHeap(int size) {
nodes = new Node[size];
heapIndexMap = new HashMap<>();
distanceMap = new HashMap<>();
this.size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public void addOrUpdateOrIgnore(Node node, int distance) {
if (inHeap(node)) {
distanceMap.put(node, Math.min(distanceMap.get(node), distance));
insertHeapify(node, heapIndexMap.get(node));
}
if (!isEntered(node)) {
nodes[size] = node;
heapIndexMap.put(node, size);
distanceMap.put(node, distance);
insertHeapify(node, size++);
}
}
public NodeRecord pop() {
NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
swap(0, size - 1);
heapIndexMap.put(nodes[size - 1], -1);
distanceMap.remove(nodes[size - 1]);
nodes[size - 1] = null;
heapify(0, --size);
return nodeRecord;
}
private void insertHeapify(Node node, int index) {
while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void heapify(int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
? left + 1 : left;
smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
if (smallest == index) {
break;
}
swap(smallest, index);
index = smallest;
left = index * 2 + 1;
}
}
private boolean isEntered(Node node) {
return heapIndexMap.containsKey(node);
}
private boolean inHeap(Node node) {
return isEntered(node) && heapIndexMap.get(node) != -1;
}
private void swap(int index1, int index2) {
heapIndexMap.put(nodes[index1], index2);
heapIndexMap.put(nodes[index2], index1);
Node tmp = nodes[index1];
nodes[index1] = nodes[index2];
nodes[index2] = tmp;
}
}
public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
NodeHeap nodeHeap = new NodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(head, 0);
HashMap<Node, Integer> result = new HashMap<>();
while (!nodeHeap.isEmpty()) {
NodeRecord record = nodeHeap.pop();
Node cur = record.node;
int distance = record.distance;
for (Edge edge : cur.edges) {
nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
}
result.put(cur, distance);
}
return result;
}
}