Graph Introduction - HolmesJJ/Data-Structures-and-Algorithms GitHub Wiki
图Graph | 超图Hypergraph | 多重图Multigraph | |
---|---|---|---|
结点Nodes(或顶点Vertices) | 至少一个 | 至少一个 | 至少一个 |
边Edges(或弧Arcs) | 1. 每条边edge连接图中两个(=2)结点nodes(有的结点nodes可能没有边edges相连) 2. 每条边edge都是唯一的 |
1. 每条边edge连接图中超过两个(>=2)结点nodes(有的结点nodes可能没有边edges相连) 2. 每条边edge都是唯一的 |
1. 每条边edge连接图中两个(=2)结点nodes(有的结点nodes可能没有边edges相连) 2. 图中两个(=2)结点nodes之间的边edge可能多于一条(>=1) |
- V是结点nodes的集合
- 至少一个|V| > 0
- E是边edges的集合
- E ⊆ {(v, w) : (v ∈ V), (w ∈ V)}
- 定义一条从v到w的边 e = (v, w)
- 每条边edge都是唯一的 For all e1, e2 ∈ E : e1 ≠ e2






已连接Connected | 未连接Disconnected | 可到达Reachable / 不可到达Unreachable | |
---|---|---|---|
图例 | ![]() |
![]() |
![]() |
两个组件Component |
度Degree | 半径Diameter | |
---|---|---|
定义 | 相邻Adjacent边的数量 | 沿着最短路径的两个结点之间的最大距离 |
图例 | ![]() |
![]() |
图的度Degree为相邻Adjacent边的最大数量,上图的度为3 |
n:结点数
星Star | 团Clique / 完全图complete graph | 道Line(Path) | |
---|---|---|---|
定义 | 一个中心结点,所有其它结点与与中心连接 | 一个图中两两不同的结点之间都恰有一条边相连 | |
图例 | ![]() |
![]() |
![]() |
度 | n - 1 | n - 1 | 2 |
半径 | 2 | 1 | n - 1 |
环Cycle | 二分图Bipartite Graph | |
---|---|---|
定义 | 结点分为两个集合,同一集合中的结点之间没有边 注意:环也是一个二分图 |
|
图例 | ![]() |
![]() |
度 | 2 | |
半径 | n / 2 或 n / 2 -1 | 最大 n - 1 |
图Graph, 深度优先遍历(DFS), 广度优先遍历(BFS)【数据结构和算法入门9】
邻接矩阵与邻接表
邻接表AdjacencyList | 邻接矩阵AdjacencyMatrix |
---|---|
![]() |
![]() |
图的存储结构(邻接矩阵、边数组、邻接表、十字链表、邻接多重表)
浅析图的邻接矩阵进行平方运算的含义
图的邻接矩阵和邻接表的比较
public class AdjListNode {
// 图中每个结点在邻接表的下标
private int index;
// 每个结点之间的权重,无权图默认为0
private int weight;
public AdjListNode(int index) {
this.index = index;
this.weight = 0;
}
public AdjListNode(int index, int weight) {
this.index = index;
this.weight = weight;
}
public int getIndex() {
return index;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof AdjListNode) {
AdjListNode adjListNode = (AdjListNode)obj;
return this.getIndex() == adjListNode.getIndex();
} else {
return false;
}
}
}
public class Node<T> {
// 结点值
private T data;
// 邻接表中的头结点
private ArrayList<AdjListNode> root;
public Node(T data) {
this.data = data;
this.root = new ArrayList<>();
}
public void add(int index, int weight) {
AdjListNode adjListNode = new AdjListNode(index, weight);
// 若结点不存在,则添加结点
if (!root.contains(adjListNode)) {
root.add(adjListNode);
}
// 若结点已存在,则更新权重
else {
root.get(root.indexOf(adjListNode)).setWeight(weight);
}
}
public void remove(int index) {
// 要转型,否则remove的是下标为index的元素
// override了AdjListNode的equals方法,确保index一样即代表AdjListNode一样
root.remove(new AdjListNode(index));
}
public int size() {
return root.size();
}
public ArrayList<AdjListNode> getRoot() {
return root;
}
public T getData() {
return data;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Node<?>) {
Node<?> node = (Node<?>)obj;
return this.getData().equals(node.getData());
} else {
return false;
}
}
}
public class AdjacencyList<T> {
private ArrayList<Node<T>> nodes;
// 结点v的数量
public AdjacencyList(ArrayList<Node<T>> nodes) {
this.nodes = nodes;
}
public int size() {
return nodes.size();
}
// 在下标为i的结点v和在下标为j的结点w之间添加一条边,权重为w
public void addEdge(int i, int j, int w) {
nodes.get(i).add(j, w);
}
// 在下标为i的结点v和在下标为j的结点w之间移除一条边
public void removeEdge(int i, int j) {
nodes.get(i).remove(j);
}
public ArrayList<Node<T>> getNodes() {
return nodes;
}
public void print() {
for (Node<T> node : nodes) {
System.out.println("这是顶点: " + node.getData() + " 的链表: ");
for (int j = 0; j < node.size() - 1; j++) {
System.out.print(nodes.get(node.getRoot().get(j).getIndex()).getData() + " -> ");
}
if (node.size() > 0) {
System.out.print(nodes.get(node.getRoot().get(node.size() - 1).getIndex()).getData());
}
System.out.println();
}
}
}
public class AdjacencyMatrix {
// 邻接矩阵
private int[][] adjMatrix;
// n代表结点数
public AdjacencyMatrix(int n) {
adjMatrix = new int[n][n];
}
// 在结点v添加一条v与w相连的边
// 注意:这里只是代表v指向w的边,若是无向图,就需要再调用addEdge(w, v)
public void addEdge(int v, int w) {
adjMatrix[v][w] = 1;
}
// 在结点v移除一条v与w相连的边
// 注意:这里只是代表v指向w的边,若是无向图,就需要再调用removeEdge(w, v)
public void removeEdge(int v, int w) {
adjMatrix[v][w] = 0;
}
}
邻接表AdjacencyList | 邻接矩阵AdjacencyMatrix | |
---|---|---|
空间复杂度 | O(V + E) | O(V^2) |
环的空间复杂度 | O(V) | O(V^2) |
团的空间复杂度 | O(V + E) = O(V^2) | O(V^2) |
查询较快 | 找到v的所有相邻结点 枚举所有相邻结点 |
结点v和结点w是否相邻 |
查询较慢 | 结点v和结点w是否相邻 | 找到v的所有相邻结点 枚举所有相邻结点 |
- 若图中边e的数目远远小于结点v^2,则称作稀疏Sparse图,此时用邻接表表示比用邻接矩阵表示节省空间
- 若图中边e的数目接近于结点v^2,对于无向图接近于v*(v-1),称作稠密Dense图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜
- 定理:令G是一个有推广的邻接矩阵A的p阶标定图,则A^n的i行j列元素(aij)^n等于由vi到vj的长度为n的通道的数目
-
通俗描述:若邻接矩阵为A,则A^n中的A[i][j]表示的是从i出发走到点j走n步,有多少种走法
- 另B = A^2,B[c, d] >= 1 当且仅当 对于一些x,A[c, x] == A[x, d],代表两个结点(c和d代表同一个结点)和x之间有边相连
- 推论:设A为简单图G的邻接矩阵
- 则A2的元素(aii)^2是vi的度数,A3的元素(aii)^2是含vi的三角形的数目的两倍
- 若G是连通的,对于i ≠ j,vi与vj之间的距离是使A^n的(aij)^n ≠ 0的最小整数n
-
通俗描述:若邻接矩阵为A,则A^n中的A[i][j]表示的是从i出发走到点j走n步,有多少种走法
- 无向图的邻接矩阵和邻接表都要注意,每次添加或移除结点时,需要考虑两个方向,e = (v, w)和e = (w, v)都要操作
- 把一个题目转换成图,最重要是确定结点,边,边的方向,边权分别指代什么
算法:根据四色定理(Four color theorem),求出地图的所有着色方案
2阶魔方
结点 | 边 |
---|---|
魔方的一个状态 8个块的排列,每个块都有3个面暴露在外面 |
如果两个状态仅相差一步,则它们用边相连 转动90度 或 转动180度 |
结点的数量 = 8! ꞏ 3^8 = 264,539,520 | |
魔方是对称的,例如魔方当前状态为A,魔方可能有另一个状态B, 然而换个角度看魔方A就是魔方B,不需要拧动,这就出现了重复 有8 * 3 = 24种状态重复 |
|
结点的数量 = 8! * 3^8 / 24 = 7! * 3^7 = 11,022,480 |
- 只能转90度:半径 = 14,即最多14步可以复原魔方
- 转90度 或 转动180度:半径 = 11,即最多11步可以复原魔方
3阶魔方
- 结点的数量 ≈ 43 quintillion = 4300 亿亿
- 半径 = 20,即最多20步可以复原魔方
n阶魔方
- 半径 = Θ(n^2 / logn)
思想:n阶邻接矩阵 = PageRank
对PageRank算法的简单理解
3 * 3的板

结点 | 边 |
---|---|
棋盘的一个状态 九个滑块的排列 |
如果两个状态仅相差一步,则它们用边相连 |
结点的数量 = 9! = 362,880 | 边的数量 < 4*9! < 1,451,520 |
最终组成的图的最大的度是4,每个滑块的滑动方式最多只有4种,处于边缘的滑块则少于4种