文章目录
一.图论与图
1.什么是图论
图论(英语:Graph theory),是组合数学的一个分支,和其他数学分支,如群论、矩阵论、拓扑学有着密切关系。
2.什么是图
图是图论的主要研究对象。图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系。
3.经典图论问题和应用
1.经典问题
- 四色问题
- 柯尼斯堡七桥问题
- 最小生成树问题
- 路径问题
2.应用
4.经典图论算法
- Prim算法
- Krusk算法
- Dijkstra算法
- Bellman-Ford算法
二.图的分类
1.无向图
无向图指的是图的边没有方向的分别,通常可以认为无向图实质上是同时拥有两个方向的有向图。
2.有向图
有向图指的是图的边有方向的分别。
3.无权图
无权图指的是图的边不带权重。
4.带权图
带权图指的是图的边拥有权重。
5.稀疏图
稀疏图指的是对于顶点总数来说,每个顶点的边远少于顶点总数。
6.稠密图与完全图
当大部分顶点的边数接近于顶点总数时可被称为稠密图;当每个顶点的边都链接了其余的节点,则被称为完全图。
三.图的连通性
1.连通性
上图中,拥有三个图的集合,它们之间并不是联通的,但是自身却是连通的。所以图具有连通性。
2.带环边与平行边
对于简单图来说,顶点与顶点之间只含有一条边;而对于复杂图来说,顶点之间的边有可能会形成自环或平行边:
自环边可以用来表示一个导向自身的路径,而平行边则可以表示导向其他顶点的多个路径。
四.图的表示
1.邻接矩阵
我们可以使用一个 V 乘 V 的布尔
二维数组来表示图。 当顶点 v 和顶点 w 之间有相连接的边
时, 定义 v 行 w 列的元素值为 true, 否则为
false。
- Graph1
/**
* @Auther: ARong
* @Date: 2020/2/29 11:34 上午
* @Description: 使用邻接矩阵实现的图结构
*/
public class Graph1 {
private int n; // 顶点数
private int m; // 边数
private boolean directed; // 是否为有向图
private boolean[][] graph; // 二维boolean数组表示的图结构
public Graph1(int n, boolean directed) {
assert n >= 0;
this.n = n;
this.directed = directed;
this.m = 0;
graph = new boolean[n][n];
}
/*
* @Author ARong
* @Description 向节点间添加一条边
* @Date 2020/2/29 11:39 上午
* @Param [v, m]
* @return void
**/
public void addEdge(int v, int m) {
assert v >= 0 && v < n;
assert m >= 0 && m < n;
// 如果有这条边则不添加
if (hasEdge(v, m)) {
return;
}
graph[v][m] = true;
// 如果是无向图,则还需要在另一方添加关系
if (!directed) {
graph[m][v] = true;
}
// 边数增加
m++;
}
/*
* @Author ARong
* @Description 判断v,m是否存在边
* @Date 2020/2/29 11:42 上午
* @Param [v, m]
* @return boolean
**/
private boolean hasEdge(int v, int m) {
return graph[v][m];
}
}
使用邻接矩阵来表示图有许多缺点,例如:
- 空间需要提前声明,如果图的节点很多,那么需要声明的空间太大,且不能动态拓展
- 不能表示平行边和自环关系
2.邻接表
我们可以使用一个以顶点为索引的列表数组, 其中的每个元素都是和该顶点相邻的顶点列表。
使用邻接表可以优化图的空间大小,但是一些操作的时间会比邻接矩阵实现的图耗费时间多。
- Graph2
/**
* @Auther: ARong
* @Date: 2020/2/29 11:55 上午
* @Description: 使用邻接表实现的图结构
*/
public class Graph2 {
private int n; // 顶点数
private int m; // 边数
private boolean directed; // 是否为有向图
private List<List<Integer>> graph; // 二维链表
public Graph2(int n, boolean directed) {
assert n >= 0;
this.n = n;
this.directed = directed;
this.m = 0;
graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
// 初始化
graph.add(new ArrayList<>());
}
}
/*
* @Author ARong
* @Description 向v,m之间添加一条边
* @Date 2020/2/29 12:01 下午
* @Param [v, m]
* @return void
**/
public void addEdge(int v, int m) {
assert v >= 0 && v < n;
assert m >= 0 && m < n;
// 如果有这条边则不添加
if (hasEdge(v, m)) {
return;
}
graph.get(v).add(m);
// 如果是无向图,则还需要在另一方添加关系
if (!hasEdge(v, m) && !directed) {
graph.get(m).add(n);
}
// 边数增加
m++;
}
/*
* @Author ARong
* @Description 判断m,v是否存在边
* @Date 2020/2/29 11:58 上午
* @Param [v, m]
* @return boolean
**/
private boolean hasEdge(int v, int m) {
List<Integer> list = graph.get(v);
for (Integer node : list) {
if (node == m) {
return true;
}
}
return false;
}
}
五.图的基本算法
0.基本代码框架
以下是无向图结构的基本代码框架,一下算法实现基于该代码框架
/**
* @Auther: ARong
* @Date: 2020/3/1 11:27 上午
* @Description: 使用邻接表实现的无向图
*/
public class UndirectedGraph {
// 顶点数量
private int vertexNum;
// 顶点边数
private int edgeNum;
// 邻接表
private List<List<Integer>> adj;
// 顶点信息
private List<String> vertexInfo;
/*
* @Author ARong
* @Description 传入节点信息构造图基本数据
* @Date 2020/3/1 11:29 上午
* @Param [vertexInfo]
* @return
**/
public UndirectedGraph(List<String> vertexInfo) {
this.vertexInfo = vertexInfo;
this.vertexNum = vertexInfo.size();
// 初始化图
adj = new ArrayList<>();
for (int i = 0; i < vertexNum; i++) {
// 构造每一个顶点的连接对象
adj.add(new LinkedList<>());
}
}
public UndirectedGraph(List<String> vertexInfo, int[][] edges) {
this(vertexInfo);
// 额外构造边信息
for (int[] twoVertex : edges) {
addEdge(twoVertex[0], twoVertex[1]);
}
}
public int vertexNum() {
return vertexNum;
}
public int edgeNum() {
return edgeNum;
}
/*
* @Author ARong
* @Description 添加边
* @Date 2020/3/1 11:31 上午
* @Param [i, j]
* @return void
**/
public void addEdge(int i, int j) {
adj.get(i).add(j);
adj.get(j).add(i);
edgeNum++;
}
// 不需要set,所以不用返回List,返回可迭代对象就够了
public Iterable<Integer> adj(int i) {
return adj.get(i);
}
/*
* @Author ARong
* @Description 获取第i个顶点的数据
* @Date 2020/3/1 11:32 上午
* @Param [i]
* @return java.lang.String
**/
public String getVertexInfo(int i) {
return vertexInfo.get(i);
}
/*
* @Author ARong
* @Description 获取第i个顶点的边数
* @Date 2020/3/1 11:32 上午
* @Param [i]
* @return int
**/
public int degree(int i) {
return adj.get(i).size();
}
/*
* @Author ARong
* @Description 获取顶点中最大的边数
* @Date 2020/3/1 11:33 上午
* @Param []
* @return int
**/
public int maxDegree() {
int max = 0;
for (int i = 0;i < vertexNum;i++) {
if (degree(i) > max) {
max = degree(i);
}
}
return max;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(vertexNum).append("个顶点, ").append(edgeNum).append("条边:\n");
for (int i = 0; i < vertexNum; i++) {
sb.append(i).append(": 【").append(adj.get(i)).append("】\n");
}
return sb.toString();
}
}
1.深度优先遍历
/*
* @Author ARong
* @Description 对图进行深度优先遍历
* @Date 2020/3/1 11:45 上午
* @Param []
* @return java.util.List<java.lang.Integer>
**/
private void dfs(int i, boolean[] visited) {
visited[i] = true;
// 获取到i所连接的节点列表
Iterator<Integer> iterator = adj(i).iterator();
while (iterator.hasNext()) {
Integer v = iterator.next();
if (!visited[v]) {
// v没有被访问过,继续搜索
dfs(v, visited);
}
}
}
2.求解连通量
/*
* @Author ARong
* @Description 获取图的连通分量
* @Date 2020/3/1 12:23 下午
* @Param []
* @return java.util.List<java.lang.Integer>
**/
public int ccount() {
int ccount = 0;
// 初始化访问数组,默认都是未访问过
boolean[] visited = new boolean[vertexNum];
// 获取图的邻接表
for (int i = 0; i < adj.size(); i++) {
if (!visited[i]) {
// 在未访问过的节点开始dfs
dfs(i, visited);
ccount++;
}
}
return ccount;
}
3.判断两个顶点是否连通
/*
* @Author ARong
* @Description 判断两个节点是否相互连通
* @Date 2020/3/1 12:37 下午
* @Param [a, b]
* @return boolean
**/
private boolean isConnected(int a, int b) {
// 先对a进行深度优先遍历,标示遍历过的节点,若此时b没有被标示到说明不连通
boolean[] visited = new boolean[vertexNum];
dfs(a, visited);
return visited[b];
}
4.寻找连通路径
/*
* @Author ARong
* @Description 找到a与b之间的一条连通路径
* @Date 2020/3/2 9:49 上午
* @Param [a, b]
* @return java.util.List<java.lang.Integer>
**/
public List<Integer> findPath(int a, int b) {
// 判断是否连通
if (!isConnected(a, b)) {
return null;
}
// 连通,通过dfs搜索,并使用数组记录路径
HashMap<Integer, Integer> map = new HashMap<>();
boolean[] visited = new boolean[vertexNum];
dfs(a, b, visited, map);
// 将路径重现
System.out.println(map.toString());
LinkedList<Integer> res = new LinkedList<>();
while (b != a) {
res.offerFirst(b);
b = map.get(b);
}
res.offerFirst(a);
return res;
}
/*
* @Author ARong
* @Description dfs搜索a到b的路径
* @Date 2020/3/2 9:56 上午
* @Param [a, b, visited, list]
* @return void
**/
private void dfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> map) {
// dfs终止条件,找到b
if (a == b) {
return;
}
visited[a] = true;
Iterator<Integer> iterator = adj(a).iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
// 继续dfs
if(!visited[next]) {
// 记录来时路径
map.put(next, a);
dfs(next, b, visited, map);
}
}
}
6.广度优先遍历
/*
* @Author ARong
* @Description 广度优先遍历
* @Date 2020/3/2 11:46 上午
* @Param [i, visited]
* @return void
**/
public void bfs(int i, boolean[] visited) {
LinkedList<Integer> queue = new LinkedList<>();
queue.offer(i);
visited[i] = true;
while (!queue.isEmpty()) {
Integer poll = queue.poll();
// 访问标记
Iterator<Integer> iterator = adj(poll).iterator();
while (iterator.hasNext()) {
// 获取poll的全部连接的顶点,继续bfs
Integer next = iterator.next();
if (!visited[next]) {
visited[next] = true;
queue.offer(next);
}
}
}
}
7.寻找最短路径
/*
* @Author ARong
* @Description 获取a和b之间的最短路径
* @Date 2020/3/2 11:57 上午
* @Param [a, b]
* @return java.util.List<java.lang.Integer>
**/
public List<Integer> findShortestPath(int a, int b) {
// 判断是否连通
if (!isConnected(a, b)) {
return null;
}
// 连通,通过dfs搜索,并使用数组记录路径
HashMap<Integer, Integer> from = new HashMap<>();
boolean[] visited = new boolean[vertexNum];
bfs(a, b, visited, from);
// 将路径重现
LinkedList<Integer> res = new LinkedList<>();
while (b != a) {
res.offerFirst(b);
b = from.get(b);
}
res.offerFirst(a);
return res;
}
/*
* @Author ARong
* @Description 广度优先遍历寻找最短路径
* @Date 2020/3/2 11:57 上午
* @Param [a, b, visited, from]
* @return void
**/
private void bfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> from) {
LinkedList<Integer> queue = new LinkedList<>();
queue.offer(a);
visited[a] = true;
while (!queue.isEmpty()) {
Integer poll = queue.poll();
// 访问标记
Iterator<Integer> iterator = adj(poll).iterator();
while (iterator.hasNext()) {
// 获取poll的全部连接的顶点,继续bfs
Integer next = iterator.next();
if (!visited[next]) {
// 记录来时路径
from.put(next, poll);
// 判断是否找到
if (next == b) {
return;
}
visited[next] = true;
queue.offer(next);
}
}
}
}