1,什么是图?
- 图是由顶点(vertex)和边(edge)组成,通常表示为G = (V , E)
- G表示一个图,V是定点集,E是边集
- 顶点集V,可以是有穷且非空
- 任意两点之间都可以用边来表示他们之间的关系,边集E可以是空的
1.1,有向图
- 有向图的边是有明确方向的
1.2,有向无环图
- 如果一个有向图,从任意顶点出发,无法经过若干条边回到该顶点,那么就是一个有向无环图
1.3,无向完全图
- 无向完全图的任意两个顶点之间都存在边
- n个顶点的无向完全图有n(n-1)/2条边
1.4,有向完全图
- 有向完全图的任意两个顶点之间都存在方向相反的两条边
- n个顶点的有向完全图有n(n - 1)条边
1.5,有权图
- 有权图的边可以有权值
2,图的出度,入度
- 出度,入度适用于有向图
出度:一个顶点的出度为x,是指有x条边以该顶点为起点
例如:顶点11的出度为3
入度:一个顶点的入度为x,是指有x条边以该顶点为终点
例如:顶点11的入度为2
3,图的实现方案
图有两种实现方案:邻接矩阵,邻接表
3.1,邻接矩阵
邻接矩阵
- 一维数组存放顶点信息
- 二维数组存放边的信息
有向图和无向图的数据都可以存储,但是会比较浪费内存,适合稠密图的存储
3.2,邻接表
- 顶点的数据通过数组或者链表的形式存储
- 每个顶点,与之两连接的顶点可以通过链表形式存储
4,邻接表的代码实现
4.1,图的基础接口
//获取点的数量
int verticesSize();
//获取边的数量
int edgeSize();
//添加顶点
void addVertex(V v);
//删除顶点
void removeVertex(V v);
//从起点到终点,添加边和权值
void addEdge(V fromV, V toV);
void addEdge(V fromV, V toV, E weight);
//删除边
void removeEdge(V fromV, V toV);
4.2,顶点的定义
private static class Vertex<V,E> {
//顶点的value值
V value;
//创建入度集合
Set<Edge<V,E>> inEdges = new HashSet<>();
//创建出度集合
Set<Edge<V,E>> outEdges = new HashSet<>();
Vertex(V value) {
this.value = value;
}
//判断顶点的值是否相等
public boolean equals(Object obj) {
return Objects.equals(value, ((Vertex<V,E>) obj).value);
}
//获取value的哈希值
public int hashCode(){
return value == null ? 0 : value.hashCode();
}
}
4.3,边的定义
private static class Edge<V,E> {
//起点
Vertex<V, E> from;
//终点
Vertex<V, E> to;
//权值
E weight;
//判断 起点 到 终点的边,是否一样
public boolean equals(Object obj) {
//转化为边
Edge<V, E> edge = (Edge<V, E>) obj;
//判断边的 起点 到 终点,是否一样
return from.equals(edge.from) && to.equals(edge.to);
}
//获取哈希值
public int hashCode() {
//计算哈希值
return from.hashCode() * 31 + to.hashCode();
}
}
4.4,实现代码
//存储每次添加的。顶点的哈希值的链表
private Map<V, Vertex<V, E>> vertices = new HashMap<>();//存储每条边的集合
private Set<Edge<V, E>> edges = new HashSet<>();
//获取边的数量
public int edgesSize() {
return edges.size();
}
//获取顶点的数量
public int verticesSize() {
return verticesSize;
}
//添加顶点
public void addVertex(V v) {
//如果包含,就return
if (vertices.containKey(v)) return;
/添加到链表的 key和value
vertices.put(v,new Vertex<>(v));
}
//添加边
public void addEdge(V from, V to) {
addEdge(from, to, null);
}
//添加边,起点,终点,权值
public void addEdge(V from, V to, E weight) {
//获取起点
Vertex<V, E> fromVertex = vertives.get(from);
//如果为空,创建一个,添加到链表
if (fromVertex == null) {
fromVertex = new Vertex<>(from);
vertices.put(from, fromVertex);
}
//获取终点
Vertex<V, E> toVertex = vertices.get(to);
//如果为空,创建一个,添加到链表
if (toVertex == null) {
toVertex = new Vertex<>(to);
vertices.put(to, toVertex);
}
//通过 起点 和 终点,创建一条边
Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
//添加 权值
edge.weight = weight;
//删除起点的边存在,说明原来边存在。如果存在:先删除,后添加
if (fromVertex.outEdges.remove(edge)) {
//终点的入度要删除
toVertex.inEdges.remove(edge);
//边的集合要删除
edges.remove(edge);
}
//起点的出度添加
fromVertex.outEdges.add(edge);
//终点的入度添加
toVertex.inEdges.add(edge);
//边的集合添加
edges.add(edge);
}
//删除边
public void removeEdge(V from, V to) {
//链表获取起点,不存在return
Vertex<V, E> fromVertex = vertices.get(from);
if (fromVertex == null) return;
//链表获取终点,不存在return
Vertex<V, E> toVertex = vertices.get(to);
if (toVertex == null) return;
//根据 起点 和 终点,创建边
Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
//如果边之前存在,就删除边
if (fromVertex.outEdges.remove(edge)) {
toVertex.inEdges.remove(edge);
edges.remove(edge);
}
}
//删除顶点
public void removeVertex(V v) {
//判断要删除的顶点是否存在,如果存在且不为空,就遍历起点和终点删除
Vertex<V, E> vertex = vertices.remove(v);
if (vertex == null) return;
//适代器,遍历删除
for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.to.inEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.outEdges中删掉
iterator.remove();
edges.remove(edge);
}
for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.from.outEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.inEdges中删掉
iterator.remove();
edges.remove(edge);
}
}
5,图的遍历
从图中的某一顶点出发,访问图中其余顶点,且每隔顶点仅被访问一次
图的遍历有两种实现方式:广度优先搜索 和 深度优先搜索
5.1,广度优先搜索
- 之前所学的二叉树层序遍历就是一种广度优先搜索
思路
- 假如从A开始遍历
- 第一层就是A,第二层就是A的相连顶点,B,F
- 第三层就是B,F的相连顶点F,C,I,G
- 如图,依次向下层遍历,最后遍历完每一层的节点
我们打印出来的每层节点,不会重复的打印
代码实现
public void bfs(V begin, VertexVisitor<V> visitor) {
//存储顶点链表为null,return
if (visitor == null) return;
//获取广度优先搜索的开始顶点
Vertex<V, E> beginVertex = vertices.get(begin);
if (beginVertex == null) return;
// 创建遍历过的顶点的集合
Set<Vertex<V, E>> visitedVertices = new HashSet<>();
//创建队列
Queue<Vertex<V, E>> queue = new LinkedList<>();
//开始顶点入队
queue.offer(beginVertex);
//存储,遍历过的顶点
visitedVertices.add(beginVertex);
//循环遍历
while (!queue.isEmpty()) {
//顶点出队
Vertex<V, E> vertex = queue.poll();
if (visitor.visit(vertex.value)) return;
//遍历顶点的所有 边的出度
for (Edge<V, E> edge : vertex.outEdges) {
//如果遍历过,继续向下遍历
if (visitedVertices.contains(edge.to)) continue;
//没有遍历过,入队
queue.offer(edge.to);
//记录遍历过的 顶点
visitedVertices.add(edge.to);
}
}
}
5.2,深度优先搜索
因为不确定,先遍历哪一条路径,所以遍历出来的结果会不一样
1,递归实现
private void dfs2(Vertex<V, E> vertex, Set<Vertex<V, E>> visitedVertices) {
//打印顶点的值
System.out.println(vertex.value);
//记录 遍历过的顶点
visitedVertices.add(vertex);
//遍历顶点的 所有边的出度
for (Edge<V, E> edge : vertex.outEdges) {
//如果遍历过,继续走下一条出度
if (visitedVertices.contains(edge.to)) continue;
//递归遍历
dfs2(edge.to, visitedVertices);
}
}
2,非递归实现
-
用栈来实现
public void dfs(V begin) { Vertex<V, E> beginVertex = vertices.get(begin); if (beginVertex == null) return; Set<Vertex<V, E>> visitedVertices = new HashSet<>(); Stack<Vertex<V, E>> stack = new Stack<>(); // 先访问起点 stack.push(beginVertex); visitedVertices.add(beginVertex); System.out.println(beginVertex.value); while (!stack.isEmpty()) { Vertex<V, E> vertex = stack.pop(); for (Edge<V, E> edge : vertex.outEdges) { if (visitedVertices.contains(edge.to)) continue; stack.push(edge.from); stack.push(edge.to); visitedVertices.add(edge.to); System.out.println(edge.to.value); break; } }
}