考虑下面的一些问题:
1.迷宫问题,找到从入口到出口的一条路径
2.两座城市间有很多不同的路口连接,导航怎么找到两座城市之间的最短路径
3.把不同地方的许多计算机通过网线连接起来,怎么布线最节省网线
这些问题都可以通过图来描述和解决,图由一组顶点和一组将两个顶点相连的边组成,图可以分为无向图,有向图,加权图,加权有向图,我们先研究无向图。
无向图的表示
无向图中的边仅仅是两个顶点间的连接,无向图可以通过下面的方式实现
public class Graph {
private static final String NEWLINE = System.getProperty("line.separator");
private final int V; //代表图中顶点的个数,顶点用数字表示从0到V-1
private int E;//代表图中边的个数
private Bag<Integer>[] adj;//大小为V,代表每个顶点关联的所有顶点,adj[i]代表与节点i相连的所有顶点,这种方式被称为邻接表数组
/**
* Initializes an empty graph with {@code V} vertices and 0 edges.
* param V the number of vertices
*
* @param V number of vertices
* @throws IllegalArgumentException if {@code V < 0}
*/
public Graph(int V) {
if (V < 0) throw new IllegalArgumentException("Number of vertices must be nonnegative");
this.V = V;
this.E = 0;
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>();
}
}
//从文件中构建一副图,示意图如下
//文件中前两个数字代码顶点和边的个数,后面每一行代表两个顶点相连的一条边
public Graph(In in) {
try {
this.V = in.readInt();
if (V < 0) throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>();
}
int E = in.readInt();
if (E < 0) throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");
for (int i = 0; i < E; i++) {
int v = in.readInt();
int w = in.readInt();
validateVertex(v);
validateVertex(w);
addEdge(v, w);
}
}
catch (NoSuchElementException e) {
throw new IllegalArgumentException("invalid input format in Graph constructor", e);
}
}
/**
* Initializes a new graph that is a deep copy of {@code G}.
*
* @param G the graph to copy
*/
//Ex 4.1.3
public Graph(Graph G) {
this(G.V());
this.E = G.E();
for (int v = 0; v < G.V(); v++) {
// Bag是通过链表实现的,遍历的时候从头节点往尾部遍历,插入的时候是插入到头节点的前面
//所以想保持与G同样的顺序用stack实现
Stack<Integer> reverse = new Stack<Integer>();
for (int w : G.adj[v]) {
reverse.push(w);
}
for (int w : reverse) {
adj[v].add(w);
}
}
}
/**
* Returns the number of vertices in this graph.
*
* @return the number of vertices in this graph
*/
public int V() {
return V;
}
/**
* Returns the number of edges in this graph.
*
* @return the number of edges in this graph
*/
public int E() {
return E;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* Adds the undirected edge v-w to this graph.
*
* @param v one vertex in the edge
* @param w the other vertex in the edge
* @throws IllegalArgumentException unless both {@code 0 <= v < V} and {@code 0 <= w < V}
*/
public void addEdge(int v, int w) {
validateVertex(v);
validateVertex(w);
E++;
adj[v].add(w);
adj[w].add(v);
}
/**
* Returns the vertices adjacent to vertex {@code v}.
*
* @param v the vertex
* @return the vertices adjacent to vertex {@code v}, as an iterable
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<Integer> adj(int v) {
validateVertex(v);
return adj[v];
}
/**
* Returns the degree of vertex {@code v}.
*
* @param v the vertex
* @return the degree of vertex {@code v}
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public int degree(int v) {
validateVertex(v);
return adj[v].size();
}
/**
* Returns a string representation of this graph.
*
* @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>,
* followed by the <em>V</em> adjacency lists
*/
public String toString() {
StringBuilder s = new StringBuilder();
s.append(V + " vertices, " + E + " edges " + 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();
}
/**
* Unit tests the {@code Graph} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
Graph G = new Graph(in);
StdOut.println(G);
}
}
迷宫问题深度优先搜索
迷宫问题使用图算法中的深度优先搜索算法来解决,等价的图如下
如果入口是顶点0出口是顶点4,则深度优先搜索的运行轨迹如下
代码实现如下,比较好理解,下面的代码是找到起点s到所有连通顶点的路径
public class DepthFirstPaths {
private boolean[] marked; // marked[v] = true代表v被标记过,说明有路径从s到v
private int[] edgeTo; // edgeTo[v]代表从s到v的路径中最后一条边的起点
private final int s; // 起点
/**
* Computes a path between {@code s} and every other vertex in graph {@code G}.
* @param G the graph
* @param s the source vertex
* @throws IllegalArgumentException unless {@code 0 <= s < V}
*/
public DepthFirstPaths(Graph G, int s) {
this.s = s;
edgeTo = new int[G.V()];
marked = new boolean[G.V()];
validateVertex(s);
dfs(G, s);
}
// depth first search from v
private void dfs(Graph G, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
if (!marked[w]) { //如果w被标记过,说明前面已经经过w,需要原路返回
edgeTo[w] = v;
dfs(G, w);
}
}
}
/**
* Is there a path between the source vertex {@code s} and vertex {@code v}?
* @param v the vertex
* @return {@code true} if there is a path, {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public boolean hasPathTo(int v) {
validateVertex(v);
return marked[v];
}
/**
* Returns a path between the source vertex {@code s} and vertex {@code v}, or
* {@code null} if no such path.
* @param v the vertex
* @return the sequence of vertices on a path between the source vertex
* {@code s} and vertex {@code v}, as an Iterable
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<Integer> pathTo(int v) {
validateVertex(v);
if (!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<Integer>();
for (int x = v; x != s; x = edgeTo[x])
path.push(x);
path.push(s);
return path;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = marked.length;
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* Unit tests the {@code DepthFirstPaths} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
Graph G = new Graph(in);
int s = Integer.parseInt(args[1]);
DepthFirstPaths dfs = new DepthFirstPaths(G, s);
for (int v = 0; v < G.V(); v++) {
if (dfs.hasPathTo(v)) {
StdOut.printf("%d to %d: ", s, v);
for (int x : dfs.pathTo(v)) {
if (x == s) StdOut.print(x);
else StdOut.print("-" + x);
}
StdOut.println();
}
else {
StdOut.printf("%d to %d: not connected\n", s, v);
}
}
}
}
最短路径广度优先搜索
找到两点间的最短路径使用广度优先搜索来实现,代码如下
这里的最短路径指两点之间边的个数最少
public class BreadthFirstPaths {
private static final int INFINITY = Integer.MAX_VALUE;
private boolean[] marked; // marked[v] = is there an s-v path
private int[] edgeTo; // edgeTo[v] = previous edge on shortest s-v path
private int[] distTo; // distTo[v] = number of edges shortest s-v path
/**
* Computes the shortest path between the source vertex {@code s}
* and every other vertex in the graph {@code G}.
* @param G the graph
* @param s the source vertex
* @throws IllegalArgumentException unless {@code 0 <= s < V}
*/
public BreadthFirstPaths(Graph G, int s) {
marked = new boolean[G.V()];
distTo = new int[G.V()];
edgeTo = new int[G.V()];
validateVertex(s);
bfs(G, s);
assert check(G, s);
}
/**
* Computes the shortest path between any one of the source vertices in {@code sources}
* and every other vertex in graph {@code G}.
* @param G the graph
* @param sources the source vertices
* @throws IllegalArgumentException unless {@code 0 <= s < V} for each vertex
* {@code s} in {@code sources}
*/
public BreadthFirstPaths(Graph G, Iterable<Integer> sources) {
marked = new boolean[G.V()];
distTo = new int[G.V()];
edgeTo = new int[G.V()];
for (int v = 0; v < G.V(); v++)
distTo[v] = INFINITY;
validateVertices(sources);
bfs(G, sources);
}
// breadth-first search from a single source
private void bfs(Graph G, int s) {
Queue<Integer> q = new Queue<Integer>();
for (int v = 0; v < G.V(); v++)
distTo[v] = INFINITY;
distTo[s] = 0;
marked[s] = true;
q.enqueue(s);
while (!q.isEmpty()) {
int v = q.dequeue();
for (int w : G.adj(v)) {
if (!marked[w]) {
edgeTo[w] = v;
distTo[w] = distTo[v] + 1;
marked[w] = true;
q.enqueue(w);
}
}
}
}
// 广度优先搜索使用队列实现,先进先出,把顶点v的所有相连顶点加入队列,就会先处理v的所有相连顶点
//对起点s和目的地v来说,使用这种方式搜索过程中每条路径边数增长的速度是一样的,
//这样最短路径会提前标记v,这样其他长路径就不会标记v了,就找到了
//s到v的最短路径
private void bfs(Graph G, Iterable<Integer> sources) {
Queue<Integer> q = new Queue<Integer>();
for (int s : sources) {
marked[s] = true;
distTo[s] = 0;
q.enqueue(s);
}
while (!q.isEmpty()) {
int v = q.dequeue();
for (int w : G.adj(v)) {
if (!marked[w]) {
edgeTo[w] = v;
distTo[w] = distTo[v] + 1;
marked[w] = true;
q.enqueue(w);
}
}
}
}
/**
* Is there a path between the source vertex {@code s} (or sources) and vertex {@code v}?
* @param v the vertex
* @return {@code true} if there is a path, and {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public boolean hasPathTo(int v) {
validateVertex(v);
return marked[v];
}
/**
* Returns the number of edges in a shortest path between the source vertex {@code s}
* (or sources) and vertex {@code v}?
* @param v the vertex
* @return the number of edges in a shortest path
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
//ex 4.1.13
public int distTo(int v) {
validateVertex(v);
return distTo[v];
}
/**
* Returns a shortest path between the source vertex {@code s} (or sources)
* and {@code v}, or {@code null} if no such path.
* @param v the vertex
* @return the sequence of vertices on a shortest path, as an Iterable
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<Integer> pathTo(int v) {
validateVertex(v);
if (!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<Integer>();
int x;
for (x = v; distTo[x] != 0; x = edgeTo[x])
path.push(x);
path.push(x);
return path;
}
// check optimality conditions for single source
private boolean check(Graph G, int s) {
// check that the distance of s = 0
if (distTo[s] != 0) {
StdOut.println("distance of source " + s + " to itself = " + distTo[s]);
return false;
}
// check that for each edge v-w dist[w] <= dist[v] + 1
// provided v is reachable from s
for (int v = 0; v < G.V(); v++) {
for (int w : G.adj(v)) {
if (hasPathTo(v) != hasPathTo(w)) {
StdOut.println("edge " + v + "-" + w);
StdOut.println("hasPathTo(" + v + ") = " + hasPathTo(v));
StdOut.println("hasPathTo(" + w + ") = " + hasPathTo(w));
return false;
}
if (hasPathTo(v) && (distTo[w] > distTo[v] + 1)) {
StdOut.println("edge " + v + "-" + w);
StdOut.println("distTo[" + v + "] = " + distTo[v]);
StdOut.println("distTo[" + w + "] = " + distTo[w]);
return false;
}
}
}
// check that v = edgeTo[w] satisfies distTo[w] = distTo[v] + 1
// provided v is reachable from s
for (int w = 0; w < G.V(); w++) {
if (!hasPathTo(w) || w == s) continue;
int v = edgeTo[w];
if (distTo[w] != distTo[v] + 1) {
StdOut.println("shortest path edge " + v + "-" + w);
StdOut.println("distTo[" + v + "] = " + distTo[v]);
StdOut.println("distTo[" + w + "] = " + distTo[w]);
return false;
}
}
return true;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = marked.length;
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertices(Iterable<Integer> vertices) {
if (vertices == null) {
throw new IllegalArgumentException("argument is null");
}
int V = marked.length;
for (int v : vertices) {
if (v < 0 || v >= V) {
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
}
}
/**
* Unit tests the {@code BreadthFirstPaths} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
Graph G = new Graph(in);
// StdOut.println(G);
int s = Integer.parseInt(args[1]);
BreadthFirstPaths bfs = new BreadthFirstPaths(G, s);
for (int v = 0; v < G.V(); v++) {
if (bfs.hasPathTo(v)) {
StdOut.printf("%d to %d (%d): ", s, v, bfs.distTo(v));
for (int x : bfs.pathTo(v)) {
if (x == s) StdOut.print(x);
else StdOut.print("-" + x);
}
StdOut.println();
}
else {
StdOut.printf("%d to %d (-): not connected\n", s, v);
}
}
}
}
连通分量
深度优先搜索的用例,遍历图中所有顶点找出图中连通分量的个数,
就是找出一副图中连通子图的个数,如下图个数就是3
public class CC {
private boolean[] marked; // marked[v] = has vertex v been marked?
private int[] id; // id[v] 代表v在哪个连通子图中
private int[] size; // size[id] 代表连通子图id中顶点的个数
private int count; // 有几个连通子图
/**
* Computes the connected components of the undirected graph {@code G}.
*
* @param G the undirected graph
*/
public CC(Graph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
size = new int[G.V()];
for (int v = 0; v < G.V(); v++) {//以图中的每个顶点作为起点遍历图G.V次
//以起点v开始遍历一次,标记所有与v连通的顶点
if (!marked[v]) {//如果v没有被标记说明v在另一副子图中,因为在一副子图中的顶点在一次遍历中全部会被标记
dfs(G, v);
count++;
}
}
}
/**
* Computes the connected components of the edge-weighted graph {@code G}.
*
* @param G the edge-weighted graph
*/
public CC(EdgeWeightedGraph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
size = new int[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
count++;
}
}
}
// depth-first search for a Graph
private void dfs(Graph G, int v) {
marked[v] = true;
id[v] = count;
size[count]++;
for (int w : G.adj(v)) {
if (!marked[w]) {
dfs(G, w);
}
}
}
// depth-first search for an EdgeWeightedGraph
private void dfs(EdgeWeightedGraph G, int v) {
marked[v] = true;
id[v] = count;
size[count]++;
for (Edge e : G.adj(v)) {
int w = e.other(v);
if (!marked[w]) {
dfs(G, w);
}
}
}
/**
* Returns the component id of the connected component containing vertex {@code v}.
*
* @param v the vertex
* @return the component id of the connected component containing vertex {@code v}
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public int id(int v) {
validateVertex(v);
return id[v];
}
/**
* Returns the number of vertices in the connected component containing vertex {@code v}.
*
* @param v the vertex
* @return the number of vertices in the connected component containing vertex {@code v}
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public int size(int v) {
validateVertex(v);
return size[id[v]];
}
/**
* Returns the number of connected components in the graph {@code G}.
*
* @return the number of connected components in the graph {@code G}
*/
public int count() {
return count;
}
/**
* Returns true if vertices {@code v} and {@code w} are in the same
* connected component.
*
* @param v one vertex
* @param w the other vertex
* @return {@code true} if vertices {@code v} and {@code w} are in the same
* connected component; {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
* @throws IllegalArgumentException unless {@code 0 <= w < V}
*/
public boolean connected(int v, int w) {
validateVertex(v);
validateVertex(w);
return id(v) == id(w);
}
/**
* Returns true if vertices {@code v} and {@code w} are in the same
* connected component.
*
* @param v one vertex
* @param w the other vertex
* @return {@code true} if vertices {@code v} and {@code w} are in the same
* connected component; {@code false} otherwise
* @throws IllegalArgumentException unless {@code 0 <= v < V}
* @throws IllegalArgumentException unless {@code 0 <= w < V}
* @deprecated Replaced by {@link #connected(int, int)}.
*/
@Deprecated
public boolean areConnected(int v, int w) {
validateVertex(v);
validateVertex(w);
return id(v) == id(w);
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = marked.length;
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* Unit tests the {@code CC} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
Graph G = new Graph(in);
CC cc = new CC(G);
// number of connected components
int m = cc.count();
StdOut.println(m + " components");
// compute list of vertices in each connected component
Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m];
for (int i = 0; i < m; i++) {
components[i] = new Queue<Integer>();
}
for (int v = 0; v < G.V(); v++) {
components[cc.id(v)].enqueue(v);
}
// print results
for (int i = 0; i < m; i++) {
for (int v : components[i]) {
StdOut.print(v + " ");
}
StdOut.println();
}
}
}
深度优先搜索的用例:判断一副图是否是无环图,假设不存在自环或平行边
public class Cycle {
private boolean[] marked;
private int[] edgeTo;
private Stack<Integer> cycle;
/**
* Determines whether the undirected graph {@code G} has a cycle and,
* if so, finds such a cycle.
*
* @param G the undirected graph
*/
public Cycle(Graph G) {
if (hasSelfLoop(G)) return;
if (hasParallelEdges(G)) return;
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
for (int v = 0; v < G.V(); v++)
if (!marked[v])
dfs(G, -1, v);
}
// does this graph have a self loop?
// side effect: initialize cycle to be self loop
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<Integer>();
cycle.push(v);
cycle.push(v);
return true;
}
}
}
return false;
}
// does this graph have two parallel edges?
// side effect: initialize cycle to be two parallel edges
private boolean hasParallelEdges(Graph G) {
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++) {
// check for parallel edges incident to v
for (int w : G.adj(v)) {
if (marked[w]) {
cycle = new Stack<Integer>();
cycle.push(v);
cycle.push(w);
cycle.push(v);
return true;
}
marked[w] = true;
}
// reset so marked[v] = false for all v
for (int w : G.adj(v)) {
marked[w] = false;
}
}
return false;
}
/**
* Returns true if the graph {@code G} has a cycle.
*
* @return {@code true} if the graph has a cycle; {@code false} otherwise
*/
public boolean hasCycle() {
return cycle != null;
}
/**
* Returns a cycle in the graph {@code G}.
* @return a cycle if the graph {@code G} has a cycle,
* and {@code null} otherwise
*/
public Iterable<Integer> cycle() {
return cycle;
}
private void dfs(Graph G, int u, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
// short circuit if cycle already found
if (cycle != null) return;
if (!marked[w]) {
edgeTo[w] = v;
dfs(G, v, w);
}
// check for cycle (but disregard reverse of edge leading to v)
else if (w != u) {//u是v的父顶点,v是w的父顶点,如果w=u说明v又回到他的父节点u,这样不能算有环
//u,v,w在一副连通子图中,如果w已经被标记并且w!=u,说明已经存在一条路径从v到w,然后v与w又是直接相连
//的,所以就构成了环
cycle = new Stack<Integer>();
for (int x = v; x != w; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
}
/**
* Unit tests the {@code Cycle} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
In in = new In(args[0]);
Graph G = new Graph(in);
Cycle finder = new Cycle(G);
if (finder.hasCycle()) {
for (int v : finder.cycle()) {
StdOut.print(v + " ");
}
StdOut.println();
}
else {
StdOut.println("Graph is acyclic");
}
}
}
符号图,顶点不一定是整数也可能是字符串或其他类型,我们可以用符号表ST<String, Integer>把顶点从string对应成int
就可以使用整形顶点的实现Graph
public class SymbolGraph {
private ST<String, Integer> st; // string -> index
private String[] keys; // index -> string
private Graph graph; // the underlying graph
/**
* Initializes a graph from a file using the specified delimiter.
* Each line in the file contains
* the name of a vertex, followed by a list of the names
* of the vertices adjacent to that vertex, separated by the delimiter.
* @param filename the name of the file
* @param delimiter the delimiter between fields
*/
public SymbolGraph(String filename, String delimiter) {
st = new ST<String, Integer>();
// First pass builds the index by reading strings to associate
// distinct strings with an index
In in = new In(filename);
// while (in.hasNextLine()) {
while (!in.isEmpty()) {
String[] a = in.readLine().split(delimiter);
for (int i = 0; i < a.length; i++) {
if (!st.contains(a[i]))
st.put(a[i], st.size());
}
}
// inverted index to get string keys in an array
keys = new String[st.size()];
for (String name : st.keys()) {
keys[st.get(name)] = name;
}
// second pass builds the graph by connecting first vertex on each
// line to all others
graph = new Graph(st.size());
in = new In(filename);
while (in.hasNextLine()) {
String[] a = in.readLine().split(delimiter);
int v = st.get(a[0]);
for (int i = 1; i < a.length; i++) {
int w = st.get(a[i]);
graph.addEdge(v, w);
}
}
}
/**
* Does the graph contain the vertex named {@code s}?
* @param s the name of a vertex
* @return {@code true} if {@code s} is the name of a vertex, and {@code false} otherwise
*/
public boolean contains(String s) {
return st.contains(s);
}
/**
* Returns the integer associated with the vertex named {@code s}.
* @param s the name of a vertex
* @return the integer (between 0 and <em>V</em> - 1) associated with the vertex named {@code s}
* @deprecated Replaced by {@link #indexOf(String)}.
*/
@Deprecated
public int index(String s) {
return st.get(s);
}
/**
* Returns the integer associated with the vertex named {@code s}.
* @param s the name of a vertex
* @return the integer (between 0 and <em>V</em> - 1) associated with the vertex named {@code s}
*/
public int indexOf(String s) {
return st.get(s);
}
/**
* Returns the name of the vertex associated with the integer {@code v}.
* @param v the integer corresponding to a vertex (between 0 and <em>V</em> - 1)
* @return the name of the vertex associated with the integer {@code v}
* @throws IllegalArgumentException unless {@code 0 <= v < V}
* @deprecated Replaced by {@link #nameOf(int)}.
*/
@Deprecated
public String name(int v) {
validateVertex(v);
return keys[v];
}
/**
* Returns the name of the vertex associated with the integer {@code v}.
* @param v the integer corresponding to a vertex (between 0 and <em>V</em> - 1)
* @throws IllegalArgumentException unless {@code 0 <= v < V}
* @return the name of the vertex associated with the integer {@code v}
*/
public String nameOf(int v) {
validateVertex(v);
return keys[v];
}
/**
* Returns the graph assoicated with the symbol graph. It is the client's responsibility
* not to mutate the graph.
* @return the graph associated with the symbol graph
* @deprecated Replaced by {@link #graph()}.
*/
@Deprecated
public Graph G() {
return graph;
}
/**
* Returns the graph assoicated with the symbol graph. It is the client's responsibility
* not to mutate the graph.
* @return the graph associated with the symbol graph
*/
public Graph graph() {
return graph;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = graph.V();
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* Unit tests the {@code SymbolGraph} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
String filename = args[0];
String delimiter = args[1];
SymbolGraph sg = new SymbolGraph(filename, delimiter);
Graph graph = sg.graph();
while (StdIn.hasNextLine()) {
String source = StdIn.readLine();
if (sg.contains(source)) {
int s = sg.index(source);
for (int v : graph.adj(s)) {
StdOut.println(" " + sg.name(v));
}
}
else {
StdOut.println("input not contain '" + source + "'");
}
}
}
}
//Ex 4.1.35 如果一条边被删除后图会被分为两个独立的连通分量,则这条边称为桥
public class Bridge {
private int bridges; // number of bridges
private int cnt; // counter
private int[] pre; // pre[v] = order in which dfs examines v
private int[] low; // low[v] = lowest preorder of any vertex connected to v
public Bridge(Graph G) {
low = new int[G.V()];
pre = new int[G.V()];
for (int v = 0; v < G.V(); v++)
low[v] = -1;
for (int v = 0; v < G.V(); v++)
pre[v] = -1;
for (int v = 0; v < G.V(); v++)
if (pre[v] == -1)
dfs(G, v, v);
}
public int components() { return bridges + 1; }
private void dfs(Graph G, int u, int v) {
pre[v] = cnt++;
low[v] = pre[v];
for (int w : G.adj(v)) {
if (pre[w] == -1) {
dfs(G, v, w);
low[v] = Math.min(low[v], low[w]);
if (low[w] == pre[w]) {
StdOut.println(v + "-" + w + " is a bridge");
bridges++;
}
}
// update low number - ignore reverse of edge leading to v
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
}
}
// test client
public static void main(String[] args) {
int V = Integer.parseInt(args[0]);
int E = Integer.parseInt(args[1]);
Graph G = GraphGenerator.simple(V, E);
StdOut.println(G);
Bridge bridge = new Bridge(G);
StdOut.println("Edge connected components = " + bridge.components());
}
}
//如果能用两种颜色将一副图中所有顶点作色,使得任意一条边的两个顶点颜色不同,则称这幅图为二分图
/**
* The {@code Bipartite} class represents a data type for
* determining whether an undirected graph is bipartite or whether
* it has an odd-length cycle.
* The <em>isBipartite</em> operation determines whether the graph is
* bipartite. If so, the <em>color</em> operation determines a
* bipartition; if not, the <em>oddCycle</em> operation determines a
* cycle with an odd number of edges.
* <p>
* This implementation uses depth-first search.
* The constructor takes time proportional to <em>V</em> + <em>E</em>
* (in the worst case),
* where <em>V</em> is the number of vertices and <em>E</em> is the number of edges.
* Afterwards, the <em>isBipartite</em> and <em>color</em> operations
* take constant time; the <em>oddCycle</em> operation takes time proportional
* to the length of the cycle.
* See {@link BipartiteX} for a nonrecursive version that uses breadth-first
* search.
* <p>
* For additional documentation, see <a href="https://algs4.cs.princeton.edu/41graph">Section 4.1</a>
* of <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public class Bipartite {
private boolean isBipartite; // is the graph bipartite?
private boolean[] color; // color[v] gives vertices on one side of bipartition
private boolean[] marked; // marked[v] = true iff v has been visited in DFS
private int[] edgeTo; // edgeTo[v] = last edge on path to v
private Stack<Integer> cycle; // odd-length cycle
/**
* Determines whether an undirected graph is bipartite and finds either a
* bipartition or an odd-length cycle.
*
* @param G the graph
*/
public Bipartite(Graph G) {
isBipartite = true;
color = new boolean[G.V()];
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
}
}
assert check(G);
}
private void dfs(Graph G, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
// short circuit if odd-length cycle found
if (cycle != null) return;
// found uncolored vertex, so recur
if (!marked[w]) {
edgeTo[w] = v;
color[w] = !color[v];
dfs(G, w);
}
// if v-w create an odd-length cycle, find it
else if (color[w] == color[v]) {
isBipartite = false;
cycle = new Stack<Integer>();
cycle.push(w); // don't need this unless you want to include start vertex twice
for (int x = v; x != w; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
}
}
}
/**
* Returns true if the graph is bipartite.
*
* @return {@code true} if the graph is bipartite; {@code false} otherwise
*/
public boolean isBipartite() {
return isBipartite;
}
/**
* Returns the side of the bipartite that vertex {@code v} is on.
*
* @param v the vertex
* @return the side of the bipartition that vertex {@code v} is on; two vertices
* are in the same side of the bipartition if and only if they have the
* same color
* @throws IllegalArgumentException unless {@code 0 <= v < V}
* @throws UnsupportedOperationException if this method is called when the graph
* is not bipartite
*/
public boolean color(int v) {
validateVertex(v);
if (!isBipartite)
throw new UnsupportedOperationException("graph is not bipartite");
return color[v];
}
/**
* Returns an odd-length cycle if the graph is not bipartite, and
* {@code null} otherwise.
*
* @return an odd-length cycle if the graph is not bipartite
* (and hence has an odd-length cycle), and {@code null}
* otherwise
*/
public Iterable<Integer> oddCycle() {
return cycle;
}
private boolean check(Graph G) {
// graph is bipartite
if (isBipartite) {
for (int v = 0; v < G.V(); v++) {
for (int w : G.adj(v)) {
if (color[v] == color[w]) {
System.err.printf("edge %d-%d with %d and %d in same side of bipartition\n", v, w, v, w);
return false;
}
}
}
}
// graph has an odd-length cycle
else {
// verify cycle
int first = -1, last = -1;
for (int v : oddCycle()) {
if (first == -1) first = v;
last = v;
}
if (first != last) {
System.err.printf("cycle begins with %d and ends with %d\n", first, last);
return false;
}
}
return true;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
int V = marked.length;
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* Unit tests the {@code Bipartite} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
int V1 = Integer.parseInt(args[0]);
int V2 = Integer.parseInt(args[1]);
int E = Integer.parseInt(args[2]);
int F = Integer.parseInt(args[3]);
// create random bipartite graph with V1 vertices on left side,
// V2 vertices on right side, and E edges; then add F random edges
Graph G = GraphGenerator.bipartite(V1, V2, E);
for (int i = 0; i < F; i++) {
int v = StdRandom.uniform(V1 + V2);
int w = StdRandom.uniform(V1 + V2);
G.addEdge(v, w);
}
StdOut.println(G);
Bipartite b = new Bipartite(G);
if (b.isBipartite()) {
StdOut.println("Graph is bipartite");
for (int v = 0; v < G.V(); v++) {
StdOut.println(v + ": " + b.color(v));
}
}
else {
StdOut.print("Graph has an odd-length cycle: ");
for (int x : b.oddCycle()) {
StdOut.print(x + " ");
}
StdOut.println();
}
}
}
package chapter4_1;
/******************************************************************************
* Compilation: javac Biconnected.java
* Execution: java Biconnected V E
* Dependencies: Graph.java GraphGenerator.java
*
* Identify articulation points and print them out.
* This can be used to decompose a graph into biconnected components.
* Runs in O(E + V) time.
*
******************************************************************************/
public class Biconnected {
private int[] low;
private int[] pre;
private int cnt;
private boolean[] articulation;
public Biconnected(Graph G) {
low = new int[G.V()];
pre = new int[G.V()];
articulation = new boolean[G.V()];
for (int v = 0; v < G.V(); v++)
low[v] = -1;
for (int v = 0; v < G.V(); v++)
pre[v] = -1;
for (int v = 0; v < G.V(); v++)
if (pre[v] == -1)
dfs(G, v, v);
}
private void dfs(Graph G, int u, int v) {
int children = 0;
pre[v] = cnt++;
low[v] = pre[v];
for (int w : G.adj(v)) {
if (pre[w] == -1) {
children++;
dfs(G, v, w);
// update low number
low[v] = Math.min(low[v], low[w]);
// non-root of DFS is an articulation point if low[w] >= pre[v]
if (low[w] >= pre[v] && u != v)
articulation[v] = true;
}
// update low number - ignore reverse of edge leading to v
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
}
// root of DFS is an articulation point if it has more than 1 child
if (u == v && children > 1)
articulation[v] = true;
}
// is vertex v an articulation point?
public boolean isArticulation(int v) { return articulation[v]; }
// test client
public static void main(String[] args) {
int V = Integer.parseInt(args[0]);
int E = Integer.parseInt(args[1]);
Graph G = GraphGenerator.simple(V, E);
StdOut.println(G);
Biconnected bic = new Biconnected(G);
// print out articulation points
StdOut.println();
StdOut.println("Articulation points");
StdOut.println("-------------------");
for (int v = 0; v < G.V(); v++)
if (bic.isArticulation(v)) StdOut.println(v);
}
}