双向链表:
双向链表(double liked list):就是在单向链表的每个结点中,添加一个指向前驱结点的指针域。
双向链表的存储结构:
class DoubleNode <T>{
DoubleNode<T> prev; //前驱
T data; //数据域
DoubleNode<T> next; //后驱
public DoubleNode(DoubleNode<T> prev, T data, DoubleNode<T> next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
双向链表循环带头结点的空链表,如图:
非空的循环带头结点的双向链表,如图
双向链表的插入操作:
插入操作不复杂,不过顺序很重要,不要写反了。
假设存储元素 e 的结点为 s,要实现将结点 s 插入 p 和 p->next 之间,需要几个步骤?
s.prev = p; /*将p赋值给s的前驱,即 步骤1*/
s.next = p->next; /*将p->next赋值给s的后驱,即 步骤2*/
p->next.prev = s; /*将s赋值给p->next的前驱,即 步骤3*/
p.next = s; /*将s赋值给p的后驱,即 步骤4*/
注意:由于第2步和第3步都用到了p->next。如果第4步先执行,这会使得p->next提前变成了s。
顺序是先搞定 s 的前驱和后驱,在搞定 p->next的后驱,在搞定 p 的 后驱。
双向链表的删除操作:
删除就跟简单了,若要删除结点 p,需要两个步骤,如图:
p->prior.next = p->next; /*将p->next赋值给p->prior的后驱,即 步骤 1 */
p->next.prior = p->prior; /*将p->prior赋值给p->next的前驱,即 步骤 2 */
完整的实现:
/**
* 双向链表(double linked list)可循环的
* 是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
*/
public class DoubleLinkedList <T> {
private DoubleNode<T> first; //首结点
private DoubleNode<T> last; //尾结点
private int size; //有效元素个数
/**
* 添加元素,尾插法
* @param data 待插入的数据
* @return
*/
public boolean add(T data){
DoubleNode newNode = new DoubleNode(null, data, null);
if (first == null) {
newNode.prev = newNode;
newNode.next= newNode;
first = newNode;
}else{
DoubleNode l = last;
while (l.next != first) {
l = l.next;
}
newNode.prev = l; //将前一元素赋值给新结点的元素的前驱
newNode.next = first; //将first赋值给新结点的后驱
l.next = newNode; //将新元素赋值为前一元素的后驱
first.prev = newNode; //first的前驱是新添加的结点
}
last = newNode;
size ++;
return true;
}
/**
* 在指定位置插入元素,
* @param data 待插入的元素
* @param index 要插入的位置
* @return
*/
public boolean add(T data, int index){
if (index - 1 == size) {//插入在最后
return this.add(data);
}
rangeCheck(index);
DoubleNode newNode = new DoubleNode(null, data, null);
if (index == 1) {//插入在头元素
newNode.prev = last; //新元素的前驱是,last结点
newNode.next = first; //新元素的后驱是,first结点
last.next = newNode; //将last的后驱,跟新为新元素
first = newNode; //把新元素作为头结点
}else{
//获取前一个元素
DoubleNode pNode = this.node(index -1);
newNode.prev = pNode; //把前一元素作为新元素的前驱
newNode.next = pNode.next; //把后一元素作为新结点的后驱
pNode.next.prev = newNode; //把后一元素的前驱跟改为新元素
pNode.next = newNode; //把前一元素的后驱跟改为新元素
}
size++;
return true;
}
/**
* 删除指定位置的元素
* @param index
* @return
*/
public T delete(int index){
rangeCheck(index);
T oldValue;
if (index == 1) {
oldValue = first.data;
last.next = first.next;
first.prev = last;
first = first.next;
} else if (index == size) {
oldValue = last.data;
last.prev.next = first;
first.prev = last.prev;
last = last.prev;
}else{
DoubleNode<T> temp = this.node(index);
oldValue = temp.data;
temp.prev.next = temp.next;
temp.next.prev = temp.prev;
}
size --;
return oldValue;
}
/**
* 返回指定位置的元素
* @param index
* @return
*/
private DoubleNode<T> node(int index){
rangeCheck(index);
DoubleNode<T> result = null;
if (index < size >> 1) { //从前往后遍历
result = first;
for (int i = 1; i <= index -1; i++) {
result = result.next;
}
}else{ //从后往前遍历
result = last;
for (int i = 1; i <= size - index; i++) {
result = result.prev;
}
}
return result;
}
public int getSize() {
return size;
}
/**
* 检测index是否合法
* 是否不在0-链表长度范围内
* @param index
*/
private void rangeCheck(int index) {
if (index < 1 || index > size){
throw new BoxIndexOutOfBoundsException("[DoubleLinkedList] index:" + index + ", size:" + size);
}
}
@Override
public String toString() {
String str = "DoubleLinkedList = [ ";
DoubleNode temp = first;
while (temp != null) {
str += temp.data + " ";
if (temp.next == first){
break;
}
temp = temp.next;
}
str += "]";
return str;
}
/**
* 结点类
*/
class DoubleNode <T>{
DoubleNode<T> prev; //前驱
T data; //数据域
DoubleNode<T> next; //后驱
public DoubleNode(DoubleNode<T> prev, T data, DoubleNode<T> next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
总结:
双向链表在结构上比单向链表多一个指针域,所以在插入和删除操作上需要给外注意。每个结点都存储了两个指针域,使用空间来交换时间。