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变量总是表示添加节点的数目