红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【CS61B】Lec4-SLList

SLList

SLList

我们将对前面创建的链表进行封装与优化

对上节课的IntList的修改:

1
2
3
4
5
6
7
8
9
10
11
12
package lec4;

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

    public IntNode(int i, IntNode n){
        item = i;
        next = n;

    }
}

重构为IntNode

接下来创建类SLList并进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package lec4;

// 数字列表
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;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        System.out.println(L.getFirst());
    }

}

但接下来有一个问题:有些用户可能会不通过给定的方法,而是直接操纵内部属性,这样可能会导致一些致命性的错误,如L.first = null,这时我们需要加强抽象屏障

我们需要修改一些实例的权限,防止可以直接被操纵,具体操作为将public改为private

private关键字使得其他类中的代码调用该类中的成员

隐藏实现细节:

  • 让用户理解更少
  • 修改方法时是安全的(因为没有人可以调用)

嵌套类

Java中可以将类进行嵌套,如我们可以将IntNode类放入SLList类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private 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;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        System.out.println(L.getFirst());
    }

}

当一个类无法独立存在(明显从属于另一个类)时,设为嵌套类是合适的。

对于第5行嵌套类是否设为静态:若嵌套类不使用外部类的任何内容,则可以设为static

增加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void addLast(int x){
    IntNode p = first;
    while(p.next!=null){
        p = p.next;
    }
    p.next = new IntNode(x, null);
}

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

其中,计算size时使用了递归,是通过在一个非递归类里调用了递归方法实现的:

  • 创建一个私有的递归方法,以那些裸递归对象作为参数
  • 在一个公共方法中直接调用该递归方法

但这种方式很低效,我们需要优化为\(O(1)\)复杂度

优化递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private IntNode first;
    private int size;

    public SLList(int x){
        first = new IntNode(x, null);
        size = 1;
    }
    public void addFirst(int x){
        size++;
        first = new IntNode(x, first);
    }

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

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

    public int size(){
        return size;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        L.addLast(20);
        System.out.println(L.getFirst());
        System.out.println(L.size());
    }
}

我们创建了一个size变量,存储数据来方便检索,在add的时候同时更新该值

创建空链表

修改为空链表后,addLast方法会出现问题,因为此时的first为null,无法访问first.next

解决方法1:

1
2
3
4
5
6
7
8
9
10
11
12
public void addLast(int x){
        size++;
        if (first == null){
            first = new IntNode(x, null);
            return ;
        }
        IntNode p = first;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new IntNode(x, null);
    }

这样需要一个特殊情况,使得代码更加复杂以及更难追踪

解决方法2:

在空链表初始化时加入头节点(sentinel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private IntNode sentinel;
    private int size;

    public SLList(int x){
        sentinel = new IntNode(0, null);
        sentinel.next = new IntNode(x, null);
        size = 1;
    }
    public SLList(){
        sentinel = new IntNode(0, null);
        size = 0;
    }
    public void addFirst(int x){
        size++;
        sentinel.next = new IntNode(x, sentinel.next);
    }

    public int getFirst(){
        return sentinel.next.item;
    }

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

    public int size(){
        return size;
    }

    public static void main(String[] args){
        SLList L = new SLList();
        L.addFirst(10);
        L.addFirst(5);
        L.addLast(20);
        System.out.println(L.getFirst());
        System.out.println(L.size());
    }

}

sentinal在这里的用处为占位,该节点永远不为空,且其值随意

不变式(Invariants)

不变式就是代码运行中保证成立的条件

存在头节点的SLList有以下不变式:

  • 头节点的引用总指向头节点
  • 第一个节点总存在sentinal.next
  • size变量总是表示添加节点的数目