一、图的基本概念
1、图的组成及应用场景
图由顶点(vertex)和边(edge)组成,通常表示为 G = (V, E) ,G表示一个图,V是顶点集,E是边集 。顶点集V有穷且非空 ,任意两个顶点之间都可以用边来表示它们之间的关系,边集E可以是空的。
下面就是图的应用:
2、图的分类及基本概念
有向图:边有明确方向的图。
无向图:所有边都没有方向的图。
混合图:既有有方向的边又有无方向的边的图。
有向无环图:从任意顶点出发无法经过若干条边回到该顶点的图。
无向完全图:任意两个顶点之间都存在边的图。n个顶点的无向完全图有(n-1) + (n-2) + (n-3) + ... + 2 + 1条边
。
有向完全图:任意两个顶点之间都存在两条相反边的图。n个顶点的有向完全图有n x (n-1)条边
。
入度:以该点为终点的所有边的条数。
出度:以该点为起点的所有边的条数。
连通图:无向图任意两个顶点之间都可相互抵达(直接或间接都算)的图。
强连通图:有向图任意两个顶点之间都可相互抵达(直接或间接都算)的图。
连通分量:无向图的极大连通子图。
强连通分量:有向图的极大强连通子图。
二、图的构建
1、图的设计
2、接口代码
public interface Graph<E, V> {
int edgesSize();
int verticesSize();
void addVertex(E e);
void addEdge(E from, E to, V weight);
void addEdge(E from, E to);
void removeVertex(E e);
void removeEdge(E from, E to);
void bfs(E e);
void dfs(E e);
}
3、注意点
① 怎么维护边的信息和顶点的信息?顶点信息(顶点出度边、入度边、外部传的对象)可用哈希表维护---势必要重写hashCode和equals函数,边信息(起点、终点、权值)用集合足以。
② 怎么删除一条边或一个顶点?删除边的时候先判断便是否存在,存在就从起点的出度边和终点的入度边中分别删除,再从总集合中删除即可。删除顶点的时候,需要先遍历该顶点的出度边,将所有出度边终点的入度边集合中删除这条边,同时也要删除该顶点的出度边,相当于变遍历边删除,可以用java官方的迭代器完成。
4、搭建图的代码
public class ListGraph<E, V> implements Graph<E, V> {
private Map<E, Vertex<E, V>> vertices;
private Set<Edge<E, V>> edges;
private static class Vertex<E, V> {
E element;
Set<Edge<E, V>> outEdges = new HashSet<>();
Set<Edge<E, V>> inEdges = new HashSet<>();
public Vertex(E element) {
this.element = element;
}
@Override
public String toString() {
return "Vertex{" +
"element=" + element + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vertex<?, ?> vertex = (Vertex<?, ?>) o;
return Objects.equals(element, vertex.element);
}
@Override
public int hashCode() {
return Objects.hash(element);
}
}
private static class Edge<E, V> {
Vertex<E, V> from, to;
V weight;
public Edge(Vertex<E, V> from, Vertex<E, V> to) {
this.from = from;
this.to = to;
}
public EdgeInfo<E, V> castToInfo() {
EdgeInfo<E, V> info = new EdgeInfo<>();
info.setFrom(this.from.element);
info.setTo(this.to.element);
info.setWeight(this.weight);
return info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edge<?, ?> edge = (Edge<?, ?>) o;
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.element +
", to=" + to.element +
", weight=" + weight +
'}';
}
}
public ListGraph() {
edges = new HashSet<>();
vertices = new HashMap<>();
}
@Override
public int edgesSize() {
return edges.size();
}
@Override
public int verticesSize() {
return vertices.size();
}
@Override
public void addVertex(E e) {
if (vertices.get(e) != null)
return;
vertices.put(e, new Vertex<>(e));
}
@Override
public void addEdge(E from, E to, V weight) {
Vertex<E, V> fromVertex = vertices.get(from);
Vertex<E, V> toVertex = vertices.get(to);
if (fromVertex == null) {
fromVertex = new Vertex<>(from);
vertices.put(from, fromVertex);
}
if (toVertex == null) {
toVertex = new Vertex<>(to);
vertices.put(to, toVertex);
}
Edge<E, V> 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);
}
@Override
public void addEdge(E from, E to) {
addEdge(from, to, null);
}
@Override
public void removeVertex(E e) {
Vertex<E, V> vertex = vertices.get(e);
if (vertex == null)
return;
//先遍历顶点的出边进行相应删除(边遍历边删除需要用迭代器)
for (Iterator iterator = vertex.outEdges.iterator(); iterator.hasNext(); ) {
Edge<E, V> edge = (Edge<E, V>) iterator.next();
edge.to.inEdges.remove(edge);
iterator.remove(); //将当前遍历到的元素从遍历的集合中删除
edges.remove(edge);
}
//再遍历顶点的入边进行相应删除(边遍历边删除需要用迭代器)
for (Iterator iterator = vertex.inEdges.iterator(); iterator.hasNext(); ) {
Edge<E, V> edge = (Edge<E, V>) iterator.next();
edge.from.outEdges.remove(edge);
iterator.remove(); //将当前遍历到的元素从遍历的集合中删除
edges.remove(edge);
}
}
@Override
public void removeEdge(E from, E to) {
Vertex<E, V> fromVertex = vertices.get(from);
Vertex<E, V> toVertex = vertices.get(to);
if (fromVertex == null || toVertex == null)
return;
Edge<E, V> edge = new Edge<>(fromVertex, toVertex);
if (edges.remove(edge)) {
fromVertex.outEdges.remove(edge);
toVertex.inEdges.remove(edge);
}
}
}
二、深度优先搜索
1、递归版思路
先访问起点,再依次对起点的出度边进行深度优先搜索。为了避免重复访问,还需要增加一个函数参数,用来记录已经访问过的顶点,访问过的结点就不再访问。当然递归结束的条件就是到达的顶点没有出度边为止。
2、递归版实现
@Override
public void dfs(E e) {
Vertex<E, V> vertex = vertices.get(e);
if (vertex == null)
return;
dfs(vertex, new HashSet<Vertex<E, V>>());
}
/**
* 递归版深搜
*
* @param vertex 起点
* @param visitedVertices 访问过的顶点集合
*/
private void dfs(Vertex<E, V> vertex, Set<Vertex<E, V>> visitedVertices) {
System.out.println(vertex.element);
visitedVertices.add(vertex);
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
dfs(edge.to, visitedVertices);
}
}
3、非递归版思路
利用栈模拟递归。先将起点入栈并访问,再从栈顶弹出起点,以后每次当栈顶弹出顶点的时候立即选择一条该点的出度边(终点未访问过),将这条边的起点、终点依次压入栈中,并访问该终点。如此反复,直到栈为空。这里也需要一个集合记录访问过的顶点。
4、非递归版实现
/**
* 非递归版深搜
*
* @param beginVertex 起点
* @param visitedVertices 访问过的顶点集合
*/
private void dfs2(Vertex<E, V> beginVertex, Set<Vertex<E, V>> visitedVertices) {
Stack<Vertex<E, V>> stack = new Stack<>();
stack.push(beginVertex);
System.out.println(beginVertex.element);
visitedVertices.add(beginVertex);
while (!stack.isEmpty()) {
Vertex<E, V> vertex = stack.pop();
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
stack.push(edge.from);
stack.push(edge.to);
System.out.println(edge.to.element);
visitedVertices.add(edge.to);
break;
}
}
}
二、广度优先搜索
1、BFS思路
和二叉树的层次遍历类似,利用队列实现。先将起点入队并访问,再从队列中出队一个顶点,将该顶点所有出度边的终点(未被访问过的顶点)依次入队并访问,每次从队列中出队都是相同操作,直到队列为空。
2、BFS实现
@Override
public void bfs(E e) {
Vertex<E, V> startVertex = vertices.get(e);
if (startVertex == null)
return;
Queue<Vertex<E, V>> queue = new LinkedList<>();
Set<Vertex<E, V>> visitedVertices = new HashSet<>();
queue.offer(startVertex);
System.out.println(startVertex.element);
visitedVertices.add(startVertex);
while (!queue.isEmpty()) {
Vertex<E, V> vertex = queue.poll();
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
queue.offer(edge.to);
System.out.println(edge.to.element);
visitedVertices.add(edge.to);
}
}
}