邏輯結構分爲兩部分: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步,就可跳成下圖局面:
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())); } }