1)由点的集合和边的集合构成
2)虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达
3)边上可能带有权值
图结构的表达
1)邻接表法
2)邻接矩阵法
3)除此之外还有其他众多的方式
图的面试题如何搞定
1)先用自己最熟练的方式,实现图结构的表达
2)在自己熟悉的结构上,实现所有常用的图算法作为模板
3)把面试题提供的图结构转化为自己熟悉的图结构,再调用模板或改写即可
// 从node出发,进行宽度优先遍历
public static void bfs(Node start) {
if (start == null) {
return;
}
Queue queue = new LinkedList<>();
HashSet set = new HashSet<>();
queue.add(start);
set.add(start);
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);
}
}
}
}
public class Node {
public int value;
public int in;
public int out;
public ArrayList nexts;
public ArrayList edges;
public Node(int value) {
this.value = value;
in = 0;
out = 0;
nexts = new ArrayList<>();
edges = new ArrayList<>();
}
}
public static void dfs(Node node) {
if (node == null) {
return;
}
Stack stack = new Stack<>();
HashSet 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;
}
}
}
}
public class Node {
public int value;
public int in;
public int out;
public ArrayList nexts;
public ArrayList edges;
public Node(int value) {
this.value = value;
in = 0;
out = 0;
nexts = new ArrayList<>();
edges = new ArrayList<>();
}
}
图的拓扑排序算法
1)在图中找到所有入度为0的点输出
2)把所有入度为0的点在图中删掉,继续找入度为0的点输出,周而复始
3)图的所有点都被删除后,依次输出的顺序就是拓扑排序
要求:有向图且其中没有环
应用:事件安排、编译顺序
public class Node {
public int value;
public int in;
public int out;
public ArrayList nexts;
public ArrayList edges;
public Node(int value) {
this.value = value;
in = 0;
out = 0;
nexts = new ArrayList<>();
edges = new ArrayList<>();
}
public static class DirectedGraphNode {
public int label;
public ArrayList neighbors;
public DirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList();
}
}
// 提交下面的
public static ArrayList topSort(ArrayList graph) {
HashMap indegreeMap = new HashMap<>();
for (DirectedGraphNode cur : graph) {
indegreeMap.put(cur, 0);
}
for (DirectedGraphNode cur : graph) {
for (DirectedGraphNode next : cur.neighbors) {
indegreeMap.put(next, indegreeMap.get(next) + 1);
}
}
Queue zeroQueue = new LinkedList<>();
for (DirectedGraphNode cur : indegreeMap.keySet()) {
if (indegreeMap.get(cur) == 0) {
zeroQueue.add(cur);
}
}
ArrayList ans = new ArrayList<>();
while (!zeroQueue.isEmpty()) {
DirectedGraphNode cur = zeroQueue.poll();
ans.add(cur);
for (DirectedGraphNode next : cur.neighbors) {
indegreeMap.put(next, indegreeMap.get(next) - 1);
if (indegreeMap.get(next) == 0) {
zeroQueue.offer(next);
}
}
}
return ans;
}
public static class DirectedGraphNode {
public int label;
public ArrayList neighbors;
public DirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList();
}
}
// 提交下面的
public static class Record {
public DirectedGraphNode node;
public int deep;
public Record(DirectedGraphNode n, int o) {
node = n;
deep = o;
}
}
public static class MyComparator implements Comparator {
@Override
public int compare(Record o1, Record o2) {
return o2.deep - o1.deep;
}
}
public static ArrayList topSort(ArrayList graph) {
HashMap order = new HashMap<>();
for (DirectedGraphNode cur : graph) {
f(cur, order);
}
ArrayList recordArr = new ArrayList<>();
for (Record r : order.values()) {
recordArr.add(r);
}
recordArr.sort(new MyComparator());
ArrayList ans = new ArrayList();
for (Record r : recordArr) {
ans.add(r.node);
}
return ans;
}
public static Record f(DirectedGraphNode cur, HashMap order) {
if (order.containsKey(cur)) {
return order.get(cur);
}
int follow = 0;
for (DirectedGraphNode next : cur.neighbors) {
follow = Math.max(follow, f(next, order).deep);
}
Record ans = new Record(cur, follow + 1);
order.put(cur, ans);
return ans;
}
public static class DirectedGraphNode {
public int label;
public ArrayList neighbors;
public DirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList();
}
}
// 提交下面的
public static class Record {
public DirectedGraphNode node;
public long nodes;
public Record(DirectedGraphNode n, long o) {
node = n;
nodes = o;
}
}
public static class MyComparator implements Comparator {
@Override
public int compare(Record o1, Record o2) {
return o1.nodes == o2.nodes ? 0 : (o1.nodes > o2.nodes ? -1 : 1);
}
}
public static ArrayList topSort(ArrayList graph) {
HashMap order = new HashMap<>();
for (DirectedGraphNode cur : graph) {
f(cur, order);
}
ArrayList recordArr = new ArrayList<>();
for (Record r : order.values()) {
recordArr.add(r);
}
recordArr.sort(new MyComparator());
ArrayList ans = new ArrayList();
for (Record r : recordArr) {
ans.add(r.node);
}
return ans;
}
// 当前来到cur点,请返回cur点所到之处,所有的点次!
// 返回(cur,点次)
// 缓存!!!!!order
// key : 某一个点的点次,之前算过了!
// value : 点次是多少
public static Record f(DirectedGraphNode cur, HashMap order) {
if (order.containsKey(cur)) {
return order.get(cur);
}
// cur的点次之前没算过!
long nodes = 0;
for (DirectedGraphNode next : cur.neighbors) {
nodes += f(next, order).nodes;
}
Record ans = new Record(cur, nodes + 1);
order.put(cur, ans);
return ans;
}
// directed graph and no loop
public static List sortedTopology(Graph graph) {
// key 某个节点 value 剩余的入度
HashMap inMap = new HashMap<>();
// 只有剩余入度为0的点,才进入这个队列
Queue zeroInQueue = new LinkedList<>();
for (Node node : graph.nodes.values()) {
inMap.put(node, node.in);
if (node.in == 0) {
zeroInQueue.add(node);
}
}
List 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)总是从权值最小的边开始考虑,依次考察权值依次变大的边
2)当前的边要么进入最小生成树的集合,要么丢弃
3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
5)考察完所有边之后,最小生成树的集合也得到了
// Union-Find Set
public static class UnionFind {
// key 某一个节点, value key节点往上的节点
private HashMap fatherMap;
// key 某一个集合的代表节点, value key所在集合的节点个数
private HashMap sizeMap;
public UnionFind() {
fatherMap = new HashMap();
sizeMap = new HashMap();
}
public void makeSets(Collection nodes) {
fatherMap.clear();
sizeMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
sizeMap.put(node, 1);
}
}
private Node findFather(Node n) {
Stack path = new Stack<>();
while(n != fatherMap.get(n)) {
path.add(n);
n = fatherMap.get(n);
}
while(!path.isEmpty()) {
fatherMap.put(path.pop(), n);
}
return n;
}
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 aDai = findFather(a);
Node bDai = findFather(b);
if (aDai != bDai) {
int aSetSize = sizeMap.get(aDai);
int bSetSize = sizeMap.get(bDai);
if (aSetSize <= bSetSize) {
fatherMap.put(aDai, bDai);
sizeMap.put(bDai, aSetSize + bSetSize);
sizeMap.remove(aDai);
} else {
fatherMap.put(bDai, aDai);
sizeMap.put(aDai, aSetSize + bSetSize);
sizeMap.remove(bDai);
}
}
}
}
public static class EdgeComparator implements Comparator {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set kruskalMST(Graph graph) {
UnionFind unionFind = new UnionFind();
unionFind.makeSets(graph.nodes.values());
// 从小的边到大的边,依次弹出,小根堆!
PriorityQueue priorityQueue = new PriorityQueue<>(new EdgeComparator());
for (Edge edge : graph.edges) { // M 条边
priorityQueue.add(edge); // O(logM)
}
Set 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;
}
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;
}
}
最小生成树算法之Prim
1)可以从任意节点出发来寻找最小生成树
2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
6)当所有点都被选取,最小生成树就得到了
public static class EdgeComparator implements Comparator {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set primMST(Graph graph) {
// 解锁的边进入小根堆
PriorityQueue priorityQueue = new PriorityQueue<>(new EdgeComparator());
// 哪些点被解锁出来了
HashSet nodeSet = new HashSet<>();
Set result = new HashSet<>(); // 依次挑选的的边在result里
for (Node node : graph.nodes.values()) { // 随便挑了一个点
// node 是开始点
if (!nodeSet.contains(node)) {
nodeSet.add(node);
for (Edge edge : node.edges) { // 由一个点,解锁所有相连的边
priorityQueue.add(edge);
}
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll(); // 弹出解锁的边中,最小的边
Node toNode = edge.to; // 可能的一个新的点
if (!nodeSet.contains(toNode)) { // 不含有的时候,就是新的点
nodeSet.add(toNode);
result.add(edge);
for (Edge nextEdge : toNode.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
// break;
}
return result;
}
// 请保证graph是连通图
// graph[i][j]表示点i到点j的距离,如果是系统最大值代表无路
// 返回值是最小连通图的路径之和
public static int prim(int[][] graph) {
int size = graph.length;
int[] distances = new int[size];
boolean[] visit = new boolean[size];
visit[0] = true;
for (int i = 0; i < size; i++) {
distances[i] = graph[0][i];
}
int sum = 0;
for (int i = 1; i < size; i++) {
int minPath = Integer.MAX_VALUE;
int minIndex = -1;
for (int j = 0; j < size; j++) {
if (!visit[j] && distances[j] < minPath) {
minPath = distances[j];
minIndex = j;
}
}
if (minIndex == -1) {
return sum;
}
visit[minIndex] = true;
sum += minPath;
for (int j = 0; j < size; j++) {
if (!visit[j] && distances[j] > graph[minIndex][j]) {
distances[j] = graph[minIndex][j];
}
}
}
return sum;
}
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;
}
}
Dijkstra算法
1)Dijkstra算法必须指定一个源点
2)生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大
3)从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
4)源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了
public static HashMap dijkstra1(Node from) {
HashMap distanceMap = new HashMap<>();
distanceMap.put(from, 0);
// 打过对号的点
HashSet selectedNodes = new HashSet<>();
Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
while (minNode != null) {
// 原始点 -> minNode(跳转点) 最小距离distance
int distance = distanceMap.get(minNode);
for (Edge edge : minNode.edges) {
Node toNode = edge.to;
if (!distanceMap.containsKey(toNode)) {
distanceMap.put(toNode, distance + edge.weight);
} else { // toNode
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 distanceMap, HashSet touchedNodes) {
Node minNode = null;
int minDistance = Integer.MAX_VALUE;
for (Entry 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; // 实际的堆结构
// key 某一个node, value 上面堆中的位置
private HashMap heapIndexMap;
// key 某一个节点, value 从源节点出发到该节点的目前最小距离
private HashMap distanceMap;
private int size; // 堆上有多少个点
public NodeHeap(int size) {
nodes = new Node[size];
heapIndexMap = new HashMap<>();
distanceMap = new HashMap<>();
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
// 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance
// 判断要不要更新,如果需要的话,就更新
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]);
// free C++同学还要把原本堆顶节点析构,对java同学不必
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;
}
}
// 改进后的dijkstra算法
// 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回
public static HashMap dijkstra2(Node head, int size) {
NodeHeap nodeHeap = new NodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(head, 0);
HashMap 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;
}
// 点结构的描述
public class Node {
public int value;
public int in;
public int out;
public ArrayList nexts;
public ArrayList 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 static int networkDelayTime1(int[][] times, int n, int k) {
ArrayList> nexts = new ArrayList<>();
for (int i = 0; i <= n; i++) {
nexts.add(new ArrayList<>());
}
for (int[] delay : times) {
nexts.get(delay[0]).add(new int[] { delay[1], delay[2] });
}
PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]);
heap.add(new int[] { k, 0 });
boolean[] used = new boolean[n + 1];
int num = 0;
int max = 0;
while (!heap.isEmpty() && num < n) {
int[] record = heap.poll();
int cur = record[0];
int delay = record[1];
if (used[cur]) {
continue;
}
used[cur] = true;
num++;
max = Math.max(max, delay);
for (int[] next : nexts.get(cur)) {
heap.add(new int[] { next[0], delay + next[1] });
}
}
return num < n ? -1 : max;
}
// 方法二 : 加强堆的解法
public static int networkDelayTime2(int[][] times, int n, int k) {
ArrayList> nexts = new ArrayList<>();
for (int i = 0; i <= n; i++) {
nexts.add(new ArrayList<>());
}
for (int[] delay : times) {
nexts.get(delay[0]).add(new int[] { delay[1], delay[2] });
}
Heap heap = new Heap(n);
heap.add(k, 0);
int num = 0;
int max = 0;
while (!heap.isEmpty()) {
int[] record = heap.poll();
int cur = record[0];
int delay = record[1];
num++;
max = Math.max(max, delay);
for (int[] next : nexts.get(cur)) {
heap.add(next[0], delay + next[1]);
}
}
return num < n ? -1 : max;
}
// 加强堆
public static class Heap {
public boolean[] used;
public int[][] heap;
public int[] hIndex;
public int size;
public Heap(int n) {
used = new boolean[n + 1];
heap = new int[n + 1][2];
hIndex = new int[n + 1];
Arrays.fill(hIndex, -1);
size = 0;
}
public void add(int cur, int delay) {
if (used[cur]) {
return;
}
if (hIndex[cur] == -1) {
heap[size][0] = cur;
heap[size][1] = delay;
hIndex[cur] = size;
heapInsert(size++);
} else {
int hi = hIndex[cur];
if (delay <= heap[hi][1]) {
heap[hi][1] = delay;
heapInsert(hi);
}
}
}
public int[] poll() {
int[] ans = heap[0];
swap(0, --size);
heapify(0);
used[ans[0]] = true;
hIndex[ans[0]] = -1;
return ans;
}
public boolean isEmpty() {
return size == 0;
}
private void heapInsert(int i) {
int parent = (i - 1) / 2;
while (heap[i][1] < heap[parent][1]) {
swap(i, parent);
i = parent;
parent = (i - 1) / 2;
}
}
private void heapify(int i) {
int l = (i * 2) + 1;
while (l < size) {
int smallest = l + 1 < size && heap[l + 1][1] < heap[l][1] ? (l + 1) : l;
smallest = heap[smallest][1] < heap[i][1] ? smallest : i;
if (smallest == i) {
break;
}
swap(smallest, i);
i = smallest;
l = (i * 2) + 1;
}
}
private void swap(int i, int j) {
int[] o1 = heap[i];
int[] o2 = heap[j];
int o1hi = hIndex[o1[0]];
int o2hi = hIndex[o2[0]];
heap[i] = o2;
heap[j] = o1;
hIndex[o1[0]] = o2hi;
hIndex[o2[0]] = o1hi;
}
}
模板
// matrix 所有的边
// N*3 的矩阵
// [weight, from节点上面的值,to节点上面的值]
//
// [ 5 , 0 , 7]
// [ 3 , 0, 1]
//
public static Graph createGraph(int[][] matrix) {
Graph graph = new Graph();
for (int i = 0; i < matrix.length; i++) {
// 拿到每一条边, matrix[i]
int weight = matrix[i][0];
int from = matrix[i][1];
int 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;
}
public class Graph {
public HashMap nodes;
public HashSet edges;
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}