设计双向链表需要两个类
1. LinkedList类,包含头结点first,尾节点last,节点个数size,记录头结点和尾节点有助于理解和简化编程。提供最基本的方法为:在指定索引位置插入和删除数据。
2. Node类,包含一个泛型对象的引用item,一个前引用prev,一个后引用next,这个可以设置为内部静态类。
在指定索引位置插入和删除数据,需要通过node(int index)方法返回index位置上的Node数据一遍操作。在返回index位置上的node数据时,要考虑index与size的关系,从而判别是从前往后查找还是从后往前查找。
插入和删除数据时需要分三种情况考虑:头结点插入删除,尾节点插入删除,中间节点插入删除。每种情况均写成私有的方法,由public方法根据实际情况调用。
在public的方法中,凡是牵扯的index的操作都要对index做检查,防止下标越界,这个由checkIndex(int index)方法完成。需要注意的是插入数据时的下标检查与其他时候的下标检查不一致,因为插入数据时index是可以等于size的。
以下代码为个人所写代码:
package wenpq.util;
/**
* @author wenpq
* wenpq的linkedList
*/
public class LinkedList<E>{
private int size = 0;
private Node<E> first; // 头结点与尾节点的使用,排除了特殊情形简化编码
private Node<E> last;
// ------------------------------------------------构造函数
public LinkedList() {}
// ------------------------------------------------内部使用函数
// 检查下标是否越界
private void checkIndex(int index) {
if (!(index >= 0 && index < size))
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
// 返回index位置上的node
Node<E> node(int index) {
// 右移1 和除2取整等价
if(index<(size>>1)){
// 从前向后 连接到指定节点
Node<E> x=first;
for(int i=0;i<index;i++)
x = x.next;
return x;
}else{
// 从后向前 连接到指定节点
Node<E> x=last;
for(int i=size-1;i>index;i--)
x = x.prev;
return x;
}
}
// 头节点前插入
void linkFirst(E e){
Node<E> f = first;
Node<E> newNode = new Node<>(null,e,f);
if (f==null){
last = newNode;
}else{
f.prev = newNode;
}
first = newNode;
size++;
}
// 尾节点后插入
void linkLast(E e){
Node<E> l = last;
Node<E> newNode = new Node<>(l,e,null);
if(l==null){
first = newNode;
}else{
l.next = newNode;
}
last = newNode;
size++;
}
// 中间前节点插入 插入后 element位置就是index 故要插入原来位置之前
void linkBefore(E e, Node<E> succ){
Node<E> newNode = new Node<>(succ.prev,e,succ);
succ.prev.next = newNode;
succ.prev = newNode;
size++;
}
// 头节点删除
void unlinkFirst(Node<E> f) {
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
}
// 尾节点删除
void unlinkLast(Node<E> l) {
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
}
// 中间节点删除
void unlink(Node<E> x) {
final Node<E> next = x.next;
final Node<E> prev = x.prev;
prev.next = next;
x.prev = null;
next.prev = prev;
x.next = null;
x.item = null;
size--;
}
// ------------------------------------------------可以继承自接口的一些操作(没有写完)
public int size() {
return size;
}
public boolean add(E e) {
linkLast(e);
return true;
}
public E get(int index) {
checkIndex(index);
return node(index).item;
}
public boolean set(int index, E element) {
checkIndex(index);
Node<E> x = node(index);
x.item = element;
return true;
}
public void add(int index, E element) {
// 注意在add的时候index是可以等于size的
if (!(index >= 0 && index <= size))
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
if (index == size){
linkLast(element);
}else{
if(index ==0 ){
linkFirst(element);
}else
linkBefore(element, node(index));
}
}
public void remove(int index) {
checkIndex(index);
if (index == size-1){
unlinkLast(node(index));
}else{
if(index ==0 ){
unlinkFirst(node(index));
}else
unlink(node(index));
}
}
/**
* 采用静态内部类 减少代码 较少动态创建开销
*/
private static class Node<E> {
// 一个E类型引用
// 一个前引用
// 一个后引用
Node<E> prev;
E item;
Node<E> next;
public Node(Node<E> prev, E item, Node<E> next) {
super();
this.prev = prev;
this.item = item;
this.next = next;
}
}
}
以下为测试代码:
public class Test {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<String>();
list.add("one");
list.add("two");
list.add("three");
list.add(0,"first");
list.add(list.size(),"end");
list.set(1, "new one");
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
list.remove(3);
list.remove(3);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
}
对比util的LinkedList的代码,标准库则做了更多考虑,在类申明时:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
之所以继承AbstractSequentialList ,是因为它实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些骨干性函数。降低了List接口的复杂度。这些接口都是随机访问List的,LinkedList是双向链表;既然它继承于AbstractSequentialList,就相当于已经实现了“get(int index)这些接口”。
java.io.Serializable用于声明该类的对象可以序列化,在socket通信和远程方法调用时会用到。实际上在源代码中头结点first,尾节点last,节点个数size这些都被transient关键字修饰,限定了不可能序列化的属性。
而在完成get,set,add方法时也不像上面代码那样分三种情况讨论,因为它调用的内部方法已经做了考虑。
对于remove方法,util源码返回了remove的对象,可能是因为考虑到同时要取数据燃耗删除数据的操作吧,目前还明白为什么要这么做,先留着,以后再来想。