简单单链表的实现有一个缺点:尾端插入元素操作的效率很低。可以考虑给表对象增加一个尾结点引用域:

通过继承之前的单链表并修改增删操作,简单实现一个双引用域的单链表:
class SingleLinkedListWithTail extends SingleLinkedList {
Node tail;
private void checkRange(int i) {
if(i<0 || i>=size) {
throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
}
}
public void prepend(int elem) {
Node newNode = new Node(elem);
size++;
if(head==null) {
head = newNode;
tail = newNode;
return;
}
newNode.next = head;
head = newNode;
}
public void append(int elem) {
Node newNode = new Node(elem);
size++;
if(head==null) {
head = newNode;
tail = newNode;
return;
}
tail.next = newNode;
tail = newNode;
}
public void insert(int elem, int i) {
if(i==size) {
append(elem);
return;
}
super.insert(elem, i);
}
public void deleteFirst() {
super.deleteFirst();
if(head==null) {
tail = null;
}
}
public void deleteLast() {
delete(size-1);
}
public void delete(int i) {
checkRange(i);
if(i==0) {
deleteFirst();
return;
}
Node currNode = head;
while(i!=1) {
currNode = currNode.next;
i--;
}
if(i==size-1) {
tail = currNode;
}
currNode.next = currNode.next.next;
size--;
}
}
单链表的另一类常见变形是循环单链表,最后一个结点的引用域不为空(null),而是指向首结点,表对象只需要一个数据域rear来引用尾结点,从而高效实现首尾两端的插入/删除操作

简单实现一个循环单链表,不多赘述
class CircularSingleLinkedList {
Node rear;
int size;
public CircularSingleLinkedList() { }
public boolean isEmpty() {
return rear==null;
}
public void prepend(int elem) {
size++;
Node newNode = new Node(elem);
if(rear==null) {
rear = newNode;
rear.next = rear;
return;
}
newNode.next = rear.next;
rear.next = newNode;
}
public void append(int elem) {
prepend(elem);
rear = rear.next;
}
public int pop() {
if(rear==null) {
throw new IndexOutOfBoundsException("the list is empty");
}
size--;
Node head = rear.next;
if(head == rear) {
rear = null;
return head.elem;
}
rear.next = head.next;
return head.elem;
}
}
上述变形只能改善尾端插入的效率,如果希望两端的插入和删除都能高效完成,就必须修改结点的设计,增加另一方向的链接:

利用继承关系,简单实现双向链接表结点:
class DoubleLinkedNode extends Node{
DoubleLinkedNode prev;
DoubleLinkedNode next;
public DoubleLinkedNode(int elem) {
super(elem);
}
public DoubleLinkedNode(int elem, DoubleLinkedNode prev, DoubleLinkedNode next) {
super(elem);
this.prev = prev;
this.next = next;
}
}
为了支持两端的高效操作,可以使用双引用域

继承单链表类,简单实现一个双链表:
class DoubleLinkedList extends SingleLinkedList{
DoubleLinkedNode head;
DoubleLinkedNode tail;
private void insertFromBack(int elem, int i) {
if(i==0) {
append(elem);
return;
}
DoubleLinkedNode currNode = tail;
while(i!=0) {
currNode = currNode.prev;
i--;
}
DoubleLinkedNode newNode = new DoubleLinkedNode(elem, currNode, currNode.next);
currNode.next.prev = newNode;
currNode.next = newNode;
size++;
}
private void insertFromFront(int elem, int i) {
if(i==0) {
prepend(elem);
return;
}
DoubleLinkedNode currNode = head;
while(i!=0) {
currNode = currNode.next;
i--;
}
DoubleLinkedNode newNode = new DoubleLinkedNode(elem, currNode.prev, currNode);
currNode.prev.next = newNode;
currNode.prev = newNode;
size++;
}
private void deleteFromFront(int i) {
if(i==0) {
deleteFirst();
return;
}
DoubleLinkedNode currNode = head;
while(i!=0) {
currNode = currNode.next;
i--;
}
currNode.prev.next = currNode.next;
currNode.next.prev = currNode.prev;
size--;
}
private void deleteFromBack(int i) {
if(i==0) {
deleteLast();
return;
}
DoubleLinkedNode currNode = tail;
while(i!=0) {
currNode = currNode.prev;
i--;
}
currNode.prev.next = currNode.next;
currNode.next.prev = currNode.prev;
size--;
}
public void prepend(int elem) {
DoubleLinkedNode newNode = new DoubleLinkedNode(elem, null, head);
if(head==null) {
tail = newNode;
} else {
head.prev = newNode;
}
head = newNode;
size++;
}
public void append(int elem) {
DoubleLinkedNode newNode = new DoubleLinkedNode(elem, tail, null);
if(head==null) {
head = newNode;
} else {
tail.next = newNode;
}
tail = newNode;
size++;
}
public void insert(int elem, int i) {
if(i<0 || i>size) {
throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
}
int mid = size>>1;
if(i<mid) {
insertFromFront(elem, i);
return;
}
insertFromBack(elem, size-i);
}
public void deleteFirst() {
if(head == null) {
throw new IndexOutOfBoundsException("the list is empty");
}
size--;
head = head.next;
if(head!=null) {
head.prev = null;
return;
}
tail = null;
}
public void deleteLast() {
if(tail==null) {
throw new IndexOutOfBoundsException("the list is empty");
}
size--;
tail = tail.prev;
if(tail!=null) {
tail.next = tail;
return;
}
head = null;
}
public void delete(int i) {
if(i<0 || i>=size) {
throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
}
int mid = size>>1;
if(i<mid) {
deleteFromFront(i);
return;
}
deleteFromBack(size-i-1);
}
public int get(int i) {
if(i<0 || i>=size) {
throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
}
DoubleLinkedNode currNode = head;
while(i!=0) {
currNode = currNode.next;
i--;
}
return currNode.elem;
}
public int find(int elem) {
DoubleLinkedNode currNode = head;
int i = 0;
while(currNode!=null) {
if(currNode.elem==elem) {
return i;
}
i++;
currNode = currNode.next;
}
return -1;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
DoubleLinkedNode currNode = head;
while(currNode!=null) {
sb.append(currNode.elem);
currNode = currNode.next;
if(currNode!=null) {
sb.append(',');
}
}
sb.append(']');
return sb.toString();
}
}
双链表可以变形为循环双链表,表尾结点next指向首结点,表首结点prev指向尾结点,与循环单链表需要掌握尾结点tail不同的是,循环双链表无论是掌握首结点head还是尾结点tail,都可以高效实现首尾两端的插入/删除操作,不再赘述
本文介绍了如何通过在单链表基础上增加尾节点引用和双向链接,提升尾端插入效率,并扩展到循环链表,以便高效实现首尾两端的插入和删除操作。
1538





