圖表與其餘

鄰接矩陣

邏輯結構分爲兩部分:V和E集合,其中,V是頂點,E是邊。所以,用一個一維數組存放圖中全部頂點數據;用一個二維數組存放頂點間關係(邊或弧)的數據,這個二維數組稱爲鄰接矩陣。鄰接矩陣又分爲有向圖鄰接矩陣和無向圖鄰接矩陣。java

所以只要對任意一個圖,均可以使用鄰接矩陣表示。web

鄰接矩陣(Adjacency Matrix)是表示頂點之間相鄰關係的矩陣。設G=(V,E)是一個圖,其中V={v1,v2,…,vn} 。G的鄰接矩陣是一個具備下列性質的n階方陣:
1. 對無向圖而言,鄰接矩陣必定是對稱的,並且主對角線必定爲零(在此僅討論無向簡單圖),副對角線不必定爲0,有向圖則不必定如此。
2. 在無向圖中,任一頂點i的度爲第i列(或第i行)全部非零元素的個數,在有向圖中頂點i的出度爲第i行全部非零元素的個數,而入度爲第i列全部非零元素的個數。
3. 用鄰接矩陣法表示圖共須要n^2個空間,因爲無向圖的鄰接矩陣必定具備對稱關係,因此扣除對角線爲零外,僅須要存儲上三角形或下三角形的數據便可,所以僅須要n(n-1)/2個空間。
算法

這樣咱們就能夠把一個抽象的圖,轉化爲程序能夠識別的矩陣數學邏輯問題了。編程

深度優先遍歷

深度優先搜索屬於圖算法的一種,英文縮寫爲DFS即Depth First Search。其過程簡要來講是對每個可能的分支路徑深刻到不能再深刻爲止,並且每一個節點只能訪問一次。
深度優先遍歷圖的方法是,從圖中某頂點v出發:
(1)訪問頂點v;
(2)依次從v的未被訪問的鄰接點出發,對圖進行深度優先遍歷;直至圖中和v有路徑相通的頂點都被訪問;
(3)若此時圖中尚有頂點未被訪問,則從一個未被訪問的頂點出發,從新進行深度優先遍歷,直到圖中全部頂點均被訪問過爲止。數組

深度優先遍歷具備探索特性,能夠優先尋找一切可以實現的路徑。所以,連通性類的全部題目適用於深度優先遍歷。網絡

連通性判斷

【問題描述】給定一個方陣,定義連通:上下左右相鄰,而且值相同。
能夠想象成一張地圖,不一樣的區域被塗以不一樣顏色。
輸入:
整數N, (N<50)表示矩陣的行列數
接下來N行,每行N個字符,表明方陣中的元素
接下來一個整數M,(M<1000)表示詢問數
接下來M行,每行表明一個詢問,
格式爲4個整數,y1,x1,y2,x2,
表示(第y1行,第x1列) 與 (第y2行,第x2列) 是否連通。
連通輸出true,不然false
例如:
10
0010000000
0011100000
0000111110
0001100010
1111010010
0000010010
0000010011
0111111000
0000010000
0000000000
3
0 0 9 9
0 2 6 8
4 4 4 6svg

程序應該輸出:
false
true
truespa

import java.util.Scanner;

public class Connectivity { 
 
  
	static boolean connect(char[][] data,int y1,int x1,int y2,int x2) { 
 
  
		if(y1 == y2 && x1 == x2)
			return true;
		char old = data[y1][x1];
		data[y1][x1] = '*';
		try { 
 
  
			if(y1 > 0 && data[y1 - 1][x1] == old && connect(data,y1 - 1,x1,y2,x2))
				return true;
			if(y1 < data.length-1 && data[y1 + 1][x1] == old && connect(data,y1 + 1,x1,y2,x2))
				return true;
			if(x1 > 0 && data[y1][x1 - 1] == old && connect(data,y1,x1 - 1,y2,x2))
				return true;
			if(x1 < data.length-1 && data[y1][x1 + 1] == old && connect(data,y1,x1 + 1,y2,x2))
				return true;
		}
		finally{ 
 
  
			data[y1][x1] = old;
		}
		return false;
	}
	public static void main(String[] args) { 
 
  
		Scanner sc = new Scanner(System.in);
		int N = Integer.parseInt(sc.nextLine());
		char[][] data = new char[N][];
		for(int i=0; i<N; i++){ 
 
  
			data[i] = sc.nextLine().toCharArray();
		}
		
		int M = Integer.parseInt(sc.nextLine());
		for(int i=0; i<M; i++){ 
 
  
			String[] ss = sc.nextLine().split(" ");
			int y1 = Integer.parseInt(ss[0]);
			int x1 = Integer.parseInt(ss[1]);
			int y2 = Integer.parseInt(ss[2]);
			int x2 = Integer.parseInt(ss[3]);
			
			System.out.println(connect(data,y1,x1,y2,x2));
		}
	} 
}

風險度量

【問題描述】
標題:風險度量
X星系的的防衛體系包含 n 個空間站。這 n 個空間站間有 m 條通訊鏈路,構成通訊網。
兩個空間站間可能直接通訊,也可能經過其它空間站中轉。
對於兩個站點x和y (x != y), 若是能找到一個站點z,使得:
當z被破壞後,x和y不連通,則稱z爲關於x,y的關鍵站點。
顯然,對於給定的兩個站點,關於它們的關鍵點的個數越多,通訊風險越大。
你的任務是:已經網絡結構,求兩站點之間的通訊風險度,即:它們之間的關鍵點的個數。
輸入數據第一行包含2個整數n(2 <= n <= 1000), m(0 <= m <= 2000),分別表明站點數,鏈路數。
空間站的編號從1到n。通訊鏈路用其兩端的站點編號表示。
接下來m行,每行兩個整數 u,v (1 <= u, v <= n; u != v)表明一條鏈路。
最後1行,兩個數u,v,表明被詢問通訊風險度的兩個站點。
輸出:一個整數,若是詢問的兩點不連通則輸出-1.
例如:
用戶輸入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
則程序應該輸出:
2debug

import java.util.List;
import java.util.Scanner;
import java.util.Vector;

public class RiskMeasurement { 
 
  
	static void fan_zu(int[][] re, int me, int goal){ 
 
  
		if(re[me][3] <= goal) return;
		re[me][3] = goal;
		fan_zu(re, re[me][2], goal); 
	}
	
	static void dfs(List[] gr, int[][] re, int v1, int v2){ 
 
  
		if(v1==v2) return;
		
		for(Object obj: gr[v1]){ 
 
  
			int it = (Integer)obj;
			if(re[it][0] > 0){ 
 
  
				fan_zu(re,v1,re[it][1]);  // 更新整個家族的返祖級
				continue;
			}
			
			re[it][0] = 1;
			re[it][1] = re[v1][1]+1;
			re[it][2] = v1;
			re[it][3] = re[v1][1];
			dfs(gr,re,it,v2);
		}
	}
	
	static int solve(int[][] re, int root, int leaf){ 
 
  
		int sum = 0;
		int p = leaf;
		int min = re[p][3];  // 當前最高(小)返祖級
		while(true){ 
 
  
			int pa = re[p][2];
			System.out.println("pa " + pa);
			if(pa==0 || pa==root) break;
			if(re[pa][1] <= min) sum++;
			if(re[pa][3] < min) min = re[pa][3];
			p = pa;
		}
		return sum;
	}
	
	public static void main(String[] args)
	{ 
 
  
		Scanner scan = new Scanner(System.in);
		String[] ss = scan.nextLine().trim().split(" ");
		int m = Integer.parseInt(ss[0]); //節點數
		int n = Integer.parseInt(ss[1]); //邊數
		
		List[] gr = new List[m+1];    // 頂點 --> list(頂點)
		for(int i=0; i<gr.length; i++) gr[i] = new Vector();
		
		int[][] re = new int[m+1][4]; // dfs生成樹結果: 頂點 --> (可見性, 深度, 父節點, 最高返祖) 
		
		for(int i=0; i<n; i++){ 
 
  
			ss = scan.nextLine().trim().split(" ");
			int v1 = Integer.parseInt(ss[0]);
			int v2 = Integer.parseInt(ss[1]);
			gr[v1].add(v2);
			gr[v2].add(v1);
		}
		
		ss = scan.nextLine().trim().split(" ");
		int a = Integer.parseInt(ss[0]);
		int b = Integer.parseInt(ss[1]);
		
		re[a][0] = 1;
		re[a][1] = 0;
		re[1][3] = 0;
		dfs(gr,re,a,b);	
		
		System.out.println(solve(re,a,b));	
	}
	
	static void debug(int[][] re){ 
 
  
		for(int i=0; i<re.length; i++){ 
 
  
			System.out.println(i + ": " + re[i][0] + "," + re[i][1] + "," + re[i][2] + "," + re[i][3]);
		}
	}
}

廣度優先遍歷

寬度優先搜索算法(又稱廣度優先搜索)是最簡便的圖的搜索算法之一,這一算法也是不少重要的圖的算法的原型。Dijkstra單源最短路徑算法和Prim最小生成樹算法都採用了和寬度優先搜索相似的思想。其別名又叫BFS,屬於一種盲目搜尋法,目的是系統地展開並檢查圖中的全部節點,以找尋結果。換句話說,它並不考慮結果的可能位置,完全地搜索整張圖,直到找到結果爲止。code

廣度優先遍歷方法相似於層序遍歷,從一個點開始,全部路徑同時開始遍歷,像一個潮水,從開始到結尾,全部路徑將被一次性遍歷。

廣度優先遍歷具備掃描特性,能夠同時探索全部路徑,所以首先返回的必定是最短路徑。所以,廣度優先遍歷普遍用於尋找最短路徑算法和最少操做數的題目。

迷宮問題

【問題描述】
…11111111111111111111111111111
11.111111…1111111111.1111
11.111111…111.11111111…1111
11.11111111111.1111111111.111111
11.111111…111111
11.111111.11111111111.11111.1111
11.111111.11111111111.11111…111
11…111111111.11111.1111
11111.111111111111111.11…1111
11111.111111111111111.11.11.1111
11111.111111111111111.11.11.1111
111…111111111111111.11.11.1111
111.11111111111111111…11.1111
111.11111111111111111111111.1111
111.1111.111111111111111…11
111.1111…111111111.1111.11
111.1111.11111.111111111.1111.11
111…11111.111111111.1111111
11111111111111.111111111.111…1
11111111111111…1.1
111111111111111111111111111111…
如上圖的迷宮,入口,出口分別:左上角,右下角
「1"是牆壁,」."是通路
求最短鬚要走多少步?

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class MazeProblem { 
 
  		
	static int maze(char[][] data,Set come,String goal) { 
 
  
		if(come.contains(goal))
			return 0;
		
		Set set = new HashSet();
		
		for(Object object : come) { 
 
  
			String[] crr = ((String)object).split(",");
			
			int y = Integer.parseInt(crr[0]);
			int x = Integer.parseInt(crr[1]);
			
			if(y > 0 && data[y - 1][x] == '.') { 
 
  
				data[y - 1][x] = '*';
				set.add(y-1 + "," + x);
			}
			
			if(y < data.length - 1 && data[y + 1][x] == '.') { 
 
  
				data[y + 1][x] = '*';
				set.add(y+1 +","+x);
			}
			
			if(x > 0 && data[y][x - 1] == '.') { 
 
  
				data[y][x - 1] = '*';
				set.add(y+","+(x - 1));
			}
			
			if(x < data[0].length - 1 && data[y][x + 1] == '.') { 
 
  
				data[y][x + 1] = '*';
				set.add(y+","+(x + 1));
 			}
		}
		if(set.isEmpty())
			return -1;
		int r = maze(data,set,goal);
		if(r < 0)
			return r;
		return r + 1;
	}
	public static void main(String[] args) { 
 
  
		Scanner sc = new Scanner(System.in);
		int n = Integer.parseInt(sc.nextLine());
		int m = Integer.parseInt(sc.nextLine());
		
		char[][] data = new char[n][];
		for(int i = 0;i<data.length;i++) { 
 
  
			data[i] = sc.nextLine().trim().toCharArray();
		}
		
		Set set = new HashSet();
		set.add("0,0");
		System.out.println(maze(data,set,n-1 + "," + (m-1)));
	}
}

分酒問題(韓信走馬分油)

有4個紅酒瓶子,它們的容量分別是:9升, 7升, 4升, 2升
開始的狀態是 [9,0,0,0],也就是說:第一個瓶子滿着,其它的都空着。

容許把酒從一個瓶子倒入另外一個瓶子,但只能把一個瓶子倒滿或把一個瓶子倒空,不能有中間狀態。
這樣的一次倒酒動做稱爲1次操做。

假設瓶子的容量和初始狀態不變,對於給定的目標狀態,至少須要多少次操做才能實現?
本題就是要求你編程實現最小操做次數的計算。

輸入:最終狀態(空格分隔)
輸出:最小操做次數(如沒法實現,則輸出-1)

例如:
輸入:
9 0 0 0
應該輸出:
0

輸入:
6 0 0 3
應該輸出:
-1

輸入:
7 2 0 0
應該輸出:
2

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class LiquorDistribution { 
 
  
	static Set dispose(String liquor) { 
 
  
		int[] bottle = { 
 
  9,7,4,2};//瓶子的容積
		int[] data = new int[4];
		String[] ss = liquor.split(" ");
		for(int i = 0;i<4;i++)
			data[i] = Integer.parseInt(ss[i]);
		
		Set set = new HashSet();
		for(int i = 0;i<4;i++) { 
 
  //以i所在的瓶子爲酒
			for(int j = 0;j<4;j++) { 
 
  //以j所在瓶子爲目標,將i向j倒入
				if(i == j)
					continue;
				if(data[i] == 0)
					continue;
				if(data[j] == bottle[j])
					continue;
				
				int sum = data[i] + data[j];
				int Vi = (sum <= bottle[j]) ? 0 : (sum - bottle[j]);//i所在的酒的瓶子
				int Vj = (sum <= bottle[j]) ? sum : bottle[j];//j所在的目標的的瓶子
				
				String result = "";
				for(int k = 0;k<4;k++) { 
 
  
					if(k == i)
						result += Vi + " ";
					else if(k == j)
						result += Vj + " ";
					else
						result += data[k] + " ";
				}
				
				set.add(result.trim());
			}
		}
		return set;
	}
	static int distribute(Set history,Set begin,String end) { 
 
  
		if(begin.contains(end))
			return 0;
		Set set = new HashSet();
		
		for(Object obj:begin) { 
 
  
			Set temp = dispose(obj.toString());
			set.addAll(temp);
		}
		set.removeAll(history);
		if(set.isEmpty())
			return -1;
		history.addAll(set);
		int r = distribute(history,set,end);
		if(r < 0)
			return r;
		return r + 1;
	}
	
	public static void main(String[] args) { 
 
  
		Scanner sc = new Scanner(System.in);
		
		Set begin = new HashSet();
		begin.add("9 0 0 0");
		Set history = new HashSet();
		history.addAll(begin);
		System.out.println(distribute(history,begin,sc.nextLine().trim()));
	}
}

青蛙交換問題

【問題描述】
X星球的流行寵物是青蛙,通常有兩種顏色:白色和黑色。
X星球的居民喜歡把它們放在一排茶杯裏,這樣能夠觀察它們跳來跳去。
以下圖,有一排杯子,左邊的一個是空着的,右邊的杯子,每一個裏邊有一隻青蛙。
*WWWBBB
其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。
X星的青蛙頗有些癖好,它們只作3個動做之一:

  1. 跳到相鄰的空杯子裏。
  2. 隔着1只其它的青蛙(隨便什麼顏色)跳到空杯子裏。
  3. 隔着2只其它的青蛙(隨便什麼顏色)跳到空杯子裏。

對於上圖的局面,只要1步,就可跳成下圖局面:
WWW · BBB
本題的任務就是已知初始局面,詢問至少須要幾步,才能跳成另外一個目標局面。
輸入爲2行,2個串,表示初始局面和目標局面。
輸出要求爲一個整數,表示至少須要多少步的青蛙跳。
例如:
輸入:
· WWBB
WWBB ·
則程序應該輸出:
2
再例如,
輸入:
WWW · BBB
BBB · WWW
則程序應該輸出:
10
咱們約定,輸入的串的長度不超過15

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class frogJump { 
 
  
	static void move(char[] arr,int i,int step,Set frogs) { 
 
  
		if(arr[i] == '*')
			return;
		int moved = i + step;
		if(moved < 0 || moved >= arr.length)
			return;
		if(arr[moved] != '*')
			return;
		
		arr[moved] = arr[i];
		arr[i] = '*';
		frogs.add(new String(arr));
		arr[i] = arr[moved];
		arr[moved] = '*';
	}
	static Set Frog(String frog) { 
 
  
		Set s = new HashSet();
		char[] arr = frog.toCharArray();
		for(int i = 0;i<arr.length;i++) { 
 
  
			move(arr,i,-1,s);
			move(arr,i,-2,s);
			move(arr,i,-3,s);
			move(arr,i,1,s);
			move(arr,i,2,s);
			move(arr,i,3,s);
		}
		return s;
	}
	static int Jump(Set begin,Set history,String goal) { 
 
  
		if(begin.contains(goal))
			return 0;
		Set set = new HashSet();
		for(Object obj:begin) { 
 
  
			Set temp = Frog(obj.toString());
			set.addAll(temp);
		}
		set.removeAll(history);
		if(set.isEmpty())
			return -1;
		history.addAll(set);
		int r = Jump(set,history,goal);
		if(r < 0)
			return r;
		return r+1;
	}
	public static void main(String[] args) { 
 
  
		Scanner sc = new Scanner(System.in);
		
		Set begin = new HashSet();
		begin.add(sc.nextLine().trim());
		Set history = new HashSet();
		history.addAll(begin);
		System.out.println(Jump(begin,history,sc.nextLine().trim()));
	}
}