算法筆記13:無向圖


本文講解無向圖,算法筆記14:有向圖講解有向圖。

無向圖的表示和實現

如下圖Graph所示,有一些定點和邊組成圖,邊連接的兩個定點相鄰,邊是對稱的,0-1表示可以從0到1,也可以從1到0。從圖中可以看到,對於任意一個頂點,都可以有任意多的相鄰頂點,我們使用List數組(因爲相鄰頂點的個數不一致)保存對應索引頂點的相鄰頂點,稱之爲鄰接表。下圖Graph的鄰接表如下:

頂點 相鄰頂點
0 1
1 0 2 4
2 1 3
3 2
4 1

在這裏插入圖片描述
根據鄰接表實現Graph

import java.io.*;
import java.util.*;
/**
 * 圖
 * @author XY
 *
 */
public class Graph {
	private int V;//頂點個數
	private int E;//邊的數量
	private ArrayList<Integer>[] adj;//鄰接表

	public Graph(int v) {//使用頂點個數
		this.V = v;
		adj = (ArrayList<Integer>[]) new ArrayList[this.V];		
		for (int i = 0; i < adj.length; i++) {
			adj[i] = new ArrayList<Integer>();
		}
	}
	public Graph(Scanner scanner) throws Exception {//根據輸入流
		this(scanner.nextInt());
		int numedag=scanner.nextInt();
		for(int i=0;i<numedag;i++){
			int v=scanner.nextInt();
			int w=scanner.nextInt();
			addEdage(v, w);
		}

	}
	public void addEdage(int v, int w) {//圖是對稱的,所以一條邊要改變兩個頂點的list.
		if(v!=w && !adj[v].contains(w)){//排除自環和平行邊		
		adj[v].add(w);
		adj[w].add(v);
		E++;
		}
	}
	public Iterable<Integer> adj(int v) {//返回頂點的相鄰頂點
		return adj[v];
	}
	public int V() {
		return this.V;
	}

	public int E() {
		return this.E;
	}
	@Override
	public String toString() {
		StringBuffer sb=new StringBuffer();
		sb.append("V:"+this.V+" E:"+this.E);
		for (int i = 0; i < adj.length; i++) {
			sb.append("\n"+i+":");
			for(int n:adj[i]){
				sb.append(" "+n);
			}
		}
		return sb.toString();
	}

	public static void main(String[] args) throws Exception {
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		System.out.println(graph);
	}
}

上圖中Graph的輸入流構造函數的輸入流和toString()的結果如下所示:
在這裏插入圖片描述在這裏插入圖片描述

符號圖

我們在實際使用中不會直接使用數字,而是使用名稱,如第一張圖中的航線圖,邊表示城市之間可以直達,爲了使用名稱完成圖,實現符號圖;

import java.io.*;
import java.util.*;
/**
 * 符號圖:將符號和索引一一對應,圖的具體實現使用索引
 * @author XY
 *
 */
public class SymbolGraph {
	private HashMap<String, Integer> map;//根據符號找對應索引
	private String[] index;//根據索引找符號
	private int count=0;
	private Graph graph;
	public SymbolGraph(File file,String delim) throws Exception{	
		map=new HashMap<String, Integer>();
//		Scanner scan=new Scanner(new BufferedInputStream(new FileInputStream(file)));
		//注意BufferedInputStream和InputStream相比緩存對IO次數的影響
		Scanner scan=new Scanner(new FileInputStream(file));
		while (scan.hasNextLine()){
			String[] temp=scan.nextLine().split(delim);
			for (int i = 0; i < temp.length; i++) {
				if(!map.containsKey(temp[i]))
				          map.put(temp[i],count++);
			}
		}
		index=new String[count];
		graph=new Graph(count);
		scan=new Scanner(file);
		while(scan.hasNextLine()){			
			String[] temp=scan.nextLine().split(delim);
			int v=map.get(temp[0]);
			index[v]=temp[0];
			for (int i = 1; i < temp.length; i++) {
				int w=map.get(temp[i]);
				index[w]=temp[i];
				graph.addEdage(v, w);
			}			
		}		
	}
	
	public boolean contains(String key){
		return map.containsKey(key);
	}
	public int index(String key) throws Exception{
		if(!map.containsKey(key))throw new IllegalAccessException("no this name");
		return map.get(key);
	}
	public String name(int v) throws Exception{
		if(v>=count) throw new IllegalAccessException("no this index");
		return index[v];
	}
	public Graph graph(){
		return this.graph;
	}
	public static void main(String[] args) throws Exception {
		SymbolGraph sgraph=new SymbolGraph(new File("E:"+File.separator+"route.txt"), " ");
		Graph graph=sgraph.graph();
		BFPath path=new BFPath(graph, sgraph.index("beijing"));
		for (int i :path.pathTo(sgraph.index("chongqing"))) {
			System.out.println(sgraph.name(i));
		}
		
	}
}

深度優先搜索

可達性


import java.io.*;
import java.util.*;
/**
 * 深度優先搜索
 * @author XY
 *
 */
public class DFSearch {
	private boolean[] marked;
	private int N = 0;
	private Graph graph;
	public DFSearch(Graph graph, int s) {
		marked = new boolean[graph.V()];
		this.graph = graph;
		dfs(s);
	}

	private void dfs(int s) {
		marked[s] = true;
		for (int x : this.graph.adj(s)) {
			if (!marked[x]) {
				marked[x] = true;
				N++;
				dfs(x);
			}
		}
	}
	public boolean marked(int v) {
		return this.marked[v];
	}

	public int count() {
		return this.N;
	}

	public void show() {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < marked.length; i++) {
			if (marked[i])
				sb.append(i + " ");
		}
		System.out.println(sb);
	}
	public static void main(String[] args) throws Exception {
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		DFSearch search=new DFSearch(graph, 0);
		System.out.println(search.marked(3));
	}

}

路徑

import java.io.*;
import java.util.*;
/**
 * 深度優先搜索路徑
 * @author XY
 *
 */
public class DFPath {
	private boolean[] marked;
	private Graph graph;
	private int[] edage;//保存該頂點的父頂點:由其父頂點搜索到此頂點
	private int source;
	public DFPath(Graph graph, int s) {
		this.source=s;
		this.graph = graph;
		marked = new boolean[graph.V()];
		edage=new int[graph.V()];		
		dfs(s);
	}

	private void dfs(int s) {
		marked[s] = true;
		for (int x : this.graph.adj(s)) {
			if (!marked[x]) {
				marked[x] = true;
				edage[x]=s;
				dfs(x);
			}
		}
	}
	public Iterable<Integer> pathTo(int v){//根據edage[]上溯
		if(marked[v]==false) return null;
		Stack<Integer> stack=new Stack<Integer>();
		for(int x=v;x!=source;x=edage[x])
			stack.push(x);
		stack.push(source);
		return stack;
	}
	public String stringpathTo(int v){
		if(marked[v]==false) return null;
		StringBuffer sb=new StringBuffer();
		for(int x:pathTo(v)){
			sb.append(x+" ");
		}
		return sb.toString();
	}
	public static void main(String[] args) throws Exception {
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		DFPath path=new DFPath(graph, 0);
//		System.out.println(path.stringpathTo(3));
		for(int x:path.pathTo(3))
			System.out.println(x);
	}

}

廣度優先搜索

最短路徑

import java.io.*;
import java.util.*;
/**
 * 廣度優先搜索路徑:最短路徑
 * @author XY
 *
 */

public class BFPath {
	private Graph graph;
	private boolean[] marked;
	private int[] edage;//保存該頂點的父頂點:由其父頂點搜索到此頂點
	private int source;
	private Queue<Integer> queue;
	public BFPath(Graph graph,int s){
		this.queue=new LinkedList<Integer>();
		this.graph=graph;
		this.source=s;
		marked=new boolean[graph.V()];
		edage=new int[graph.V()];
		for (int i = 0; i < edage.length; i++) {
			edage[i]=i;
			marked[i]=false;
		}
		queue.add(s);
		while(!queue.isEmpty()){
			bfs(queue.poll());
		}
	}
	
	private void bfs(int s){
		marked[s]=true;
		for (int i :this.graph.adj(s)) {
			if(!marked[i]){
				marked[i]=true;
				edage[i]=s;
				this.queue.add(i);
			}
		}
	}
	public boolean hasPahTo(int v){
		return marked[v];
	}
	public Iterable<Integer> pathTo(int v){//根據edage[]上溯
		Stack<Integer> stack=new Stack<Integer>();
		for(int x=v;x!=this.source;x=edage[x]){
			stack.push(x);
		}
		stack.push(this.source);
		return stack;		
	}
	public int path2(int v){
		Stack<Integer> stack=new Stack<Integer>();
		for(int x=v;x!=this.source;x=edage[x]){
			stack.push(x);
		}
		stack.push(this.source);
		return stack.size()-1;		
	}
	public static void main(String[] args) throws Exception{
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		BFPath path=new BFPath(graph, 0);
		for (int i:path.pathTo(3)) {
			System.out.println(i);
		}
	}

}

環和二色圖

import java.io.*;
import java.util.*;
/**
 * 檢測是否有環
 * @author XY
 *
 */
public class Cycle {
	private Graph graph;
	private boolean[] marked;
	private boolean hascycle=false;
	public Cycle(Graph graph){
		this.graph=graph;
		marked=new boolean[graph.V()];
		for (int i = 0; i < marked.length; i++) {
			if(!marked[i])
				dfs(i,i);				
		}
	}
	private void dfs(int s,int v){
		marked[s]=true;
		for(int x:graph.adj(s)){
			if(!marked[x]){
				dfs(x,s);
			}else if (x!=v) {//x的父頂點肯定會被搜索到而且是已訪問,所以要排除上一級的影響
				hascycle=true;System.out.println("s"+s+" x"+x);
			}
		}
	}
	public boolean hasCycle(){
		return this.hascycle;
	}
	public static void main(String[] args) throws Exception{
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		Cycle cycle=new Cycle(graph);
		System.out.println(cycle.hasCycle());
	}

}

二色圖


import java.io.File;
import java.util.Scanner;
/**
 * 二色圖:相鄰頂點的顏色不同
 * @author XY
 *
 */
public class Binarycolor {
	private Graph graph;
	private boolean[] color;
	private boolean[] marked;
	private static final boolean BLACK = true;
	private static final boolean RED = false;
	private boolean binary_color=true;
	public Binarycolor(Graph graph) {
		this.graph = graph;
		marked = new boolean[graph.V()];
		color = new boolean[graph.V()];
		for (int i = 0; i < marked.length; i++) {
			if (!marked[i]){
				color[i]=BLACK;
				dfs(i);
			}
		}
	}
	private void dfs(int s) {
		marked[s]=true;
		for(int i:this.graph.adj(s)){
			if(!marked[i]){
				color[i]=!color[s];
				dfs(i);
			}else if (color[i]==color[s]) {binary_color=false;}
		}
	}
	public boolean isbincolor(){
		return binary_color;
	}
	@Override
	public String toString() {
		return String.valueOf(binary_color);
	}
	public static void main(String[] args) throws Exception {
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		Binarycolor bc=new Binarycolor(graph);
		System.out.println(bc);
	}
}

連通性

使用深度優先搜索和廣度優先搜索能夠實現和union-find的功能,下面是廣度優先搜索實現連通分量:

import java.io.*;
import java.util.*;
/**
 * 基於廣度優先搜索的連通分量,深度優先搜索和union-find都可以實現
 * @author XY
 *
 */
public class BFcc {
	private boolean[] marked;
	private Graph graph;
	private int[] id;//頂點所在連通分量
	private int cc_count=0;//連通分量計數
	private Queue<Integer> queue;
	public BFcc(Graph graph){
		this.graph=graph;
		marked=new boolean[graph.V()];
		id=new int[graph.V()];
		for (int i = 0; i < id.length; i++) {
			id[i]=0;
		}
		queue=new LinkedList<Integer>();
		for (int i = 0; i < marked.length; i++) {			
			if(!marked[i]){
			cc_count++;
			queue.add(i);
			while(!queue.isEmpty())
				bfs(queue.poll());				
			}
		
		}		
	}
	private void bfs(int s){
		marked[s]=true;
		id[s]=cc_count;
		for(int x:this.graph.adj(s)){
			if(!marked[x]){
			queue.add(x);
			marked[x]=true;
			id[x]=cc_count;
			}
		}
	}
	public int count(){
		return cc_count;
	}
	public int id(int w){
		return id[w];
	}
	public boolean isconnected(int v,int w){
		return id[w]==id[v];
	}
	public static void main(String[] args) throws Exception {
		Graph graph=new Graph(new Scanner(new File("E:"+File.separator+"graph.txt")));
		BFcc cc=new BFcc(graph);
		System.out.println(cc.cc_count);
		System.out.println(cc.isconnected(0, 2));
	}
}

圖的性質:離心率、半徑、直徑

import java.io.File;
import java.util.Arrays;
import java.util.Scanner;
/**
 * 圖的屬性:半徑、直徑、頂點的離心率
 * @author XY
 *
 */
public class GraphProperties {
	private int[] eccen;//頂點的離心率數組
	private int d = -1;
	private int r = Integer.MAX_VALUE;
	private int c = -1;

	public GraphProperties(Graph graph) {
		if (new BFcc(graph).count() != 1)
			throw new IllegalArgumentException("this graph is not connected");
		else {
			int len = graph.V();
			eccen = new int[len];
			for (int i = 0; i < len; i++) {
				BFPath path = new BFPath(graph, i);
				//離心率爲從頂點出發到其他頂點的最短路徑的最大值,所以使用廣度優先搜索
				int count = 0;
				for (int j = 0; j < len; j++) {
					count = path.path2(j) > count ? path.path2(j) : count;//找最大值
				}
				eccen[i] = count;
			}
		}
	}

	public int eccentricity(int v) {
		return this.eccen[v];
	}

	public int diameter() {//直徑爲最大離心率
		if (d == -1)
			for (int i : eccen)
				d = i > d ? i : d;
		return d;
	}

	public int radius() {//半徑爲最小離心率
		if (r == Integer.MAX_VALUE)
			this.center();
		return r;
	}

	public int center() {//離心率最小的某個頂點
		if (c == -1) {
			for (int i = 0; i < eccen.length; i++)
				if (eccen[i] < r) {
					r = eccen[i];
					c = i;
				}
		}
		return c;
	}

	@Override
	public String toString() {
		return Arrays.toString(eccen);
	}

	public static void main(String[] args) throws Exception {
		Graph graph = new Graph(new Scanner(new File("E:" + File.separator
				+ "graph.txt")));
		GraphProperties p = new GraphProperties(graph);
		System.out.println(p);
		System.out.println(p.diameter());
		System.out.println(p.radius());
		System.out.println(p.center());
	}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章