問題定義(以下均爲Java實現)
輸入一個行列的字符矩陣, 統計字符“@”組成多少個八連塊。 如果兩個字符“@”所在的格子相鄰( 橫、 豎或者對角線方向) , 就說它們屬於同一個八連塊。 例如, 下圖有個八連塊。
解題思路:深/廣度優先遍歷,記錄已經遍歷過的字符。深度優先遍歷有不同的實現,下面是非遞歸(棧)或者遞歸的兩種解法。
解法一
基於棧的DFS,當遍歷到某個字符時,入棧並標記該字符,然後繼續判斷該字符的相鄰字符,當該字符沒有相鄰字符爲“@”則出棧。代碼如下:
- 首先構造一個描述圖節點的類Dot,包含實例屬性r,c分別表示該字符所在行,所在列。其中關於get、set以及構造方法已經省略,此外爲了判斷是否已經遍歷過,重寫了equals和hashCode方法。
class Dot{
private int r;
private int c;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dot dot = (Dot) o;
return getR() == dot.getR() &&
getC() == dot.getC();
}
@Override
public int hashCode() {
return Objects.hash(getR(), getC());
}
}
- 下面是算法實現:
方法有一個參數二維數組graph,即字符圖。
集合exists用於保存已經遍歷過的字符。首先是兩層循環(多個八連塊),遍歷到某一字符時,如果該字符爲“@”且未遍歷過(則意味着新的八連塊出現),則以當前節點爲根進行DFS,這裏聲明瞭一個棧,根節點首先入棧,並加入exists中,然後判斷其周圍是否有符合條件的字符,有則入棧並繼續進行DFS,否則執行出棧,最後直至棧爲空,則當前的一個八連塊完畢,繼續下一個八連塊,直至整個圖都遍歷完畢,程序最後返回圖中八連塊的個數。
/**
* @param graph 圖
* @return 八連塊個數
*/
private int solver1(char[][] graph){
if(graph == null){
return 0;
}
Set<Dot> exists = new HashSet<>();
int rnum = graph.length;
int cNum = graph[0].length;
int count = 0;
for(int i = 0;i<rnum;i++){
for(int j = 0;j<cNum;j++){
Dot dot = new Dot(i,j);
if(graph[i][j] == '@' && !exists.contains(dot)){
Stack<Dot> stack = new Stack<>();
stack.push(dot);
exists.add(dot);
while(!stack.isEmpty()){
Dot cur = stack.peek();
int r1 = Math.max(0, cur.getR()-1);
int r2 = Math.min(cur.getR()+1, rnum-1);
int c1 = Math.max(0, cur.getC() - 1);
int c2 = Math.min(cur.getC()+1, cNum-1);
/* flag:當存在相鄰且未遍歷的字符“@”時,需要跳出循環*/
boolean flag = false;
for(int r = r1;r<=r2;r++){
for(int c = c1;c<=c2;c++){
Dot d = new Dot(r,c);
if(graph[r][c] == '@' && !exists.contains(d)){
stack.push(d);
exists.add(d);
flag = true;
break;
}
}
if(flag){
break;
}
}
/* 如果不存在相鄰且未遍歷的@字符,則出棧 */
if(cur == stack.peek()){
stack.pop();
}
}
count++;
}
}
}
return count;
}
解法二
基於遞歸的DFS,對於當前節點,首先判斷是否越界,是否爲“@”,是否已經遍歷過,否則符合條件,標記當前節點屬於哪個八連塊(這裏二維數組exists用於記錄節點屬於哪個八連塊),然後判斷周圍字符。
/**
*
* @param graph 圖
* @param r 字符縱座標
* @param c 字符橫座標
* @param id 八連塊序號
* @param exists 判斷是否已經遍歷過
*/
private void solver2(char[][] graph, int r, int c, int id, int[][] exists){
if(r<0 || c<0 || r>=graph.length || c>=graph[0].length){
return ;
}
if(exists[r][c] != 0 || !(graph[r][c]=='@')){
return;
}
exists[r][c] = id;
for(int i = -1;i<2;i++){
for(int j = -1;j<2;j++){
if(i != 0 || j != 0){
solver2(graph, r+i, c+j, id, exists);
}
}
}
}
輔助輸入輸出的代碼如下所示:
public class OilDeposits {
private int solver1(char[][] graph){{/*代碼在上面*/}
private void dfs(char[][] graph, int r, int c, int id, int[][] exists){/*代碼在上面*/}
public void solution(char[][] graph){
System.out.println("解法一:" + solver1(graph));
int[][] exists = new int[graph.length][graph[0].length];
int count = 0;
for(int i = 0;i<graph.length;i++){
for(int j = 0;j<graph[0].length;j++){
if(graph[i][j] == '@' && exists[i][j] == 0){
solver2(graph, i, j, ++count, exists);
}
}
}
System.out.println("解法二:" + count);
}
public static void main(String[] args){
char graph[][] = {
{'*','*','*','*','*','@',},
{'*','*','@','@','*','@',},
{'*','*','@','*','*','@',},
{'@','@','@','*','*','@',},
{'*','*','@','*','*','@',},
{'*','*','*','*','*','@',},
{'*','*','@','@','*','@',},};
OilDeposits oilDeposits = new OilDeposits();
oilDeposits.solution(graph);
}
}