【CS61B】Lec4-SLList

  • ~5.11K 字
  1. 1. SLList
    1. 1.1. SLList
    2. 1.2. 嵌套类
    3. 1.3. 增加功能
    4. 1.4. 优化递归
    5. 1.5. 创建空链表
    6. 1.6. 不变式(Invariants)

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
27
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
38
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
18
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时使用了递归,是通过在一个非递归类里调用了递归方法实现的:

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

但这种方式很低效,我们需要优化为复杂度

优化递归

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
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
61
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变量总是表示添加节点的数目
赞助喵
非常感谢您的喜欢!
赞助喵