无向图由边(edge)和顶点(vertex)组成。 当两个顶点通过一条边相连时我们称这两个顶点相邻。同时这条边依附于这两个顶点 某个顶点的度数为依附它的边的条数。

  • 连通图

    如果从任意一个顶点都存在一条路径到达一个任意顶点,我们称这幅图是连通图

图的接口

public interface Graph {
	/**
	 * @Description: Get the vertex number.
	 * @return
	 */
	public int V();
	/**
	 * @Description: Get the edge number.
	 * @return
	 */
	public int E();
	/**
	 * @Description: Create an edge between w and v.
	 * @param v
	 * @param w
	 */
	public void addEdge(int v, int w);
	/**
	 * @Description: Get all vertex adjacent to v.
	 * @param v
	 * @return
	 */
	public Iterable<Integer> adj(int v);
	/**
	 * @Description: Return degree of given vertex.
	 * @param G
	 * @param V
	 * @return
	 */
	static Integer degree(Graph G, int V){
		Integer degree = new Integer(0);
		for(int w : G.adj(V)) degree++;
		return degree;
	}
	/**
	 * @Description: Find the largest degree in the graph
	 * @param G
	 * @param V
	 * @return
	 */
	static int maxDegree(Graph G, int V){
		int max = 0;
		for(int w : G.adj(V) )
			if(w > max)
				max = w;
		return max;
	}
	/**
	 * @Description: Calculate the average degree for all vertex.
	 * @param G
	 * @return
	 */
	static double avgDegree(Graph G){
		return 2 * G.E() / G.V();
	}
		/**
	 * @Description: Get the number of selt loop.
	 * @param G
	 * @param V
	 * @return
	 */
	static int numOfSelfLoop(Graph G, int V){
		int num = 0;
		int vNum = G.V();
		for(int v = 0; v < vNum; v++ )
			for(int w : G.adj(v))
				if(w == v)
					num ++;
		return num/2;	//w,v and v,w will both be counted.
	}
		/**
	 * @Description: Print a graph.
	 */
	default void display(){
		int vertexNum = this.V();
		for(int v = 0; v < vertexNum; v++){
			StringBuilder sb = new StringBuilder(v + " -> ");
			for(int w : adj(v))
				sb.append(w + "");
			System.out.println(sb.toString());
	}
}

无向图的实现

public class UndirectedGraph implements Graph {
	private final int V; //vertex
	private int E;	//edge
	private Bag<Integer>[] adj;	//adjacency table.
	@SuppressWarnings("unchecked")
	public UndirectedGraph(int V) {
		this.V = V;
		adj =  new ListBag[V];
		for(int v = 0; v < V; v++)
			adj[v] = new ListBag<>();
	}
	@SuppressWarnings("unchecked")
	public UndirectedGraph(FileInputStream in){	//Read graph from file.
		Scanner scanner = new Scanner(in);
		this.V = scanner.nextInt();
		adj =  new ListBag[V];
		for(int v = 0; v < V; v++)
			adj[v] = new ListBag<>();
		int E = scanner.nextInt();
		for(int e = 0; e < E; e++){
			int v = scanner.nextInt();
			int w = scanner.nextInt();
			addEdge(v, w);
		}
		scanner.close();
	}
	@Override
	public int V() { return V; }
	@Override
	public int E() { return 0; }
	@Override
	public void addEdge(int v, int w) {	//Add a connection between v and w.
		adj[v].add(w);
		adj[w].add(v);
		this.E++;
	}

	@Override
	public Iterable<Integer> adj(int v) {	//Return a list that the certex connected to.
		return adj[v];
	}
}

图的API

接口

public interface Search {
	/**
	 * @Description: If s and v are connected.
	 * @param v
	 * @return
	 */
	public boolean mark(int v);
	/**
	 * @Description: Number of vertex connected to source.
	 * @return
	 */
	public int count();
}

抽象类

  • 之所以使用抽象类是为了减少重复定义图的构造器方法,而构造器是无法在接口中通过default实现的。
    public abstract class AbstractSearch implements Search {
      protected final Graph g;
      protected final int s;	//source点
      public AbstractSearch(Graph g, int s) {
          this.g = g;
          this.s = s;
      }
    }
    

通过加权并查集实现的Seach类: UFSearch

public class UFSearch extends AbstractSearch {
	private final UF uf;
	public UFSearch(Graph g, int s) {
		super(g, s);
		this.uf = new UF(g.V());
		//Insert connections into the UF.
		for(int v = 0; v < g.V(); v++){
			for(int w : g.adj(v)){
				if(uf.connected(v, w))	continue;
				else	uf.union(v, w);
			}
		}
	}
	@Override
	public boolean mark(int v) {
		return uf.connected(super.s, v);
	}
	@Override
	public int count() {
		return uf.size[super.s];
	}
	private final class UF{	//定义了一个内部final私有类UF,其为加权并查集的实现。
		private final int N;
		private final int[] a;	//并查集数组
		private final int[] size;	//当前结点的子结点的数量。
		public UF(int N){
			this.N = N;
			a = new int[N];
			for(int i = 0; i < N; i++)	a[i] = i;
			size = new int[N];
			for(int i = 0; i < N; i++)	size[i] = 1;
		}
		public int find(int v){
			if(a[v] == v)	return v;
			else	return find(a[v]);
		}
		public void union(int p, int q){
			int qRoot = find(q);	//分别找到p和q的根路径
			int pRoot = find(p);
			if(pRoot == qRoot) return;
			if(size[qRoot] < size[pRoot]){	//比较子树数量,连接到更大的树上以减小树得深度
				a[qRoot] = pRoot;
				size[pRoot] += size[qRoot];
			}else{	//size[qRoot] >= size[pRoot]
				a[pRoot] = qRoot;
				size[qRoot] += size[pRoot];
			}
		}
		public boolean connected(int p, int q){
			return find(q) == find(p);
		}
	}

}

UFSearch测试

		Graph g = new UndirectedGraph(new FileInputStream(new File("src/ca/mcmaster/chapter/four/graph/tinyG.txt")));
		Search search = new UFSearch(g, 3);
		System.out.println(search.mark(7));
		g.display();
  • 结果

    false 0 -> 6 2 1 5 1 -> 0 2 -> 0 3 -> 5 4 4 -> 5 6 3 5 -> 3 4 0 6 -> 0 4 7 -> 8 8 -> 7 9 -> 11 10 12 10 -> 9 11 -> 9 12 12 -> 11 9

深度优先查找DFSearch, DFPath

public class DeepFirstSearch extends AbstractSearch {
	private boolean[] marked;	//A array used to mark if current node is connected to s
	private int count;	//number of vertex connected to s
	public DeepFirstSearch(Graph g, int s) {
		super(g, s);
		marked = new boolean[g.V()];
		dfs(g, s);
	}
	private void dfs(Graph g, int v){
		marked[v] = true;	//It means current vertex has been accessed.
		count++;	//update the number of vertex connected to s.
		for(int w : g.adj(v))
			if(!marked[w]) dfs(g, w);	//Check all point connected to v, if not accessed, access recursively.
	}
	@Override
	public boolean mark(int v) {
		return marked[v];
	}
	@Override
	public int count() {
		return this.count;
	}
}

测试

	public static void main(String[] args) throws FileNotFoundException {
		Graph g = new UndirectedGraph(new FileInputStream(new File("src/ca/mcmaster/chapter/four/graph/tinyG.txt")));
		Search search = new DeepFirstSearch(g, 12);
		System.out.println(search.mark(9));
		System.out.println(search.count());
		//g.display();
	}
  • 结果

    true 4

寻找路径

  • 定义路径的接口函数 ```Java public interface Path { /**
    • @Description: If there is a path from s to v
    • @param v
    • @return */ public boolean hasPathTo(int v); /**
    • @Description: Return the path from s to v if it exists and return null if not existing.
    • @param v
    • @return */ Iterable pathTo(int v); } ```
  • 抽象类

    用于定义构造函数。

    public abstract class AbstractPath implements Path {
      protected final Graph g;
      protected final int s;
      public AbstractPath(Graph g, int s) {
          super();
          this.g = g;
          this.s = s;
      };
    }
    
  • 深度优先查找路径
    public class DepthFirstPath extends AbstractPath {
      private boolean[] marked;	//If current vertex has been accessed.
      private int[] edgeTo;	//Record the path from that point to s.
      public DepthFirstPath(Graph g, int s) {
          super(g, s);
          marked = new boolean[g.V()];
          edgeTo = new int[g.V()];
          dfs(g, s);
      }
      @Override
      public boolean hasPathTo(int v) {
          return marked[v];
      }
      @Override
      public Iterable<Integer> pathTo(int v) {
          if(true == hasPathTo(v)){
              //存入的时候是逆序,读取的时候需要顺序,所以LIFO的队列最为合适,使用栈
              MyStack<Integer> path = new ListStack<>();
              do {
                  path.push(v);
                  v = edgeTo[v];
              } while (v != s);
              return path;
          }
          return null;
      }
      private void dfs(Graph g, int v){
          marked[v] = true;
          for(int w : g.adj(v)){	//All vertex connected to v
              if(!marked[w]){	//If current vertex has not been accessed, we mark it and save the previous vertex to current vertex.
                  edgeTo[w] = v;
                  dfs(g, w);
              }
          }
      }
    }
    
  • 测试
      public static void main(String[] args) throws FileNotFoundException {
          Graph g = new UndirectedGraph(new FileInputStream(new File("src/ca/mcmaster/chapter/four/graph/tinyCG.txt")));
          Path p = new DepthFirstPath(g, 0);
          Iterable<Integer> path = p.pathTo(1);
          StringBuilder sb = new StringBuilder("0");
          for(Integer pnode : path){
              sb.append("->" + pnode);
          }
          System.out.println(sb.toString());
      }
    

    0->2->1

广度优先搜索

  • 广度优先用于寻找最短路径。
  • 深度优先(DFP)所寻找到的路径是通过递归调用寻找到路径,递归调用的路径返回是根据adjacency table中的链表的显示顺序。所以返回的值不一定是最短的。
  • 广度优先查找定义了相邻的所有顶点,再找到相邻两个,以此类推。
    public class BreadthFirstPath extends AbstractPath{
      private final boolean[] marked;
      private final int[] edgeTo;
      public BreadthFirstPath(Graph g, int s) {
          super(g, s);
          marked = new boolean[g.V()];
          edgeTo = new int[g.V()];
          bfs(g, s);
      }
    
      @Override
      public boolean hasPathTo(int v) {
          return marked[v];
      }
    
      @Override
      public Iterable<Integer> pathTo(int v) {
          MyStack<Integer> stack = new ListStack<>();
          do {
              stack.push(v);
              v = edgeTo[v];
          } while (v != s);
          return stack;
      }
      private void bfs(Graph g, int s){
          LinkedBlockingQueue<Integer> q = new LinkedBlockingQueue<Integer>();
          marked[s] = true;
          q.add(s);
          while(!q.isEmpty()){
              Integer v = q.poll();	//从队列中读取下一个顶点并对其遍历相邻顶点。
              for(int w : g.adj(v)){
                  if(!marked[w]){	//Current vertex is not accessed.
                      q.add(w);	//如果相邻的顶点没有被访问过,就将它加入队列中。
                      edgeTo[w] = v;
                      marked[w] = true;
                  }
              }
          }
      }
    }
    
  • 测试
      public static void main(String[] args) throws FileNotFoundException {
          Graph g = new UndirectedGraph(new FileInputStream(new File("src/ca/mcmaster/chapter/four/graph/tinyCG.txt")));
          Path p = new BreadthFirstPath(g, 0);
          Iterable<Integer> path = p.pathTo(4);
          StringBuilder sb = new StringBuilder("0");
          for(Integer pnode : path){
              sb.append("->" + pnode);
          }
          System.out.println(sb.toString());
      }
    

连通分量 Connection Component

无向图G的极大连通子图称为G的连通分量( Connected Component)。任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量。

  • 接口 ```Java public interface ConnectionComponent { /**
    • @Description: If v and w are connected.
    • @param v
    • @param w
    • @return */ public boolean connected(int v, int w); /**
    • @Description: Number of cc in current graph.
    • @return */ public int count(); /**
    • @Description: Identification of connection component.
    • @param v
    • @return */ public int id(int v); } ```
  • 抽象类
    public abstract class AbstractCC implements ConnectionComponent {
      protected final Graph g;
      public AbstractCC(Graph g){
          this.g = g;
      }
    }
    
  • 基于DFS的实现
    public class DFSCC extends AbstractCC {
      private boolean[] marked;
      private int[] id;
      private int count;
      public DFSCC(Graph g) {
          super(g);
          marked = new boolean[g.V()];
          id = new int[g.V()];
          for(int v = 0; v < g.V(); v++)	//Traversal all of vertex if not accessed
              if(!marked[v]){	//Current vertex is not accessed.
                  dfs(g, v);
                  count++;
              }
      }
      @Override
      public boolean connected(int v, int w) {
          return id[w] == id[v];
      }
      @Override
      public int count() {
          return this.count;
      }
      @Override
      public int id(int v) {
          return id[v];
      }
      private void dfs(Graph g, int v){
          marked[v] = true;
          id[v] = count;	//assign current count to this vertex as id.
          for(int w : g.adj(v))
              if(!marked[w])
                  dfs(g, w);
      }
    }
    
  • 测试
      public static void main(String[] args) throws FileNotFoundException {
          Graph g = new UndirectedGraph(new FileInputStream(new File("src/ca/mcmaster/chapter/four/graph/tinyG.txt")));
          DFSCC cc = new DFSCC(g);
          int ccNum = cc.count();
          System.out.println("Number of cc: " + ccNum);
          Bag<Integer>[] bag = new ListBag[ccNum];	//Create bag array to save all connection components.
          for(int i = 0; i < ccNum; i++)
              bag[i] = new ListBag<>();
          for(int i = 0; i < g.V(); i++)
              bag[cc.id(i)].add(i);	//Add vertex into bag.
          for(int i = 0; i < ccNum; i++){
              StringBuilder sb = new StringBuilder(i + ": ");
              for(int v : bag[i])
                  sb.append(v + " ");
              System.out.println(sb.toString());
          }
      }
    

    Number of cc: 3 0: 6 5 4 3 2 1 0 1: 8 7 2: 12 11 10 9

符号图

  • 图中的顶点都是int型,所以我们制作一张哈希表存储符号(字符串)和值之间的关系。

    符号图接口

    ```Java public interface SymbolGraph { /**

    • @Description: Is key a vertex.
    • @param key
    • @return */ public boolean contains(String key); /**
    • @Description: Index of key.
    • @param key
    • @return */ public int index(String key); /**
    • @Description: name of v in symbol table
    • @param v
    • @return */ public String name(int v); /**
    • @Description: Get the anonymous graph object.
    • @return */ public Graph G(); } ```

符号图的实现

public class SymbolGraphImpl implements SymbolGraph {
	private final Map<String, Integer> st;
	private String[] keys;
	private final Graph G;
	public SymbolGraphImpl(String file, String sp) throws FileNotFoundException {
		st = new HashMap<>();
		//fulfill the symbol table
		Scanner scanner = null;
		try {
			scanner = new Scanner(new File(file));	//将每个顶点都加入符号表
			int count = 0;
			while(scanner.hasNextLine()){
				String[] tokens = scanner.nextLine().split(sp);
				for(String t : tokens){
					if(!st.containsKey(t)){	//If not contains token then put it into the st.
						st.put(t, count++);
					}
				}
			}
		}finally{
			scanner.close();
		}
		keys = new String[st.size()];	//没有办法之间用(String[])st.keySet.toArray(),只能通过遍历将每一个顶点的符号复制。
		for(String name : st.keySet()){
			keys[st.get(name)] = name;
		}
		// Create graph
		G = new UndirectedGraph(st.size());
		//Add edges
		Scanner scanner2 = null;
		try{
			scanner2 = new Scanner(new File(file));
			while(scanner2.hasNextLine()){
				String[] tokens = scanner2.nextLine().split(sp);
				for(int i = 1; i < tokens.length; i++){
					int v = st.get(tokens[0]);
					G.addEdge(v, st.get(tokens[i]));
				}
			}
		}finally{
			scanner2.close();
		}
	}
	@Override
	public boolean contains(String key) { return st.containsKey(key); }

	@Override
	public int index(String key) { return st.get(key); }

	@Override
	public String name(int v) { return keys[v]; }

	@Override
	public Graph G() { return G; }

	public static void main(String[] args) throws FileNotFoundException {
		SymbolGraphImpl symbolGraphImpl = new SymbolGraphImpl("src/ca/mcmaster/chapter/four/graph/movies.txt", "/");
		Graph graph = symbolGraphImpl.G();
		int vertexNum = graph.V();
		for(int v = 0; v < vertexNum; v++){
			StringBuilder sb = new StringBuilder(symbolGraphImpl.name(v) + " -> ");
			for(int w : graph.adj(v))
				sb.append(symbolGraphImpl.name(w) + " ");
			System.out.println(sb.toString());
		}
	}
}

间隔的度数

间隔的度数实际上就是最短路径,即两个顶点之间的最短距离。

	public static void main(String[] args) throws FileNotFoundException {
		SymbolGraphImpl symbolGraphImpl = new SymbolGraphImpl("src/ca/mcmaster/chapter/four/graph/movies.txt", "/");
		Graph graph = symbolGraphImpl.G();
		int vertexNum = graph.V();
		for(int v = 0; v < vertexNum; v++){
			StringBuilder sb = new StringBuilder(symbolGraphImpl.name(v) + " -> ");
			for(int w : graph.adj(v))
				sb.append(symbolGraphImpl.name(w) + "|");
		}
		//Use BFS to find the shorted path.
		BreadthFirstPath bfs = new BreadthFirstPath(graph, symbolGraphImpl.index("Bacon, Kevin"));
		Iterable<Integer> pathTo = bfs.pathTo(symbolGraphImpl.index("Kidman, Nicole"));
		System.out.println("Bacon, Kevin");
		for(int w : pathTo){
			System.out.println(symbolGraphImpl.name(w));
		}
	}