图的底层基础代码实现
基础接口
package _02_图._01_graph;
import java.util.List;
public interface Graph<V,E> {
int edgesSize();//边的数量
int verticesSize();//顶点数量
void addVertex(V v);//添加顶点
void addEdge(V from, V to);//添加边
void addEdge(V from, V to, E weight);//添加边
void removeVertex(V v);//删除顶点
void removeEdge(V from, V to);//删除边
interface vertexVisitor<V>{
boolean visit(V v);
}
}
完整源码
package _02_图._01_graph;
import java.util.*;
/**
* 邻接表实现图
*/
@SuppressWarnings("unchecked")
public class ListGraph<V, E> implements Graph<V, E> {
// 传入的V与顶点类Vertex的映射
private Map<V, Vertex<V, E>> vertices = new HashMap<>();
// 边的Set集合
private Set<Edge<V, E>> edges = new HashSet<>();
/**
* 顶点
*/
private static class Vertex<V, E> {
V value;
Set<Edge<V, E>> inEdges = new HashSet<>(); // 进来的边
Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的边
public Vertex(V value){
this.value = value;
}
@Override
public boolean equals(Object obj) {
return Objects.equals(value, ((Vertex<V, E>)obj).value);
}
@Override
public int hashCode() {
return value == null ? 0 : value.hashCode();
}
@Override
public String toString() {
return value == null ? "null" : value.toString();
}
}
/**
* 边
*/
private static class Edge<V, E> {
Vertex<V, E> from; // 出发点
Vertex<V, E> to; // 到达点
E weight; // 权值
public Edge(Vertex<V, E> from, Vertex<V, E> to) {
this.from = from;
this.to = to;
}
@Override
public boolean equals(Object obj) {
Edge<V, E> edge = (Edge<V, E>) obj;
return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);
}
@Override
public int hashCode() {
return from.hashCode() * 31 + to.hashCode();
}
@Override
public String toString() {
return "Edge [from=" + from + ", to=" + to + ", weight=" + weight + "]";
}
}
public void print(){
System.out.println("[顶点]-------------------");
vertices.forEach((V v, Vertex<V, E> vertex) -> {
System.out.println(v);
System.out.println("out-----------");
System.out.println(vertex.outEdges);
System.out.println("int-----------");
System.out.println(vertex.inEdges);
});
System.out.println("[边]-------------------");
edges.forEach((Edge<V, E> edge) -> {
System.out.println(edge);
});
}
@Override
public int edgesSize() {
return edges.size();
}
@Override
public int verticesSize() {
return vertices.size();
}
@Override
public void addVertex(V v) {
if(vertices.containsKey(v)) return;
vertices.put(v, new Vertex<>(v));
}
@Override
public void addEdge(V from, V to) {
addEdge(from, to, null);
}
@Override
public void addEdge(V from, V to, E weight) {
// 根据传入的参数from找到起点,如果不存在则创建
Vertex<V, E> fromVertex = vertices.get(from);
if(fromVertex == null){
fromVertex = new Vertex<>(from);
vertices.put(from, fromVertex);
}
// 根据传入的参数to找到终点,如果不存在则创建
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; // 有权值则加上权值,无权值则为null
// 不管原来是否存在,都先删除此边,再添加进去
if(fromVertex.outEdges.remove(edge)){
toVertex.inEdges.remove(edge);
edges.remove(edge);
}
fromVertex.outEdges.add(edge);
toVertex.inEdges.add(edge);
edges.add(edge);
}
@Override
public void removeVertex(V v) {
// 根据传入的值找到点并删除,不存在则不做操作
Vertex<V, E> vertex = vertices.remove(v);
if(vertex == null) return;
// 迭代器遍历集合vertex.outEdges, 删除所有从该点出去的边
for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next(); // 遍历到的该点出去的边
edge.to.inEdges.remove(edge);// 获取终点进入的边,并从中删除遍历到的边
iterator.remove(); // 将当前遍历到的元素edge从集合vertex.outEdges中删掉
edges.remove(edge);
}
// 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边
for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边
edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边
iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉
edges.remove(edge);
}
}
@Override
public void removeEdge(V from, V to) {
// 根据传入的from获得起点,不存在则不需要删除
Vertex<V, E> fromVertex = vertices.get(from);
if(fromVertex == null) return;
// 根据传入的to找到终点,不存在则不需要删除
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);
}
}
}
图的遍历
- 从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次
2种常见的遍历方式:
广度优先搜索
(Breadth First Search,BFS
),又称为宽度优先搜索、横向优先搜索深度优先搜索
(Depth First Search,DFS
)
接口中添加bfs
和dfs
方法:
package _02_图._01_graph;
import java.util.List;
public interface Graph<V,E> {
int edgesSize();//边的数量
int verticesSize();//顶点数量
void addVertex(V v);//添加顶点
void addEdge(V from, V to);//添加边
void addEdge(V from, V to, E weight);//添加边
void removeVertex(V v);//删除顶点
void removeEdge(V from, V to);//删除边
void bfs(V begin, vertexVisitor<V> visitor); // 广度优先搜索
void dfs(V begin, vertexVisitor<V> visitor); // 深度优先搜索
List<V> topologicalSort(); // 拓扑排序
interface vertexVisitor<V>{
boolean visit(V v);
}
}
BFS(广度优先搜索)(Breadth First Search)
- 二叉树层序遍历就是一种广度优先搜索。
- BFS结果不唯一
从某个点开始,将它可以到达的点放入队列,如果已经访问过则跳过,然后从队列中取出点重复该过程。
第一层:假设从点A开始,它可以到达B、F,则将B、F入队。
此时队列中元素 [B、F]
第二层:队头B出队,B可以到达C、I、G,将C、I、G入队。
此时队列中元素 [F、C、I、G]
第三层:队头F出队,F可以到达G、E,但G已访问过,将E入队。
此时队列中元素 [C、I、G、E]
第四层:队头C出队,C可以到达I、D,但I已访问过,将D入队。
此时队列中元素 [I、G、E、D]
第五层:队头I出队,I可以到达D,但D已访问过,不执行操作。
此时队列中元素 [G、E、D]
第六层:队头G出队,G可以到达D、H,但D已访问过,将H入队。
此时队列中元素 [E、D、H]
第七层:队头E出队,E可以到达D、H、F,都访问过,不执行操作。
此时队列中元素 [D、H]
第八层:队头D出队,D可以到达C、H、E,都访问过,不执行操作。
此时队列中元素 [H]
第九层:队头H出队,H可以到达D、G、E,都访问过,不执行操作。
此时队列中元素 []
队列为空,广度优先搜索结束。
代码:
/*
* 广度优先搜索BFS
* */
@Override
public void bfs(V begin, vertexVisitor<V> visitor) {
//根据传入的值begin找到顶点
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);
}
}
}
DFS (深度优先搜索)(Depth First Search)
- 二叉树前序遍历就是一种深度优先搜索
- DFS结果不唯一
递归实现DFS:
/**
* 递归实现深度优先搜索DFS
*/
@Override
public void dfs(V begin) {
Vertex<V, E> beginVertex = vertices.get(begin); // 根据传入的值获取顶点
if (beginVertex == null) return; // 顶点不存在则不执行操作
dfs(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
}
private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){
System.out.println(vertex.value);
vistedVertices.add(vertex);
for(Edge<V, E> edge : vertex.outEdges){
if(vistedVertices.contains(edge.to)) continue;
dfs(edge.to, vistedVertices);
}
}
非递归实现DFS:
/**
* 非递归实现深度优先搜索DFS
*/
public void dfs(V begin, vertexVisitor<V> visitor){
if(visitor == null) return;
Vertex<V, E> beginVertex = vertices.get(begin);// 根据传入的值获取顶点
if(begin == null) return;
Set<Vertex<V, E>> visitedVertices = new HashSet<>();// 传入的集合,用来记录访问过的顶点
Stack<Vertex<V, E>> stack = new Stack<>();
stack.push(beginVertex); // 先访问起点
visitedVertices.add(beginVertex);
if(visitor.visit(begin)) return;
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);
if(visitor.visit(edge.to.value)) return;
break;
}
}
}
总结
- 深度优先更适合目标比较明确,以找到目标为主要目的的情况;
- 广度优先更适合在不断扩大遍历范围时找到相对最优解的情况。