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