源码概览
1、GraphDemo,用于演示一个图结构以及图的遍历。
2、Graph,表示图数据结构,维护顶点的集合与边的集合,并提供广度优先遍历和深度优先遍历方法。
3、Edge,表示图的一条边,维护了一条边相关的信息,如端点、边的权值、边的名称。泛型V表示权值的类型。
4、GraphNode,表示图中的顶点,泛型V表示顶点存储的数据的类型;此类还维护了一个Boolean类型的变量isAccess,此变量用于在遍历时记录当前顶点是否被访问过,为true表示访问过,false表示未被访问过。
图的遍历
1、深度优先遍历: 广度优先搜索:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所 有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w, 从w出发按同样的方法向前遍历,直到图中所有顶点都被访问
2、广度优先遍历:首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2,…, vi t,并均标记已访问过,然后再按照vi1,vi2,…, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点vi有路径相通的顶点都被访问过为止
源码如下:
package datastructure;
import java.util.Arrays;
public class GraphDemo {
public static void main(String[] args) {
// 1.构造5个顶点
GraphNode<String> n1 = new GraphNode<>("v1");
GraphNode<String> n2 = new GraphNode<>("v2");
GraphNode<String> n3 = new GraphNode<>("v3");
GraphNode<String> n4 = new GraphNode<>("v4");
GraphNode<String> n5 = new GraphNode<>("v5");
// 2.用5个顶点构造7条边
Edge<Integer> e1 = new Edge<>(1, "e1", n1, n2);
Edge<Integer> e2 = new Edge<>(5, "e2", n2, n3);
Edge<Integer> e3 = new Edge<>(9, "e3", n1, n4);
Edge<Integer> e4 = new Edge<>(9, "e4", n3, n4);
Edge<Integer> e5 = new Edge<>(9, "e5", n2, n4);
Edge<Integer> e6 = new Edge<>(9, "e6", n1, n3);
Edge<Integer> e7 = new Edge<>(9, "e7", n4, n5);
// 3.用5个顶点和7条边构造一个图结构
Graph graph = new Graph();
graph.addNode(n1);
graph.addNode(n2);
graph.addNode(n3);
graph.addNode(n4);
graph.addNode(n5);
graph.addEdge(e1);
graph.addEdge(e2);
graph.addEdge(e3);
graph.addEdge(e4);
graph.addEdge(e5);
graph.addEdge(e6);
graph.addEdge(e7);
// 深度优先遍历
// graph.dfsErgodic(n1);
GraphNode<?>[] ns = {n1};
// 广度优先遍历
graph.wfsErgodic(ns);
}
}
/**
* @Author gzx @create 2022-1-27
*/
class Graph {
/* 顶点的集合,保存所有顶点 */
private GraphNode<?>[] nodes;
/* 边的集合,保存所有边 */
private Edge<?>[] edges;
/*顶点集合与边集合的初始下标*/
private int n = 0, e = 0;
/**
* 默认18个顶点,9条边
*/
public Graph() {
this(18, 9);
}
/**
* @param nPoints 顶点个数
* @param nEdges 边条数 使用指定的顶点数和边数初始化图
*/
public Graph(int nPoints, int nEdges) {
nodes = new GraphNode<?>[nPoints];
edges = new Edge<?>[nEdges];
}
public void addNode(GraphNode<?> node) {
// if (n>=nodes.length-1) {//每次添加节点都要进行数组容量检查,影响效率
// pointArrayExpand();
// }
// nodes[n++]=node;
try {// 使用异常捕获提高效率,避免每次都检查数组容量
nodes[n++] = node;
} catch (ArrayIndexOutOfBoundsException e) {
pointArrayExpand();
nodes[n++] = node;
}
}
public void addEdge(Edge<?> edge) {
try {
edges[e++] = edge;
} catch (ArrayIndexOutOfBoundsException ex) {
edgeArrayExpand();
edges[e++] = edge;
}
}
/**
* @param from
* @return 返回指定顶点的邻接点
*/
private GraphNode<?>[] getLinkedNodes(GraphNode<?> from) {
/* 获取邻接点个数 */
int count = 0;
for (Edge<?> e : edges) {
if (e != null && e.getFrom() == from) {
count++;
}
}
/* 保存邻接点至数组中,并返回数组引用 */
GraphNode<?>[] linkedNodes = new GraphNode<?>[count];
int i = 0;
for (Edge<?> e : edges) {
if (e != null && e.getFrom() == from) {
GraphNode<?> n = e.getTo();
if (n.isAccess) {
continue;
}
linkedNodes[i++] = n;
}
}
return linkedNodes;
}
/**
* @param nodes
* @return 与此数组中每个节点距离为1的邻接点,可能会获取到重复的节点
*/
private GraphNode<?>[] getLinkedNodes(GraphNode<?>... nodes) {
/* 获取精确的数组大小 */
int count = 0;
for (GraphNode<?> node : nodes) {
for (Edge<?> e : edges) {
if (e != null && e.getFrom() == node) {
count++;
}
}
}
GraphNode<?>[] results = null;
int index = 0;
if (count > 0) {
results = new GraphNode<?>[count];
for (GraphNode<?> node : nodes) {
for (Edge<?> e : edges) {
if (e != null && e.getFrom() == node) {
results[index++] = e.getTo();
}
}
}
}
return results;
}
/**
* 广度优先搜索 首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2,…, vi t,并均标记已访问过,
* 然后再按照vi1,vi2,…, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,
* 直到图中所有和初始点vi有路径相通的顶点都被访问过为止
*/
public void wfsErgodic(GraphNode<?>... nodes) {
if (nodes != null) {
for (GraphNode<?> n : nodes) {
if (!n.isAccess) {
System.out.println(n.getData());
n.isAccess = true;
}
}
GraphNode<?>[] linkedNodes = getLinkedNodes(nodes);
wfsErgodic(linkedNodes);
}
}
/**
* 深度优先遍历: 广度优先搜索:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,
* 再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所
* 有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w, 从w出发按同样的方法向前遍历,直到图中所有顶点都被访问
*/
public void dfsErgodic(GraphNode<?> node) {
if (node == null) {
return;
}
if (!node.isAccess) {
System.out.println(node.getData());
node.isAccess = true;
}
GraphNode<?>[] graphNodes = getLinkedNodes(node);
if (Arrays.asList(graphNodes).isEmpty()) {
return;
}
for (int i = 0; i < graphNodes.length; i++) {
GraphNode<?> v = graphNodes[i];
dfsErgodic(v);
}
}
/**
* 扩容,先急后缓,即数组容量较小时扩容速度快(避免频繁扩容), 容量大时,扩容速度变慢(避免空间浪费)
*/
private void pointArrayExpand() {
if (nodes.length < 64) {
nodes = Arrays.copyOf(nodes, nodes.length * 2);
} else {
nodes = Arrays.copyOf(nodes, (int) (nodes.length * 1.5));
}
}
private void edgeArrayExpand() {
if (edges.length < 64) {
edges = Arrays.copyOf(edges, (int) (edges.length * 1.5));
} else {
edges = Arrays.copyOf(edges, (int) (edges.length * 1.2));
}
}
}
/**
* @Author gzx @create 2022-1-27
* @param <V> 边的权值的数据类型 该类的一个实例表示图中的一条边,这条边携带的信息包括:
* 边的权值(用于排序),边的名称(用于搜索),边的起点和终点
*/
class Edge<V> {
/* 边的权值 */
private V weight;
/* 边的名称 */
private String name;
/* 边的起点和终点 */
private GraphNode<?> from, to;
public Edge() {
}
/**
* 使用下列参数构造边
*
* @param weight 边权值
* @param name 边名称
* @param from 边端点
* @param to 边端点
*/
public Edge(V weight, String name, GraphNode<?> from, GraphNode<?> to) {
this.weight = weight;
this.name = name;
this.from = from;
this.to = to;
}
public V getWeight() {
return weight;
}
public String getName() {
return name;
}
public GraphNode<?> getFrom() {
return from;
}
public GraphNode<?> getTo() {
return to;
}
public void setWeight(V weight) {
this.weight = weight;
}
public void setName(String name) {
this.name = name;
}
public void setFrom(GraphNode<?> from) {
this.from = from;
}
public void setTo(GraphNode<?> to) {
this.to = to;
}
}
/**
* @Author gzx @create 2022-1-27
* @param <V> 顶点的数据类型 该类的一个实例表示图的一个顶点
*/
class GraphNode<V> {
private V data;
boolean isAccess = false;
public GraphNode(V data) {
this.data = data;
}
public V getData() {
return data;
}
public void setData(V data) {
this.data = data;
}
}