【CS61b-sp21自学笔记】 Lec 5

35 阅读5分钟

新的链表定义

这节课的开始josh就讲述了一个新的关于链表定义的方式,他称之为unnaked的方法:

public class IntNode {
   public int item;
   public IntNode next;

   public IntNode(int i, IntNode n) {
         item = i;
         next = n;
   }
}
public class SLList {
   public IntNode first;

   public SLList(int x) {
      first = new IntNode(x, null);
   }

   public void addFirst(int x) {
       first = new IntNode(x, first);
   }

   public int getFirst() {
        return first.item;
   }
}

此处的SLList就是我们想要的所谓“unnaked”的链表定义方法

此时如果我们要创建一个新的链表只需new出来一个新的链表,然后根据我们定义的方法:addFrist来对这个链表进行延长

在SLList中 first是一个成员变量,他的类型是IntNode,即它是对IntNode的一个引用,相当于一个指针指向这个IntNode;

在我们使用new IntNode(x,first)的时候我们新创建了一个IntNode,它的first是x,next指向first就是之前的first所指向的IntNode的地址,这样最初的first中的IntNode就连接在了这个IntNode后面。

而我们令first = 这个new IntNode的含义是first存储这个IntNode的地址,这样下一次我们又可以将此IntNode连接到其他的IntNode后面了。

Private

在链表中如果有人故意捣乱()可能会发生这种错误:

L.first.next.next = L.first.next;

为了避免这种惨案的发生,我们可以将SLList中的first成员变量的属性改为Private,这样在这个类之外的任何地方我们都无法调用这个变量。类似于方法里的局部变量?

嵌套类

将一个类从属进另一个类中

为SLList写addLast和size方法

addList

关于addList方法,这里josh使用了迭代来书写,至于为什么,下面在写size的时候他就解释了

public void addLast(int x){
        IntNode p = first;
        while(p.next != null){
            p = p.next;
        }
        p.next = new IntNode(x,null);
}

size

这里josh让我们自己去书写一下size方法,我们在进行递归的时候会发现SLList并不能以我们学过的naked的链表一样进行自我递归,所以在这里他是用的方法就是书写一个Private修饰的辅助方法

private static int size(IntNode p){
    if(p == null){
        return 0;
    }else {
        return 1 + size(p.next);
    }
}
public int size(){
    return size(first);
}

补充写法

那么我们就可以尝试利用辅助方法来对addLast进行递归

private static void addList(IntNode p, int x){
    if(p.next == null){
        p.next = new IntNode(x,null);
    }else{
        addList(p);
    }
}
public void addList(){
    addList(first);
}

同样的,我们可以用迭代写size

public int size(){
    IntNode p = first;
    int size = 0;
    while(p.next != null){
        p = p.next;
        size += 1;
    }
    return size;
}

增加size的运行速度(增加类的属性)

我第一个想到的方法是直接用我们写的递归的方法来返回size,这样就不用进入递归,可以减少我们的步骤从而运行的更快

但是josh定义了一个新的成员变量 size 用来存储这个类的大小属性, 在需要的时候直接返回这个size的值,明显 这样更加高效

链表中的面向对象思想

这里josh也引入了他为什么推荐用SLList书写这个链表的原因: 尽管他看起来更加复杂,但是我们用这种方式去书写链表就引入了面向对象的编程方式,SLList不仅仅是一个链表的类,而是一个链表对象,它不仅能执行链表的一系列操作,而且有自己的属性 这些属性我们可以直接调用,这样在很多时候我们就不用一遍一遍的进入这个对象的内部去找到我们需要的属性,而是直接调出,节省了很多浪费时间空间的操作

null pointer exception 空指针异常!

我第一次接触到这个词是在project 0 中,前面几个方法在书写的时候总会有tilt == null的时候,这时如果我们没有考虑到这个空指针异常的情况下就去运行我们的代码,他就会进行报错,但是我当时还没有学debug,所以那一堆红色的字直接给我吓住了,之前语法错误的时候还知道去百度百度最上面的那句话看看错误在哪,但是因为project 0中使用了junit测试导致报错非常多就没有往这方面想。。在群里请教大佬才第一次接触到这个词TAT

其代表的含义是 : 如果你使用了一个本事就为null的对象,然后还用他去调用其中的方法。此时就会出现这个问题(因为你无法在null中找到任何的方法)

修复addLast

public void addLast(int x) {
  size += 1;
     if(first == null){
        first = new IntNode(0,null);
    }

  IntNode p = first;
  while (p.next != null) {
    p = p.next;
  }

  p.next = new IntNode(x, null);
}

哨兵节点

为了让上面addLast这个不优雅的写法看起来更加整洁,也为了我们可以舍去这个特殊情况,josh为我们引入了一个“十分可靠的伙伴”:我们的哨兵节点

哨兵节点就是在创建列表时我们用一个不存储数据(存储无意义的数据)的一个节点作为我们永远的第一个值,这样我们就不会有第一个节点是null这种特殊情况了,写法也是相当的神奇(虽然我现在还不能理解这个东西到底有什么意义。。。)就是排除了特殊情况,让我们有时不用去思考那么多特殊

要创建一个有哨兵节点的SLList我们应该这么做:

/**创建我们的哨兵节点*/

private IntNode sentinel;

public SLList(){
    sentinel = new IntNode(任意整数,null);
    size = 0;
}
然后在我们以后的代码中我们如果想获取list中的第一项,我们就得调用sentinel.next
这样我们的sentinel中的任意整数就没有任何意义了