版权声明:

本文由Faye_Zuo发布于http://www.cnblogs.com/zuofeiyi/, 本文可以被全部的转载或者部分使用,但请注明出处.

这一个月以来,都在学习平面上的地图搜索,主要涉及到深度优先搜索(DFS)和广度优先搜索(BFS)。这两个算法和相关数据结构我整整学习了一个月,中间经历了很多对自己畏难情绪的克服,现在终于搞懂了,不过并没有掌握得很好。要想掌握得很好,需要后期编码来巩固加强了。

先从这道leetcode上面的题目作为引子:

Number of islands(200)

Given a 2d grid map of ‘1’s (land) and ‘0’s (water), count the number of islands.An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.You may assume all four edges of the grid are all surrounded by water.

开始看到这道题目时,我的内心是崩溃的。因为我完全是连题目都看不懂,什么是islands?

  islands定义
(1)被water包围;
(2)被水平或者垂直lands相连。
(3)一个只包含0和1的二维数组,找到里面不相邻的只包含1的块的个数。
 
然后看到答案,说解决这道题既可以用到DFS,又可以用到BFS。之前对DFS还有一点点接触,然后BFS就非常头大了。只要遍历一遍,碰到一个1,就把他周围相邻的1都标记为非1,这样整个遍历过程中碰到的1的个数就是所求解。 
 
第一部分:DFS(Depth-first search)—深度优先搜索
不理解的东西很多,从“图”的概念开始就不懂,找了很多视频,慢慢学。最后大概花了一周完全理解了
 
第一个大型知识点:图
一. 图的遍历定义
从任意指定的顶点(初始点)出发,按照一定的搜索方法依次访问图中所有顶点,且每个顶点仅被访问一次。
 
二. 回路
图中任意一个顶点都可能和其余顶点相邻接,从图的初始点到其余顶点可能存在多条路径。因此,在访问了某个结点之后,很可能顺着某条回路又回到该顶点。
 
三. 深度优先搜索的基本思想(递归)
(1)首先访问初始出发点,并标记为已访问。
(2)任选一个与Vo相邻且没有被访问的邻接点V1作为新的出发点,访问V1之后,设为已访问。
(3)以V1为新的出发点,访问与V1相邻且没有被访问的任一顶点V2.
(4)重复上述过程,若遇到一个所有邻接点均被访问过的结点,则回到已访问结点序列中最后一个还没有被访问的相邻结点的顶点,再DFS,直到图中所有顶点都被访问过,结束。
 
四 .DFS核心代码
private void dfs(int v, boolean[] visited){                v为初始结点,visited表示是否已访问
 System.out.print(getValue(v));                            getValue(v) 访问该结点
 visited[v]=true;                                          visited[v]=true; 做已访问标记
 int w=getFirstNeighbor(v);                                getFirstNeighbor(v) 取第一个邻接点                            
 while(w!=-1){                                             当邻接点存在时循环        
  if(!=visited[w]){                                        如果没有访问过
   dfs(w,visited);                                         递归遍历
 }
w=getNextNeighbor(v,w);                                    取下一个邻接结点
}
 
 最后花了一周的时间,大概能默写出下面程序:
  static class Solution {
    private int m, n;

    public int numIslands(char[][] grid) {
      m = grid.length;
      if (m == 0) {
        return 0;
      }
      n = grid[0].length;
      if (n == 0) {
        return 0;
      }

      int ans = 0;
      for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
          if (grid[i][j] != '1') {
            continue;
          }

          ans++;
          dfs(grid, i, j);
        }
      }

      return ans;
    }

    public void dfs(char[][] grid, int i, int j) {              \\dfs是一种查找方式,所以没有返回值
      if (i < 0 || i >= m || j < 0 || j >= n) {
        return;
      }

      if (grid[i][j] == '1') {
        grid[i][j] = '2’;                                      \\表示已经访问
     
        dfs(grid, i - 1, j);                                   \\四周相邻的点
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i, j + 1);
      }
    }
  }

}
 
第二部分:BFS(Breadth-first search)—广度优先搜索
更加类似于一个分层搜索的过程,广度优先遍历需要使用一个队列(queue)以保持访问过的结点顺序,以便按这个顺序来访问这些结点的邻接结点。
 
第二个大型知识点:二维数组
对的,二维数组很多基本的知识我并没有掌握,这就是文科生强行学习CS的艰辛,简直想掐死自己有时候。
 
1.定义
(1)静态定义
int[][] arr={{3,1,3},{5,8,2,9},{4,1}}
 
(2)动态定义
int[][] arr=new int[3][2];
 
char[][] grid = new char[][] { { '0', '1', '1', '0' }, { '1', '0', '0', '1' },
        { '1', '0', '0', '1' }, { '1', '1', '0', '0' } };
 
2.打印
(1)打印二维数组
System.out.println(array);
 
(2)打印二维数组中的一维数组
System.out.println(array[0]);
 
(3)打印二维数组中的某个元素
System.out.println(array[0][0]);
 
3.长度
(1)一维数组的个数
array.length
 
(2)每个一维数组的长度
array[1].length
 
4.遍历
for(int x=0;x<array.length;x++)
  {
    for(int y=0;y<array[x].length;y++)
     {
         System.out.println(array[x][y]);
     }
   }
 
 第三个大型知识点:队列
队列一个最大的原理就是:先进先出,后进后出
 
广度优先搜索的原理 

1.首先访问1,将1的邻居放入队列:
 

1 2 3   

2.根据队列先进先出的原则,1出来。访问队头2的邻居:
 

2 3 4 6

3.根据队列先进先出的原则,2出来,访问队头3的邻居(3的邻居已经在里面了)
 

3 4 6 

4.根据队列先进先出的原则,3出来,访问4的邻居(4的邻居已经在里面)
 

4 6

5.根据队列先进先出的原则,4出来,访问6的邻居
 

6 5

顺序:1 2 3 4 6 5
 
最后得出结论:
(1)一定是先进先出;
(2)一定是访问队头的邻居
 
第四个大型知识点:集合大家族
1.ArrayList
  动态数组:每一个ArrayList都有一个初始容量,也就是数组的大小。数组的大小随着元素的增加而增加,容量快溢出时,进行扩容操作。
 
2.LinkedList
  双向链表:格外提供get、remove、insert方法在LinkedList的首位部。
 
3.ArrayList和LinkedList的区别
(1)对于随机访问get、set,ArrayList优于LinkedList,因为LinkedList要移动指针。
(2)对于新增和删除,LinkedList占优势,ArrayList要移动数据。 
 
第五个大型知识点:图的邻接矩阵(adjacency matrix)
1.无向图 

5.邻接矩阵的特点:
(1)一个图的邻接矩阵一定是唯一的
(2)对于一个无向图来说,他的邻接矩阵是关于主对角线对称的。
无向图有n个顶点,则邻接矩阵的存储空间为 n(n+1)/2(对称的);
有向图有n个顶点,则邻接矩阵的存储空间为 n的平方。
(3)借助邻接矩阵可判断两个顶点是否有边,查找边和权都很方便。
(4)利用邻接矩阵很容易计算出图中各顶点的度。
无向图:矩阵中第i行非零元素或者第j行元素的个数就是顶点i的度;
有向图:第i行非零元素的个数是顶点Vi的出度,第i列的非零元素为顶点入度。
 
 
花了整整三个星期,我才理解到这个程序。除了上面的大型知识点,里里外外还有泛型啊,抽象类和接口等等java的基础知识,我都像一头猪。由于程序实在太长了,我自己写出来的程序run不出来,但是我肉眼已经看不出来错误在哪里。于是克服了心里障碍,开始学会一步一步的打印,学会分析计算机的运算过程,从而来理解程序。这是一个很大的突破,我今天下午通过打印功能,终于理解了以下程序染色的过程。我之前的理解的确是错误了。这个方法也能帮助我以后自行查找错误。
 
BFS程序如下: 
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
 
public class BFS {
 
  public static void main(String[] args) {
 
 
  }
 
  private Queue<Node> queue;
  static ArrayList<Node> nodes=new ArrayList<Node>();
  static class Node{
    int data;
    boolean visited;
    Node(int data){
      this.data=data;
    }
  }
 
  public BFS(){
    queue=new LinkedList<Node>();
  }
 
  public ArrayList<Node> findNeighbours(int adjacency_matrix[][],Node x){      
    int nodeIndex=-1;
    ArrayList<Node> neighbours=new ArrayList<Node>();
    for(int i=0;i<nodes.size();i++){                                      //第一个for循环用来找element在数组中的index
      if(nodes.get(i).equals(x)){
        nodeIndex=i;
        break;
      }
    }
 
    if(nodeIndex!=-1){                                                   //已经找到index,第二个for循环用来找1,表示相邻
      for(int j=0;j<adjacency_matrix[nodeIndex].length;j++){             //然后返回给neighbour数组
        if(adjacency_matrix[nodeIndex][j]>0){
          neighbours.add(nodes.get(j));
        }
      }
    }
    return neighbours;
 
  }
 
  public void bfs(int adjacency_matrix[][],Node node){                    //bfs的主函数,从这里看起。主函数就是运用队列
    queue.add(node);
    node.visited=true;
    while(!queue.isEmpty()){
      Node element=queue.remove();                                        //注意:队列的remove都是从头开始的
      System.out.println(element.data);                                    //这步打印的是queue的第一个元素
 
      ArrayList<Node> neighbours=findNeighbours(adjacency_matrix,element); //这步开始找邻居
 
      for(int i=0;i<neighbours.size();i++){
        Node n=neighbours.get(i);
        if(n!=null&&!n.visited){
          queue.add(n);
          n.visited=true;
        }
      }
 
    }
  }
 
 
Numbers of Islands的BFS解法完成程序如下:

import java.util.ArrayDeque;
import java.util.Deque;

public class numberOfIslandBFS {

  public static void main(String[] args) {
    char[][] grid = new char[][] {
      { '0', '1', '1', '0' },
      { '1', '0', '0', '1' },
      { '1', '0', '0', '1' },
      { '1', '1', '0', '0' } };
    System.out.println("#islands=" + new Solution().numIslands(grid));

  }

  private static class Point {
    int x;
    int y;

    Point(int x, int y) {
      this.x = x;
      this.y = y;
    }
  }

  public static class Solution {
    public int numIslands(char[][] grid) {
      int res = 0;
      if (grid == null || grid.length == 0 || grid[0].length == 0) {
        return 0;
      }
 
      int row = grid.length;                                      \\row和col主要用来设置for循环的边界
      int col = grid[0].length;

      boolean[][] visited = new boolean[row][col];
      int[][] directions = {
          { 1, 0 },
          { -1, 0 },
          { 0, 1 },
          { 0, -1 } };
      Deque<Point> queue = new ArrayDeque<Point>();

      for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
          if (grid[i][j] == '0' || visited[i][j]) {
            continue;
          }

          queue.add(new Point(i, j));
          res += 1;
          while (!queue.isEmpty()) {
            Point cur = queue.poll();                                     // queue =[ (1,1) ]
            int tpx = cur.x;                                              // 1,用来记录弹出后的x,y
            int tpy = cur.y;                                              // 1
 
            visited[tpx][tpy] = true;                                    //并且标记为已访问
            for (int k = 0; k < 4; k++) {
              int x = tpx + directions[k][0]; // 0
              int y = tpy + directions[k][1]; // 1

              if (x >= 0 && x < row && y >= 0 && y < col && grid[x][y] == '1'
                  && !visited[x][y]) {
                queue.add(new Point(x, y));
              }
            }
          }
        }
      }

      return res;
    }

  }

}
 
 
 

平面上的地图搜索--Java学习笔记(四)的更多相关文章

  1. Java学习笔记四---打包成双击可运行的jar文件

    写笔记四前的脑回路是这样的: 前面的学习笔记二,提到3个环境变量,其中java_home好理解,就是jdk安装路径:classpath指向类文件的搜索路径:path指向可执行程序的搜索路径.这里的类文 ...

  2. 初涉深度优先搜索--Java学习笔记(二)

    版权声明: 本文由Faye_Zuo发布于http://www.cnblogs.com/zuofeiyi/, 本文可以被全部的转载或者部分使用,但请注明出处. 上周学习了数组和链表,有点基础了解以后,这 ...

  3. Java学习笔记四——运算符

    算术运算符 加减乘除(+.-.*./)就不说了. 求余运算符% 描述:第一个操作数除以第二个操作数,得到一个整除的结果后剩下的值就是余数 注意:求余预算的结果不一定总是整数,当操作数是浮点数时,结果可 ...

  4. Java 学习笔记 (四) Java 语句优化

    这个问题是从headfirst java看到的. 需求: 一个移动电话用的java通讯簿管理系统,要求最有效率的内存使用方法. 下面两段程序的优缺点,哪个占用内存更少. 第一段: Contact[]c ...

  5. Java学习笔记四:Java的八种基本数据类型

    Java的八种基本数据类型 Java语言提供了八种基本类型.六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型. Java基本类型共有八种,基本类型可以分为三类,字符类型char,布 ...

  6. Java学习笔记四:三目运算符与字符串连接符等

    一 .三目运算符与自增自减 GitHub代码练习地址:https://github.com/Neo-ML/JavaPractice/blob/master/OperPrac02.java 条件运算符由 ...

  7. Java学习笔记四

    1.简介.进程和线程:简单的说就是进程负责为程序开辟内存空间,线程负责具体的执行单元(执行路径). 一个进程中可以有多个执行路径,称为多线程.CPU一次只能执行一个进程,但是一个进程内部可以有多个线程 ...

  8. 面向对象三大特征之封装与static——(Java学习笔记四)

    面向对象     编程本质:以类的方式组织代码,以对象的方式组织(封装)数据 对象:具体的事物,某个类的对象(实例) 类:是对对象的抽象,用于描述同一类型的对象的一个抽象概念 对象和类的关系:特殊到一 ...

  9. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

随机推荐

  1. MySQL-基本sql命令

    关于环境的搭建和数据库的连接,我直接跳过,假设电脑上已经安装好了MySQL的环境,接下来直接进行数据库的操作:(虽然数据库不区分大小写,但是本文约定:命令用大写,用户变量和字段用小写) 1.创建数据库 ...

  2. oracle关键字(保留字)

    其实这个东西可以在oracle 上输入一个sql语句就可以得到: select * from v$reserved_words order by keyword asc;   //order 后边不是 ...

  3. Python3基础 在print中用 %d 输出一个整数

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  4. 13.TCP的超时与重传

    TCP提供可靠的运输层.它使用的方法之一就是确认从另一端收到的数据.但数据和确认都有可能会丢失.TCP通过在发送时设置一个定时器来解决这种问题.如果当定时器溢出时还没有收到确认,它就重传该数据. 对于 ...

  5. 为什么ajax 必须同源,same origin policy

    ajax 所有请求都会附带主域的cookie, 若没有同源策略,攻击者就可以获取你的cookie,状态.

  6. Angular - - 脏值检查及其相关

    今天突然就想写写$digest和$apply,这些都是脏值检查的主体内容. 先以普通js来做一个简单的监控例子吧: var div = ducoment.getElementById("my ...

  7. ORACLE获取字符串中数字部分

    ') from dual; select regexp_replace('23456中国3-00=.,45','[^0-9]') from dual;标签:regexp_replace regexp ...

  8. 关于pandas精度控制

    最近使用pandas处理一批数据,数据中包含几个columns,它们的数据精度,例如 3.25165,1451684684168.0,0.23 处理完之后保存csv时发现,1451684684168. ...

  9. 【原创】梵高油画用深度卷积神经网络迭代十万次是什么效果? A neural style of convolutional neural networks

    作为一个脱离了低级趣味的码农,春节假期闲来无事,决定做一些有意思的事情打发时间,碰巧看到这篇论文: A neural style of convolutional neural networks,译作 ...

  10. Javaweb程序服务器部署

    话说从接触web后就想着写一个自己的站点然后别人都可以访问,这也是一个小目标吧,从之前在使用校园网的时候把自己的电脑当成服务器然后部署使用同学的电脑访问,现在想让所有人都可以访问,于是就花重金租了腾讯 ...