软工中级实训Part5:Grid Data Structures

Step1:The AbstractGrid Class

Set 10

  • isValid方法在哪里指定?哪些类提供此方法的实现?
    1. 回答:isValid()方法在Grid接口中被定义;在UnBoundedGrid类与BoundedGrid类中被实现。
    2. 代码:
// @file:GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java 
// @line: 60-64(BoundedGrid) 54-56(UnboundedGrid)
//(BoundedGrid)
    public boolean isValid(Location loc)
    {
        return 0 <= loc.getRow() && loc.getRow() < getNumRows()
                && 0 <= loc.getCol() && loc.getCol() < getNumCols();
    }
    
//(UnboundedGrid)
    public boolean isValid(Location loc)
    {
        return true;
    }
  • 哪些AbstractGrid类中的方法调用了isValid方法?为什么其他方法不需要调用它?
    1. 回答:
      a. getValidAdjacentLocations()函数直接调用了isVaild()函数;
      b. getEmptyAdjacentLocations()和getOccupiedAdjacentLocations()调用了函数getValidAdjacentLocations()间接调用isValid()函数。 只有这三个函数需要通过isVaild()函数的值来判断是否需要添加位置。
    2. 代码:
// @file:GridWorldCode\framework\info\gridworld\grid\AbstractGrid.java 
// @line: 36-49 54 65
    //getValidAdjacentLocations()函数
    public ArrayList<Location> getValidAdjacentLocations(Location loc)
    {
        ArrayList<Location> locs = new ArrayList<Location>();

        int d = Location.NORTH;
        for (int i = 0; i < Location.FULL_CIRCLE / Location.HALF_RIGHT; i++)
        {
            Location neighborLoc = loc.getAdjacentLocation(d);
            if (isValid(neighborLoc))
                locs.add(neighborLoc);
            d = d + Location.HALF_RIGHT;
        }
        return locs;
    }
    //getEmptyAdjacentLocations()函数中的调用
    for (Location neighborLoc : getValidAdjacentLocations(loc))
    //getOccupiedAdjacentLocations()函数中的调用
    for (Location neighborLoc : getValidAdjacentLocations(loc))
  • 在getNeighbors方法中调用了Grid接口的哪些方法?哪些类提供这些方法的实现?
    1. 回答:
      a. 调用了接口Grid()中的get()函数以及getOccupiedAdjacentLocations()函数。
      b. get()函数在BoundedGrid类与UnboundedGrid类中被实现;getOccupiedAdjacentLocations()函数在Abstrct()类中被实现。
    2. 代码:
// @file: GridWorldCode\framework\info\gridworld\grid\AbstractGrid.java 
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @file: GridWorldCode\framework\info\gridworld\grid\UnboundedGrid.java
//	@line: 62-71 85-91 66-71(对应文件的行数)
    //getOccupiedAdjacentLocation()函数
    public ArrayList<Location> getOccupiedAdjacentLocations(Location loc)
    {
        ArrayList<Location> locs = new ArrayList<Location>();
        for (Location neighborLoc : getValidAdjacentLocations(loc))
        {
            if (get(neighborLoc) != null)
                locs.add(neighborLoc);
        }
        return locs;
    }
    //BoundedGrid类中get()函数
    public E get(Location loc)
    {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        return (E) occupantArray[loc.getRow()][loc.getCol()]; // unavoidable warning
    }
    //UnboundedGrid类中get()函数
    public E get(Location loc)
    {
        if (loc == null)
            throw new NullPointerException("loc == null");
        return occupantMap.get(loc);
    }
  • 当get方法返回loaction而不是E类型的对象时,为什么必须在getEmptyAdjacentLocations方法中使用返回E类型的对象的get方法?
    1. 回答:因为getEmptyAdjacentLocations()函数的功能是通过使用get()函数找到一个临近的空白位置;而get()函数则是可以返回给定某个位置上是否有物体的存在,如果不存在返回null,只能一次对一个物体进行判断。
    2. 代码:
//getEmptyAdjacentLocations()函数
//@file:GridWorldCode\framework\info\gridworld\grid\AbstractGrid.java
 //@line: 51 - 60
     public ArrayList<Location> getEmptyAdjacentLocations(Location loc)
    {
        ArrayList<Location> locs = new ArrayList<Location>();
        for (Location neighborLoc : getValidAdjacentLocations(loc))
        {
            if (get(neighborLoc) == null)
                locs.add(neighborLoc);
        }
        return locs;
    }
  • 在getValidAdjacentLocations方法中出现的两个位置中,用Location.RIGHT替换常量Location.HALF_RIGHT会产生什么影响?
    1. 回答:如果Location.HALF_RIGHT被替换为Location.RIGHT,那么一个位置相邻的有效位置将从8个减少到四个,东北、东南、西北、西南这四个位置将无法被选中。

      我认为此条回答不需要附上代码以作补充解释。

Step2:The BoundedGrid Class

Set 11

  • 是什么确保grid具有至少一个有效位置?
    1. 回答:BoundedGrid类的构造函数当判断到row <= 0 或者 col <= 0时候会抛出一个错误。这样就确保了grid中至少有一个有效位置。
    2. 代码:
//BoundedGrid的构造函数
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 39-46
 public BoundedGrid(int rows, int cols)
    {
        if (rows <= 0)
            throw new IllegalArgumentException("rows <= 0");
        if (cols <= 0)
            throw new IllegalArgumentException("cols <= 0");
        occupantArray = new Object[rows][cols];
    }
  • 如何通过getNumCols方法确定grid中的列数?关于grid的什么假设使之成为可能?
    1. 回答:
      a. getNumCols()函数会返回 occupantArray[0].length,而这个返回值也就是列的个数;
      b. 由于BoundedGrid是个矩阵,所以第一行的列个数也就是整体矩阵的列个数。
    2. 代码:
//getNumCols()函数:
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 53-55
 public int getNumCols()
    {
        return occupantArray[0].length;
    }
  • 在BoundedGrid中有效的位置有什么要求?
    1. 回答:BoundedGrid中任意一个点的横坐标要<列数并且>=0,任意一点的纵坐标要<行数并且>=0,即一定要保证点不能越界。
    2. 代码:
//BoundedGrid的构造函数:
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 39-46
    public BoundedGrid(int rows, int cols)

在接下来的四个问题中,令r =行数,c =列数,n =占用位置数。

  • getOc​​cupiedLocations方法返回什么类型?此方法的时间复杂度(Big-Oh)是多少?
    1. 回答:
      a. 函数返回一个ArrayList集合,这个集合中元素是已经被actors占有的位置;
      b. 时间复杂度O(r*c)。
    2. 代码:
//getOccupiedLocations()函数
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 66-83
    public ArrayList<Location> getOccupiedLocations()
    {
        ArrayList<Location> theLocations = new ArrayList<Location>();

        // Look at all grid locations.
        for (int r = 0; r < getNumRows(); r++)
        {
            for (int c = 0; c < getNumCols(); c++)
            {
                // If there's an object at this location, put it in the array.
                Location loc = new Location(r, c);
                if (get(loc) != null)
                    theLocations.add(loc);
            }
        }

        return theLocations;
    }
  • get方法返回什么类型?需要什么参数?此方法的时间复杂度(Big-Oh)是多少?
    1. 回答:
      a. get()函数返回一个E类型的对象;需要的参数是这个对象的Location;
      b. 时间复杂度O(1);
    2. 代码:
//get()函数
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 85-91
   public E get(Location loc)
    {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        return (E) occupantArray[loc.getRow()][loc.getCol()]; // unavoidable warning
    }
  • 哪些条件可能导致put方法引发异常?此方法的时间复杂度(Big-Oh)是多少?
    1. 回答:
      a. 当放置的位置无效或者放置的实体为null时候put()函数会抛出错误;
      b. 该方法的时间复杂度O(1);
    2. 代码:
//put()函数
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 93-105
    public E put(Location loc, E obj)
    {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        if (obj == null)
            throw new NullPointerException("obj == null");

        // Add the object to the grid.
        E oldOccupant = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = obj;
        return oldOccupant;
    }
  • remove方法返回什么类型?尝试从空的位置删除项目会发生什么?此方法的时间复杂度(Big-Oh)是多少?
    1. 回答:
      a. remove()函数返回一个E类型的对象;
      b. 当移除的是一个无效的位置时候,会抛出一个异常错误;
      c. 时间复杂度为O(1);
    2. 代码:
//reMove()函数
// @file: GridWorldCode\framework\info\gridworld\grid\BoundedGrid.java
// @line: 106-117
        public E remove(Location loc)
    {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        
        // Remove the object from the grid.
        E r = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = null;
        return r;
    }

  • 根据对问题4、5、6和7的回答,您是否认为这是一种有效的实施方式?证明你的答案。
    1. 回答:我觉得这个实现是有效的。因为当删除,添加,访问某个位置上的某个对象时候,时间复杂度都是O(1)级别的,减少了运算量和内存消耗;同时查询时候是需要遍历的,此时时间复杂度为O(r*c),n^2级别的时间复杂度是可以接受的。因此从这两个维度来说,该实现是比较好的。
    2. 代码:
我认为该题目不需要用代码以作补充。

Step3:The UnboundedGrid Class

Set 12

  • Location类必须实现哪种方法,以便HashMap的实例可以用于map?如果改用TreeMap,Location类将需要什么?Location 是否满足这些要求?
    1. 回答:
      a. Location类中必须实现hashCode()函数与equals()函数,由于Location()使用Compare接口中的方法,所以这个CompareTo()函数也需要实现;
      b. 如果要使用TreeMap,那么必须包含Map关键字;
      c. Location类满足这些需求;
    2. 代码:
//equals()函数与hashCode函数()
// @file: GridWorldCode\framework\info\gridworld\grid\Location.java
// @line: 218-221 , 205-212 , 234-246
    public int hashCode()
    {
        return getRow() * 3737 + getCol();
    }
    public boolean equals(Object other)
    {
        if (!(other instanceof Location))
            return false;

        Location otherLoc = (Location) other;
        return getRow() == otherLoc.getRow() && getCol() == otherLoc.getCol();
    }
    
    public int compareTo(Object other)
    {
        Location otherLoc = (Location) other;
        if (getRow() < otherLoc.getRow())
            return -1;
        if (getRow() > otherLoc.getRow())
            return 1;
        if (getCol() < otherLoc.getCol())
            return -1;
        if (getCol() > otherLoc.getCol())
            return 1;
        return 0;
    }
  • 为什么对null的检查包含在get,put和remove方法中?为什么BoundedGrid的相应方法中没有包含此类检查?
    1. 回答:
      a. 由于UnboundedGrid对象实例是无界的,所以所有的位置均为有效位置,所以isValid()函数无法发挥作用判断一个位置是否出界;而get,put,remove函数首先需要判断位置是否有效,这在UnboundedGrid对象实例中是不必要的行为,所以这个几个方法也没有被加入到UnboudedGrid类中;
      b. 而BoundedGrid对象实例与上面提到的不太一样,这个对象实体是有界的,也就需要我们先去判断位置是否出界,所以在BoundedGrid类中,这几个方法仍然可以使用。
    2. 代码:
//UnboundedGrid类中isValid()函数
// @file: GridWorldCode\framework\info\gridworld\grid\UnboundedGrid.java
// @line: 53 - 56
    public boolean isValid(Location loc)
    {
        return true;
    }
  • 三种方法(get、put、remove)的平均时间复杂度(Big-Oh)是多少?如果使用TreeMap代替HashMap会怎样?

    1. 回答:
      a. 平均时间复杂度为 O(1);
      b. 如果使用HashMap,时间复杂度将会变为 O(log n);

      以下是对题目的解析:
      在本题中put,get,remove方法都实现需要先找到该实体(也即查询),然后再进行操作;使用TreeMap相当于基于二叉排序树对Grid进行构造,构造的依据是横纵坐标的大小,所以在查询时候也是基于横纵坐标的大小进行查找,在这里的查找过程也近似于二分查找,所以时间复杂度为O(log n);而在使用HashMap时候,直接可以通过下标在O(1)级别的时间复杂度上直接找到该物体,而无需遍历。

  • 如果使用TreeMap代替HashMap,则除了时间复杂度之外,此类的行为还会有什么不同?

    1. 回答:访问实例对象的方法也会发生变化。在HashMap方法中,直接通过下标访问对象实例,而在TreeMap中则需要通过树的遍历进行匹配,进而完成对实体对象的访问;
    2. 代码:
// @file: https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html
// @file: https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html
  • 可以将map实现用于BoundedGrid吗?与map实现相比,BoundedGrid类使用的二维数组实现有什么优势(如果有)?
    1. 回答:
      a. 可以被用于BoundedGrid;
      b. 当一个Grid存满时候,如果只使用Array不用ArrayList的话,可以免除对实体对象坐标的存储,这样就可以减少一大部分的内存开销。
    2. 代码:
//@file:https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
//@file:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Array.html

set 13

Part5 编程实现问题解答

  • 考虑使用HashMap 或 TreeMap实施SparseBoundedGrid。您如何使用UnboundedGrid类完成此任务?UnboundedGrid可以使用哪些方法而无需更改?填写表格,比较每次实施的预期Big-Oh效率SparseBoundedGrid。
    令r =行数,c =列数,n =占用位置数
    a. SparseBoundedGrid继承UnboundedGrid,所以SparseBoundedGrid实体对象就可以使用UnboundedGrid类中的函数完成相应的功能;

a. get(),put(),remove(),getOccupiedLocations()函数可以仍旧使用
c. 表格

方法SparseGridNode 版LinkedList 版HashMap 版TreeMap 版
getNeighborsO( c )O( c )O(1)O(log n)
getEmptyAdjacentLocationsO( c )O( c )O(1)O(log n)
getOccupiedAdjacentLocationsO( c )O( c )O(1)O(log n)
getOccupiedLocationsO(r + n)O(r + n)O(n)O(n)
getO( c )O( c )O(1)O(log n)
putO( c )O( c )O(1)O(log n)
removeO( c )O( c )O(1)O(log n)
  • 考虑一个无边界网格的实现,其中所有有效位置的行和列值均为非负值。构造函数分配一个16 x 16的数组。当调用行方法或列索引超出当前数组范围的put方法时,请将两个数组范围加倍,直到它们足够大,使用这些范围构造一个新的正方形数组,然后将现有居住者放入新数组中数组。使用此数据结构实现Grid接口指定的方法。get方法的Big-Oh效率是多少?当行和列索引值在当前数组范围内时,put方法的效率如何?需要调整阵列大小时的效率如何?
    回答:
    a. get的时间复杂度O(1)
    b. 被放置的实体在Grid内时候时间复杂度为O(1);当需要扩展大小时候,时间复杂度为O(n*n),n为新Grid维数的大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值