本文講解無向圖,算法筆記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());
}
}