Lec6
DLList 双重链表
可以通过增加last变量指向尾部节点来加快addLast的速度 使其无需遍历整个列表就可以增加尾部节点
public class SLList {
private IntNode sentinel;
private IntNode last;
private int size;
public void addLast(int x) {
last.next = new IntNode(x, null);
last = last.next;
size += 1;
}
...
}
但是如果想删除尾部节点removeLast 就没那么容易了 需要找到倒第二个节点指向null 由此类推还要找到倒第三个节点
解决办法就是在
IntNode前面加上一个前指针 也称为双重链表
public class IntNode {
public IntNode prev;
public int item;
public IntNode next;
}
然后再添加一个哨兵节点
或者使前后指针指向同一个节点
泛型
如果想在列表中保存其他类型的数据 比如DLList d2 = new DLList("hello");
就要使用泛型 在类声明时类的名称后添加一个<引用类型> 这样就允许添加其他任何引用类型的数据
public class DLList<引用类型> {
private IntNode sentinel;
private int size;
public class IntNode {
public IntNode prev;
public 引用类型 item;
public IntNode next;
...
}
...
}
然后实例化 比如
DLList<String> d2 = new DLList<>("hello");
d2.addLast("world");
需要注意的是 泛型仅适用于引用数据类型 如果是基本数据类型 则在<>内使用Integer Double Character Boolean Long Short Byte Float代替基本数据类型 比如
DLList<Integer> d1 = new DLList<>(5);
d1.insertFront(10);
Arrays 数组
创建数组的三种方法
x = new int[3];创建一个长度为3的数组 默认填充0y = new int[]{1, 2, 3, 4, 5};创建包含五个特定元素的长度为5的数组int[] z = {9, 10, 11, 12, 13};创建的同时声明包含五个特定元素的长度为5的数组
复制数组arraycopy
System.arraycopy(b, 0, x, 3, 2);
- 要用作源的数组
- 在源数组中从何处开始
- 要用作目标的数组
- 在目标阵列中从何处开始
- 要复制的项目数
二维数组
int[][] matrix;
matrix = new int[4][];
matrix = new int[4][4];
Lec7
AList 数组
使用数组构建一个支持addLast getLast get size的AList类
- 要插入的下一项的位置始终为
addLast[size] - AList中的项数始终为
size - 列表中最后一项的位置始终为
addLast[size - 1]
public class AList {
private int[] items;
private int size;
/** Creates an empty list. */
public AList() {
items = new int[100];
size = 0;
}
//调整数组的大小为任意容量
//在多次调整数组大小的情况下 可能要花费很多时间以及造成内存浪费
private void resize(int capacity){
int[] a = new int[capacity];
System.arraycopy(items, 0, a, 0, size);
items = a;
}
/** Inserts X into the back of the list. */
public void addLast(int x) {
if(size == items.length){//如果数组越界就要修改数组大小
resize(size + 1);
}
items[size] = x;
size +=1;
}
/** Returns the item from the back of the list. */
public int getLast() {
return items[size - 1];
}
/** Gets the ith item in the list (0 is the front). */
public int get(int i) {
return items[i];
}
/** Returns the number of items in the list. */
public int size() {
return size;
}
/** Deletes item from back of the list and
* returns deleted item. */
public int removeLast() {
int x = getLast();
items[size - 1] = 0;//不是必要的
size -= 1;
return x;
}
}
数组中类似泛型的操作
Glorp[] items = (Glorp []) new Object[8];
Lec8
使SLList和AList具有相同功能的方法
重载
方法同名但参数列表不同时(返回列表无所谓) java会根据参数类型判断调用哪个方法 但也会造成代码臃肿 不便修改的问题
public static String longest(SLList<String> list)
public static String longest(AList<String> list)
接口 实现 重写
- 接口
interface一种抽象类 只包含常量和方法的定义 具体方法实现要加default关键字
public class AList<Item> {...} - 实现
implements拥有接口中所指定的方法
public class AList<Item> implements List61B<Item>{...} - 重写
override在子类中把父类的方法重写一遍(方法名参数列表返回类型都相同)@Override
public interface List61B<Item> {//创建List61B
public void addFirst(Item x);//声明抽象方法
public void add Last(Item y);
public Item getFirst();
public Item getLast();
public Item removeLast();
public Item get(int i);
public void insert(Item x, int position);
public int size();
default public void print() {//接口中具体方法要加default关键字
for (int i = 0; i < size(); i += 1) {
System.out.print(get(i) + " ");
}
System.out.println();
}
}
public class SSList<Item> implements List61B<Item>{//SSList继承List61B
@Override//重写print方法
public void print() {
for (Node p = sentinel.next; p != null; p = p.next) {
System.out.print(p.item + " ");
}
}
}
下面的代码运行时 将创建SLList 并且其地址存储在someList变量中 然后将字符串“elk”插入到 addFirst引用的SLList中
public static void main(String[] args) {
List61B<String> someList = new SLList<String>();
someList.addFirst("elk");
}
若同一类中有同名方法 Java会检参数的静态类型并调用相符的方法 但不适用于重载方法
public static void peek(List61B<String> list) {
System.out.println(list.getLast());
}
public static void peek(SLList<String> list) {
System.out.println(list.getFirst());
}
SLList<String> SP = new SLList<String>();
List61B<String> LP = SP;//静态类型根据当前引用的对象的类型而变化
peek(SP);//调用第二个peek方法
peek(LP);//调用第一个peek方法
Lab3
计时测试
在这部分中 我们要进进行三部分的计时测试 包括
- 使用加法调整策略的AList的addLast方法
- 使用乘法调整策略的AList的addLast方法
- SLList的getLast方法
加法AList
我们的目标是为加法型AList的addLast方法添加不同规格的数据并进行计时 打印一个表格 如下图
- 第一栏
N给出数据结构的大小(它包含多少个元素) - 第二栏
time (s)给出完成所有操作所需的时间 - 第三栏
ops给出调用addLast的次数(同N 调用多少次就有多少个元素) - 第四栏
microsec/op给出完成每次调用平均花费的微秒
tips
- 不同规格的数据添加新元素时所花费的时间并不相同 规格越大所需时间越多
- 调用
printTimingTable(AList<Integer> Ns, AList<Double> times, AList<Integer> opCounts)方法可打印表格 分别代表前三列 最后一列自动计算得出 - 使用
Stopwatch类计算时间
Stopwatch sw = new Stopwatch(); // 开始计时
timeInSeconds = sw.elapsedTime(); // 计时结束
实现代码
public static void timeAListConstruction() {
// TODO: YOUR CODE HERE
//创建三个数组分别储存数据结构的大小 所需时间 调用次数
AList<Integer> Ns = new AList<>();
AList<Double> times = new AList<>();
AList<Integer> opCounts = new AList<>();
for(int i = 1000; i <=128000; i *=2) {
//分别创建大小为1000-128000的AList
Ns.addLast(i);
opCounts.addLast(i);
AList L = new AList<>();
Stopwatch sw = new Stopwatch();//开始计时
for(int j = 1; j <= i; j++){//调用n次addLast
L.addLast(i);
}
Double time = sw.elapsedTime();//停止计时
times.addLast(time);
}
printTimingTable(Ns, times, opCounts);//打印表格
}
}
乘法AList
调整addLast策略
public void addLast(Item x) {
if (size == items.length) {
resize((int) (size * 1.01));
}
items[size] = x;
size = size + 1;
}
调用方法后我们会发现 非常快!
SLList的getLast方法
我们想知道getLast方法的运行时间如何依赖于数据结构的大小
- 创建一个
SLList - 添加N个项目到
SLList - 启动计时器。
- 执行M次
getLast操作 - 停止计时
实现代码
public static void main(String[] args) {
timeGetLast();
}
public static void timeGetLast() {
// TODO: YOUR CODE HERE
//创建三个数组分别储存数据结构的大小 所需时间 调用次数
AList<Integer> Ns = new AList<>();
AList<Double> times = new AList<>();
AList<Integer> opCounts = new AList<>();
int M = 10000;
for(int i = 1000; i <= 128000; i *=2){
//分别创建大小为1000-128000的SLList
Ns.addLast(i);
opCounts.addLast(M);
SLList L = new SLList<>();
//添加N个项目到L
for(int j = 1; j <= i; j++){
L.addLast(j);
}
Stopwatch sw = new Stopwatch();//开始计时
for(int j = 1; j <= M; j++){//执行M次getLast操作
int k = (int) L.getLast();
}
Double time = sw.elapsedTime();//结束计时
times.addLast(time);
}
printTimingTable(Ns, times, opCounts);//打印表格
}
}
随机比较测试
创建一个简单的比较测试
其中AListNoResizing类是正确的 BuggyAList类是可能有问题的
public void testThreeAddThreeRemove(){
AListNoResizing<Integer> correct = new AListNoResizing<>();
BuggyAList<Integer> buggy = new BuggyAList<>();
correct.addLast(4);
correct.addLast(5);
correct.addLast(6);
buggy.addLast(4);
buggy.addLast(5);
buggy.addLast(6);
assertEquals(correct.size(), buggy.size());
assertEquals(correct.removeLast(), buggy.removeLast());
assertEquals(correct.removeLast(), buggy.removeLast());
assertEquals(correct.removeLast(), buggy.removeLast());
}
条件断点
调试时右键单击断点 可以设置条件
点击resume按钮 可以每次都执行到断点的位置
复杂随机测试
在随机测试里添加getLast和removeLast操作
tips
- 使
StdRandom.uniform选择0到3之间的数字 分别对应addLast size removeLast getLast方法 - size为0时应跳过
getLast和removelast方法 - size为1000时应跳过
addLast方法 因为L无法调节数组大小 超过size继续添加会越界
public void randomizedTest() {
AListNoResizing<Integer> L = new AListNoResizing<>();
BuggyAList<Integer> B = new BuggyAList<>();
int N = 5000;
for (int i = 0; i < N; i += 1) {
int operationNumber = StdRandom.uniform(0, 4);
if (operationNumber == 0 && L.size() < 1000) {
// addLast
int randVal = StdRandom.uniform(0, 100);
L.addLast(randVal);
B.addLast(randVal);
System.out.println("addLast(" + randVal + ")");
} else if (operationNumber == 1) {
// size
int size = L.size();
int size1 = B.size();
System.out.println("size: " + size);
assertEquals(size, size1);
} else if (operationNumber == 2) {
// removeLast
if (L.size() == 0) {
continue;
}
int RemoveLast = L.getLast();
int RemoveLast1= B.getLast();
System.out.println("removeLast: " + RemoveLast);
assertEquals(RemoveLast, RemoveLast1);
} else if (operationNumber == 3) {
// getLast
if (L.size() == 0) {
continue;
}
int Last = L.getLast();
int Last1 = B.getLast();
System.out.println("getLast: " + Last);
assertEquals(Last, Last1);
}
}
}
}
执行测试 可能成功也可能失败 增大N的值 可发现几乎每次都是失败(随机测试可能无法检测到错误
执行断点
工作遇到异常时停止代码并可视化异常的情况
- 单击运行--查看断点
- 勾选任何异常--条件--输入具体异常(从JUnit给出的异常问题中复制粘贴
- 进行调试 找出异常原因并修改代码
用断点找出错误并修复
运行测试后 发现如下的错误信息
java.lang.ArrayIndexOutOfBoundsException: Index 1000 out of bounds for length 1000
执行断点操作
B经过removeLast操作后size应变为14 但resize中却变成了3 发现是removeLast方法中调用的resize方法出现了问题 新建的数组太小 无法将原来的数组复制过去 原来是缩小数组长度时 把items.length/4写成了size/4
public Item removeLast() {
//为提高空间利用率 当size小于数组长度的四分之一时 将数组长度缩小为原来的四分之一
if ((size < items.length / 4) && (size > 4)) {
//resize(size / 4);
resize(items.length / 4);
}
Item x = getLast();
items[size - 1] = null;
size = size - 1;
return x;
}