已经好久没有复习数据结构了,今天复习的是无向图。由于本人确定自己写的注释还是蛮认真的,所以就直接贴代码了。接下来张贴的几段代码或者说几个类都是循序渐进的。只要你还记得图的定义(具体是无向图),应该都不难理解。好了,废话不多说,直接看代码吧。
/**
* 一个可以用来表示无向图的类
*/
public class Graph {
private static final String NEWLINE = System.getProperty("line.separator");
private final int V;
private int E;
private List[] adj;
/**
* 构造
*
* @param V
*/
public Graph(int V) {
if (V <= 0) {
throw new IllegalArgumentException("顶点数必须是一个正整数");
}
this.V = V;
this.E = 0;
adj = (List[]) new ArrayList[V];
for (int v = 0; v < V; v++) {
adj[v] = new ArrayList();
}
}
/**
* 根据另外一个无向图构造新图
*
* @param G
*/
public Graph(Graph G) {
this(G.V());
this.E = G.E();
for (int v = 0; v < G.V(); v++) {
Stack reverse = new Stack();
for (int w : G.adj[v]) {
reverse.push(w);
}
for (int w : reverse) {
adj[v].add(w);
}
}
}
/**
* 返回顶点数
*
* @return
*/
public int V() {
return V;
}
/**
* 返回边数
*
* @return
*/
public int E() {
return E;
}
/**
* 向无向图中添加一条边
*
* @param v
* @param w
*/
public void addEdge(int v, int w) {
validateVertex(v);
validateVertex(w);
E++;
adj[v].add(w);
adj[w].add(v);
}
/**
* 返回与顶点v直接相连的其他顶点
*
* @param v
* @return
*/
public Iterable adj(int v) {
validateVertex(v);
return adj[v];
}
/**
* 返回顶点v的度数,也即与顶点v直接相连的顶点的个数
*
* @param v
* @return
*/
public int degree(int v) {
validateVertex(v);
return adj[v].size();
}
private void validateVertex(int v) {
if (v < 0 || v >= V)
throw new IndexOutOfBoundsException("顶点 " + v + "不在 0 和 " + (V - 1) + "之间");
}
@Override
public String toString() {
StringBuffer s = new StringBuffer();
s.append(V + " 个顶点, " + E + " 条边 " + NEWLINE);
for (int v = 0; v < V; v++) {
s.append(v + ": ");
for (int w : adj[v]) {
s.append(w + " ");
}
s.append(NEWLINE);
}
return s.toString();
}
/**
* @param args
*/
public static void main(String[] args) {
// 0——————6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5-----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
System.out.println(graph);
}
}
/**
* 该类可以用于无向图的深度优先搜索
*/
public class DFS {
private boolean[] visited; //visited[v] = true表示顶点v被dfs方法访问过了,也可以表示存在s~v的路径
private int count;
/**
* 构造
* @param G
* @param s
*/
public DFS(Graph G, int s) {
visited = new boolean[G.V()];
dfs(G, s);
}
/**
* 递归实现无向图的深度优先搜索
* @param G
* @param v
*/
private void dfs(Graph G, int v) {
count++;
visited[v] = true;
for (int w : G.adj(v)) {
if (!visited[w]) {
dfs(G, w);
}
}
}
/**
* 用于判断顶点v是否被访问过了,或者用于判断起始顶点s和顶点v之间是否连通
* @param v
* @return
*/
public boolean visited(int v) {
return visited[v];
}
/**
* 返回与起始顶点相连通的顶点数
* @return
*/
public int count() {
return count;
}
// 0——————6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5-----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
public static void main(String[] args) {
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
DFS ds = new DFS(graph, 0);
for (int v = 0; v < graph.V(); v++) {
if (ds.visited(v)) {
System.out.print(v + " ");
}
}
System.out.println();
if (ds.count() != graph.V()){
System.out.print("该无向图不是联通图");
}
else {
System.out.print("该无向图是联通图");
}
}
}
/**
* 该类用于计算无向图中从起始顶点s到某个特定顶点之间是存在路径使其相连
*
*/
public class DFPaths {
private boolean[] visited; // marked[v] = true 表示存在s-v的路径
private int[] edgeTo; // edgeTo[v] = 从s-v的路径上的最后一个顶点
private final int s; //起始顶点 或源
public DFPaths(Graph G, int s) {
this.s = s;
edgeTo = new int[G.V()];
visited = new boolean[G.V()];
dfs(G, s);
}
private void dfs(Graph G, int v) {
visited[v] = true;
for (int w : G.adj(v)) {
if (!visited[w]) {
edgeTo[w] = v;
dfs(G, w);
}
}
}
/**
* 用于判断s到v之间是否存在一条路径使其相连
* @param v
* @return
*/
public boolean hasPathTo(int v) {
return visited[v];
}
/**
* 返回s到v之间的路径,不存在这样的路径则返回null
* @param v
* @return
*/
public Iterable pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
Stack path = new Stack();
for (int x = v; x != s; x = edgeTo[x]) {
path.push(x);
}
path.push(s);
return path;
}
// 0——————6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5-----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
public static void main(String[] args) {
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
int s = 0;
DFPaths paths = new DFPaths(graph, s);
for (int v = 0; v < graph.V(); v++) {
if (paths.hasPathTo(v)) {
System.out.printf("%d -> %d: ", s, v);
for (int x : paths.pathTo(v)) {
if (x == s) {
System.out.print(x);
} else {
System.out.print("-" + x);
}
}
System.out.println();
}
else {
System.out.printf("顶点%d与顶点 %d之间不连通\n", s, v);
}
}
}
}
/**
*该类用于计算无向图中从构造函数得到的起点s到与其他所有顶点的最短路径。由于是无向图,边不带权重,所以实际计算出来的
*最短路径(从起点s到顶点v的顶点总数越小,路径越短)与添加边到图中的顺序是有一定关系的。或者说所得到的最短路径不唯一。
*比如本类main方法所示的无向图中,路径"4-> 6-> 0"与路径 "4-> 5-> 0"都是0到4的最短路径,但计算只是给出了
*前一条路径。不过本算法的意思在于,找不到一条0到4的路径比上述提到的两条路径还短了。
*/
public class BFPaths {
private boolean[] visited;
private int[] edgeTo;
private final int s;
/**
* 构造
* @param G
* @param s
*/
public BFPaths(Graph G, int s) {
visited = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
bfs(G, s);
}
/**
* 广度优先搜索计算最短路径
* @param G
* @param s
*/
private void bfs(Graph G, int s) {
Queue queue = new LinkedList();
visited[s] = true;
queue.add(s);
while(!queue.isEmpty()) {
int v = queue.remove();
for(int w : G.adj(v)) {
if(!visited[w]) {
edgeTo[w] = v;
visited[w] = true;
queue.add(w);
}
}
}
}
/**
* @param v
* @return
*/
public boolean hasPathTo(int v) {
return visited[v];
}
/**
* 返回s到v之间的路径,不存在这样的路径则返回null
* @param v
* @return
*/
public Iterable pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
Stack path = new Stack();
for (int x = v; x != s; x = edgeTo[x]) {
path.push(x);
}
path.push(s);
return path;
}
// 0——————6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5-----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
public static void main(String[] args) {
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
int s = 0;
BFPaths paths = new BFPaths(graph, s);
for (int v = 0; v < graph.V(); v++) {
if (paths.hasPathTo(v)) {
System.out.printf("%d -> %d: ", v, s);
for (int x : paths.pathTo(v)) {
if (x == v) {
System.out.print(x);
} else {
System.out.print("-> " + x);
}
}
System.out.println();
}
else {
System.out.printf("顶点%d与顶点 %d之间不连通\n", v, s);
}
}
}
}
“`java
/**
*该类用于计算无向图中的连通分量, CC也即Connected Component的缩写
*/
public class CC {
private boolean[] visited; //visited[v]= true则表示顶点v被访问过了
private int[] id; // id[v]的值表示包含顶点v的那个连通分量的id
private int[] size; // size[id]表示第id个连通分量所包含的顶点数
private int count; // 连通分量总数
public CC(Graph G) {
visited = new boolean[G.V()];
id = new int[G.V()];
size = new int[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!visited[v]) {
dfs(G, v);
count++;
}
}
}
private void dfs(Graph G, int v) {
visited[v] = true;
id[v] = count;
size[count]++;
for (int w : G.adj(v)) {
if (!visited[w]) {
dfs(G, w);
}
}
}
/**
* 返回标识包含顶点v的那个连通分量的id
* @param v
* @return
*/
public int id(int v) {
return id[v];
}
/**
* 返回顶点v所在的那个连通分量的大小
* @param v
* @return
*/
public int size(int v) {
return size[id[v]];
}
/**
* 返回无向图中的连通分量总数
* @return
*/
public int count() {
return count;
}
/**
* 返回顶点v和顶点w是否位于同一个连通分量
* @param v
* @param w
* @return
*/
public boolean connected(int v, int w) {
return id(v) == id(w);
}
public void printConnection(int v, int w) {
if(id(v) == id(w)) {
System.out.println("顶点" + v + "与顶点" + w + "连通");
} else {
System.out.println("顶点" + v + "与顶点" + w + "不连通");
}
}
// 0---------6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
public static void main(String[] args) {
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
int s = 0;
CC cc = new CC(graph);
System.out.println("该无向图的连通分量总数是: " + cc.count());
cc.printConnection(0, 3);
cc.printConnection(12, 3);
}
}
“`
/**
* 该类用于计算一个无向图是否包含了环,如果包含的话,找出这个环
*
* @author lhever
*
*/
public class Cycle {
private boolean[] visited;
private int[] edgeTo;
private Stack cycle;
/**
* 构造
* @param G
*/
public Cycle(Graph G) {
if (hasSelfLoop(G)) {
return;
}
if (hasParallelEdges(G)) {
return;
}
visited = new boolean[G.V()];
edgeTo = new int[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!visited[v]) {
dfs(G, -1, v);
}
}
}
/**
* 计算无向图中是否含有自环
*
* @param G
* @return
*/
private boolean hasSelfLoop(Graph G) {
for (int v = 0; v < G.V(); v++) {
for (int w : G.adj(v)) {
if (v == w) {
cycle = new Stack();
cycle.push(v);
cycle.push(v);
return true;
}
}
}
return false;
}
/**
* 计算无向图是否含有平行边
*
* @param G
* @return
*/
private boolean hasParallelEdges(Graph G) {
visited = new boolean[G.V()];
for (int v = 0; v < G.V(); v++) {
for (int w : G.adj(v)) {
if (visited[w]) {
cycle = new Stack();
cycle.push(v);
cycle.push(w);
cycle.push(v);
return true;
}
visited[w] = true;
}
// reset so marked[v] = false for all v
for (int w : G.adj(v)) {
visited[w] = false;
}
}
return false;
}
/**
* 如果含有环的话返回true
* @return
*/
public boolean hasCycle() {
return cycle != null;
}
/**
* 返回环
*
* @return
*/
public Iterable cycle() {
return cycle;
}
private void dfs(Graph G, int u, int v) {
visited[v] = true;
for (int w : G.adj(v)) {
/**
* 只要找到一个环就不在计算
*/
if (cycle != null)
return;
if (!visited[w]) {
edgeTo[w] = v;
dfs(G, v, w);
} else if (w != u) {
cycle = new Stack();
for (int x = v; x != w; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
}
// 0------6
// /| \ | 7———8
// / | \ | |
// / 1 2 | |
// / | 9 —————10
// 5-----------4 | \
// \ / | \
// \ / 11——12
// \ /
// \ /
// 3
public static void main(String[] args) {
Graph graph = new Graph(13);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(0, 5);
graph.addEdge(0, 6);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
graph.addEdge(4, 6);
graph.addEdge(7, 8);
graph.addEdge(7, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(9, 12);
graph.addEdge(11, 12);
Cycle finder = new Cycle(graph);
if (finder.hasCycle()) {
System.out.println("该无向图至少含有一个环,比如: ");
for (int v : finder.cycle()) {
System.out.print(v + " ");
}
System.out.println();
} else {
System.out.println("该无向图中无环");
}
}
}
/**
* 许多实际问题可以抽象为无向图进行解决,但往往实际问题抽象为图后,其顶点名一般是字符串,该类用于将字符串
* 类型的顶点名与数字命名的顶点进行关联,以便于我们用图的一般方法进行解决问题。
*/
public class SymbolGraph {
private Map st; // 根据字符串名称找到索引
private String[] keys; // 根据索引找到对应名称
private Graph G; // 无向图
/**
* List>中的每一个List,List.get(0)表示某一个顶点,其余元素都表示与
* 该顶点直接通过一条边相连的临接顶点。
* @param lists
*/
public SymbolGraph(List> lists) {
st = new HashMap();
for (List list : lists) {
for (String key : list) {
if (!st.containsKey(key)) {
st.put(key, st.size());
}
}
}
System.out.println("将字符串类型的顶点名与数字类型的顶点名建立关联完毕");
// 建立反向索引,以便根据顶点名可以获取对应的数字顶点名
keys = new String[st.size()];
for (String name : st.keySet()) {
keys[st.get(name)] = name;
}
G = new Graph(st.size());
for (List list : lists) {
int v = st.get(list.get(0));
for (int i = 1; i < list.size(); i++) {
int w = st.get(list.get(i));
G.addEdge(v, w);
}
}
}
public boolean contains(String s) {
return st.containsKey(s);
}
public int index(String s) {
return st.get(s);
}
public String name(int v) {
return keys[v];
}
public Graph G() {
return G;
}
// (A) 0---------1(B)
// /| \ |
// / | \ |
// / 3 2(C) |
// / (D) |
// (E) 4-------------5(F)
// \ /
// \ /
// \ /
// \ /
// 6(G)
public static void main(String... args) {
List> lists = new ArrayList>();
List li01 = new ArrayList();
li01.add("A");
li01.add("B");
li01.add("C");
li01.add("D");
li01.add("E");
List li02 = new ArrayList();
li02.add("B");
li02.add("F");
List li03 = new ArrayList();
li03.add("E");
li03.add("F");
li03.add("G");
List li04 = new ArrayList();
li04.add("F");
li04.add("G");
lists.add(li01);
lists.add(li02);
lists.add(li03);
lists.add(li04);
SymbolGraph sg = new SymbolGraph(lists);
Graph graph = sg.G();
System.out.println(graph);
}
}