2017-2018-1 20162330 实验一 线性结构


2017-2018-1 实验报告目录:   1   2   3   4   5


 课程名称:《程序设计与数据结构》

 学生班级:1623班

 学生姓名:刘伟康

 学生学号:20162330

 实验时间:2017年9月25日—2017年10月1日

 实验名称:线性结构

 指导老师:娄嘉鹏、王志强老师

目录


实验要求:

实验一 线性表的应用,实现和分析

  • 1.Java中的线性表的测试:过程,问题;
    2.Java中的线性表的应用:过程,问题;
    3.顺序表的实现(伪代码,产品代码,测试代码):过程(分析,设计,实现,测试),问题;
    4.链表的实现(伪代码,产品代码,测试代码):过程(分析,设计,实现,测试),问题;
    5.Java ArrayList,LinkedList源码分析:过程,学到的技巧。

【返回目录】

实验步骤及代码实现:

【代码托管1方法类】   【代码托管2单元测试】

1.线性结构-1:

  • ArrayList和LinkedList测试:
    查看ArrayList和LinkedList的Java API帮助文档,参考:http://www.cnblogs.com/rocedu/p/4837092.html
    用Junit对ArrayList和LinkedList的方法进行测试,要尽量覆盖正常情况,异常情况,边界情况。

  • ArrayList和LinkedList类中的基本操作:
    add()方法的两种用法:
        List.add(0,111);  //将指定的元素插入此列表中的指定位置(index,element)
        List.add(20162330);  //将指定的元素添加到此列表的尾部

clear()方法:

        for (int i = 0; i < size; i++)
                elementData[i] = null;  //将一个已经存在的表置成空表
        size = 0;

isEmpty()方法:(LinkedList类中没有)

    public boolean isEmpty() {
            return size == 0;  //判断列表是否为空
    }

get(i)方法:(注意 i 的取值范围为 0 ≤ i ≤ length()-1

        if (i < 0 || i > length - 1)
                System.out.println("第" + i + "个元素不存在");
        return List[i];  //读取并返回列表中第i个数据元素

remove()方法:(注意 i 的取值范围为 0 ≤ i ≤ length()-1

        if (i < 0 || i > length - 1)  //移除此列表中指定位置上的元素
                System.out.println("删除位置不合法");
        for (int j = i; j < length - 1; j++)
                List[j] = List[j + 1];  //元素前移
        length--;

indexOf(x)方法:

        int i = 0;
        while (i < length && !List[i].equals(x))  //依次查找
                i++;
        if (i < length)
                return i;  //返回此列表中首次出现的指定元素的索引
        else
                return -1;  //列表中不包含此数据元素

contains()方法:(与isEmpty()方法返回值类型相同)

        public static boolean Contains() {
                return List.contains(20162330);  //判断此列表中是否包含指定的元素
        }
  • 第一个实验使用Junit测试的方法比较简单,过程截图如下:(我用的是Junit4)

    ArrayList类方法测试:(单击图片可放大)
    1062725-20171001210011387-1083838187.png

    LinkedList类方法测试:
    1062725-20171001210033387-591015015.png

2.线性结构-2:

  • 分别用Java的ArrayList和LinkedList实现有序线性表的合并:
    aList,bList都是非递减线性表,合并后也是非递减
public static List<? extends Comparable> mergeSortedList(List<? extends Comparable> aList,
            List<? extends Comparable> bList) 

测试mergeSortedList的正确性,要尽量覆盖正常情况,异常情况,边界情况。

  • 首先要理解什么是“非递减线性表”,包括递增或者相邻几项相等的线性表,合并之后要求达到一种归并排序的效果。我在理解了意思,有了自己的思路之后,使用一些循环和条件语句实现此算法,需要注意的只有两点:
    (1) 两个线性表中的元素在归并时可能出现一个线性表中元素非空,而另一个线性表中元素为空的情况,这样继续归并就需要提前判断,不然就很可能出现越界异常。
    (2) 使用Junit测试为了避免出现断言空指针测试失败的情况,需要将归并后的元素遍历为String类型后再断言,这样元素的期待值就不会以线性表的形式陈列。
    我的归并排序算法如下:
for (int i = 0; ;) {
            if (aList.isEmpty() && !bList.isEmpty()) {    //提前判断每个线性表是否为空
                for (int j = bList.size() - i + 1; j <= bList.size(); j++) {    //添加非空的bList剩余的所有元素
                    List.add(bList.get(j));
                    bList.remove(j);
                }
            }
            else if (bList.isEmpty() && !aList.isEmpty()) {
                for (int j = aList.size() - i + 1; j <= aList.size(); j++) {    //添加非空的aList剩余的所有元素
                    List.add(aList.get(j));
                    aList.remove(j);
                }
            }
            else if (aList.isEmpty() && bList.isEmpty())    //元素排序结束
                break;
            else {    //两个线性表都非空
                if (aList.get(i).compareTo(bList.get(i)) == 0) {    //使用Comparable接口类中的compareTo方法比较并添加元素
                    List.add(aList.get(i));
                    aList.remove(i);
                    List.add(bList.get(i));
                    bList.remove(i);
                } else if (aList.get(i).compareTo(bList.get(i)) < 0) {
                    List.add(aList.get(i));
                    aList.remove(i);
                }
                else {    //aList比bList中的第 i 个元素大
                    List.add(bList.get(i));
                    bList.remove(i);
                }
            }
        }
        return List;  //返回已生成的线性表
    }
**ArrayList合并线性表测试**:

1062725-20171001220057497-2092822895.png

**LinkedList合并线性表测试**:

1062725-20171001220120872-496900237.png

3.线性结构-3:

  • 参考Java Foundation 3rd 第15.6节,用数组实现线性表List,用JUnit或自己编写驱动类对自己实现的ArrayList进行测试。

  • 用数组实现线性表明显需要对空间存储进行修改,无论使用静态方法还是动态方法定义数组,其存储空间都是固定的,所以这里有三种解决方法:
     直接使用Object类定义一个固定大小的数组;
     通过强制转型定义一个固定大小的数组:private T [] arr = (T [])new Object [num];
     当数组不能再存储线性表中的新元素时,创建一个更大的新数组来替换当前数组。
    我采用了第一种方法,后面的两种方法后面才想到,还未实现。
    private Object[] listSize = new Object[10];  //定义线性表及存储空间
    private int length = 0;  //线性表当前长度

采用方法一之后,定义类中的方法类型类似于第一个实验,这次我使用了 insert 方法在数组中插入新元素:

    //在线性表的第 i 个数据元素之前插入一个值为 x 的数据元素
    public void insert(int i, Object x) {
        if (length == listSize.length)
            System.out.println("线性表已满");
        if (i < 0 || i > length)
            System.out.println("插入位置不合法");
        for (int j = length; j < i; j--)
            listSize[j] = listSize[j - 1];  //元素后移
        listSize[i] = x;
        length++;
    }
Junit测试:

1062725-20171001225550997-699407855.png

4.线性结构-4:

  • 参考Java Foundation 3rd 第15.7节,用链表实现线性表List,用JUnit或自己编写驱动类对自己实现的LinkedList进行测试。

  • 首先介绍一下链表,因为教材中内容较少,所以我又查找了一些资料:
    1062725-20171001232419481-435352669.png

  • 用链表实现线性表,这个实验相对较难,对于链表娄老师和王老师都讲过,我只知其意,至于怎么实现还有些困难,于是参考了相关资料中的代码,首先单链表是由若干个结点连接而成的。因此,要实现单链表,首先要设置结点类。结点类由 data数据域和 next指针域两部分构成:
public class Node {
    private Object data;  // 存放结点值
    private Node next;  //后继结点

    public Node() {
        this(null, null);
    }

    public Node(Object data) {
        this(data, null);
    }

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}

对于单链表类,只需一个头指针就能唯一够标志它,对于单链表类,这里给出其中两个我已经理解的方法:

    //求带头结点的单链表的长度
    public int length() {
        Node p = head.getNext();  //p指向首结点
        int length = 0;
        while (p != null) {
            p = p.getNext();
            length++;
        }
        return length;
    }

    //读取带头结点的链表中的第i结点
    public Object get(int i) {
        Node p = head.getNext();
        int j = 0;
        while (p != null && j < i) {  //从首结点向后查找
            p = p.getNext();  //指向后继结点
            j++;
        }
        if (j > i || p == null) {
            System.out.println("第" + i + "个元素不存在");
        }
        return p.getData();
    }
对于链表的Junit测试,我有点“放水”地测试了比较简单的方法,原因是链表中一些方法的代码还未理解:

1062725-20171001231643387-1699643608.png

5.线性结构-5:

  • 参考:http://www.cnblogs.com/rocedu/p/7483915.html
    对Java的ArrayList,LinkedList按要求进行源码分析,并在实验报告中体现分析结果。

  • 源码分析,首先解压src源码包,找到ArrayList和LinkedList分析即可,其实我对源码分析的目的并不是很明确,源码中的大量注释基本能够解释其方法。总之,源码中的一些思想、添加注释的位置、代码的封装性等是值得学习的,我针对其中两个方法给出了自己的理解:
    第一个是ArrayList中的 trimToSize() 方法:
    private static final Object[] EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    private int size;

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
        }
    }
    //  modCount这个属性是在其父类AbstractList中定义的,主要记录ArrayList的结构性变化次数,这里的trimToSize方法
    //  的作用是调整适应列表的大小,如果size的值小于数据元素的长度,那么判断先size是否为零,这里使用三元运算符,
    //  如果为零,则赋给数据元素空值,如果不为零调整size为合适大小.
第二个比较简单,是LinkedList中的 `indexOf()` 方法:
    transient Object[] elementData;
    private int size;

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    //  indexOf方法比较熟悉一点,可以遍历列表并返回首次出现此元素索引(即使该元素是值为空),
    //  如果不包含(不存在此索引),则返回-1.

【返回目录】

测试过程及遇到的问题:

1.第二个实验对两个线性表进行归并排序时,出现下标越界异常。"aList" 和 "bList" 我各分配了四个数的空间,为什么在归并到每个列表都只剩两个数的时候出现越界异常?

  • 1062725-20171002011556818-1197532102.png

  • 解决办法:(使用debug单步跟踪进行调试)
    1062725-20171002014907708-67323043.png

    经过调试我发现当我传入的 i 自增到 2 时,aListbList各自都只剩下两个数的空间了,所以运行下一步时就会越界。原来我认为在线性表中remove了一个元素,后面元素的位置就不会再改变,但是事实上后面的数据元素会自动向前移,以至于在非空归并的时候下标越界,所以只需要将之前循环中的 i++ 去掉即可,线性表中的数据元素下标会随着之前元素的删除而自动前移。
    1062725-20171002020653927-1363513596.png

2.在将线性表形式的数据元素转换成 String 类型的数据元素时,抛出"ClassCastException"。为什么这里的强转是多余的?

  • 1062725-20171002023649005-1349172639.png

  • 解决办法:(查找API并搜索)
    1062725-20171002203759599-528700771.png

    查找之后才知道,在这里是因为将某一对象转换为某一子类时,如果该对象并非该子类的实例,就会抛出ClassCastException异常。就此例而言,前者n是String类型的,它的域要小于Object定义的get方法类,所以不能添加强转,只需要在后面添加空字符串就足够了,便可得到理想结果:
    1062725-20171002023658146-1278278671.png
    1062725-20171002023705193-420793857.png

3."ArrayList类"和"LinkedList类"中除了方法之外还有什么不同?

  • 解决办法:(搜索)
    两个类在方法种类上差别较小,但是在使用上有着较大的差异,先来看ArrayList的一些方法:
public boolean add(E e){
   ensureCapacity(size+1);//确保内部数组有足够的空间
   elementData[size++]=e;//将元素加入到数组的末尾,完成添加
   return true;      
} 
public void ensureCapacity(int minCapacity){
  modCount++;
  int oldCapacity=elementData.length;
  if(minCapacity>oldCapacity){    //如果数组容量不足,进行扩容
      Object[] oldData=elementData;
      int newCapacity=(oldCapacity*3)/2+1;  //扩容到原始容量的1.5倍
      if(newCapacitty<minCapacity)   //如果新容量小于最小需要的容量,则使用最小需要的容量大小
         newCapacity=minCapacity ;  //进行扩容的数组复制
         elementData=Arrays.copyof(elementData,newCapacity);
  }
}

所以对于ArrayList来说,在容量足够大的情况下,使用add方法的效率是很高的,而只有其对容量的需求超出数组的大小时,才会进行扩充容量。其中需要用到复制数组的System.arraycopy()方法。
而对于LinkedList来说,它的对应方法如下:

public boolean add(E e){
   addBefore(e,header);//将元素增加到header的前面
   return true;
}
private Entry<E> addBefore(E e,Entry<E> entry){
     Entry<E> newEntry = new Entry<E>(e,entry,entry.previous);
     newEntry.provious.next=newEntry;
     newEntry.next.previous=newEntry;
     size++;
     modCount++;
     return newEntry;
}

对于LinkeList来说,它使用了链表的结构和存储方式,使用不需要维护容量的大小。所以它比ArrayList具有一定的性能优势。但是,每次的元素的增加都需要新建一个Entry对象,并进行更多的赋值操作。在频繁的系统调用中,难免会对性能产生影响。
同理,在插入、删除等方法内部两个类有着类似的区别。总的来说,使用ArrayList虽然有时速度慢一些,但是有助于提高系统性能,而LinkedList虽然有的时候更方便,更省时,但是其性能较差。

4.在进行源码分析时,经常会看到 "modCount" 变量,这个变量在不同方法中的作用是什么?

  • 解决办法:(搜索)
    modCount主要用来记录ArrayList或者LinkedList类中结构性变化的次数。
    例如:在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1,还有之前举过例子的 trimToSize() 方法(ensureCapacity方法等):
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
        }
    }

【注】add()addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。
除此之外,modCount还有另外的规避风险等作用。

【返回目录】

分析总结:

  • 本周的实验主要是对于线性结构的应用,同时也再次熟悉了Junit的使用。

  • 本次实验的不足之处:
    (1)我对于链表的代码还未完全理解;
    (2)对于源码分析,我还不太明确其目的,但是我觉得可以从中学到一些以前异常出现的原因,并且可以让我们更深层次地添加注释,更规范地定义变量名称;
    (3)实验要求“Junit测试要尽量覆盖正常情况,异常情况,边界情况。”这一点我做得不好,可能是因为缺少经验,测试覆盖的全面性还欠考虑。

  • 这次实验完成地有些匆忙,但是非常充实,这种感觉是清醒的,也是作为一个学生需要的。

PSP(Personal Software Process)时间统计:

  • 步骤耗时百分比
    需求分析40min10%
    设计40min10%
    代码实现120min30%
    测试100min25%
    分析总结100min25%

【返回目录】

参考资料:

【返回目录】

转载于:https://www.cnblogs.com/super925/p/7617301.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值