一、介绍
双端链表需要链表中的每个节点持有一个前置节点和一个后置节点的引用及本身的值,链表中包含一个头节点和一个尾节点,这两个节点仅作标识使用,本身只含有对后置节点或前置节点的引用,不含值。如图所示
图中pre代表前置节点,next代表后置节点,val则是本身的值。
插入操作分析
如图所示:
做插入操作时,需要把当前节点前置节点指向原节点的前置节点,当前节点的后置节点指向原节点,另外别忘了还要将原节点的前置节点指向当前节点,原节点的前置节点的后置节点指向当前节点(比较绕,多看看图,要做四步操作)
二、代码示例
双端链表中需要一个节点类及一个迭代器,本次两个类都会作为内部类存在。
节点类:
private class Node{
private Node pre;
private Node next;
private T val;
private Node(T val,Node pre,Node next) {
this.pre = pre;
this.next = next;
this.val = val;
}
}
迭代器:
private class MyIterator implements Iterator<T>{
//当前节点
private Node current = beginNode;
//当前节点索引
private int currentIdx = -1;
//迭代器总操作次数(后面介绍)
private int mod = modCount;
//是否可做删除操作(下面介绍)
private boolean canRemove = false;
@Override
public boolean hasNext() {
return current.next!=endNode;
}
@Override
public T next() {
canRemove = true;
if(!hasNext()||mod!=modCount) {
throw new RuntimeException();
}
current = current.next;
return current.val;
}
public void remove() {
if(!canRemove) {
throw new RuntimeException();
}
//该类是内部类,MyLinkedList是它的外部类(内部类要用这种方式调用外部类的方法)
MyLinkedList.this.remove(currentIdx);
canRemove = false;
mod++;
}
迭代器中有两个标志,一个是是否可删除,一个是总操作次数mod(他的初始值等于创建迭代器时的链表的总操作数)。
删除标志是为了判断当前是否有可删除值,如果当前是头节点或者尾节点或者已经删除过一次,则都会抛出异常。
至于操作次数,是为了防止在迭代过程中用了迭代器以外的方式删除了链表元素或新增链表元素,这会导致数据异常,报错了还可以及时发现,如果没有报错可能会导致想删除当前元素却删除了前一个或后一个元素,错误难以排查。
链表:
public class MyLinkedList<T> {
private Node beginNode;
private Node endNode;
//链表长度,不含头节点和尾节点
private int size = 0;
//对链表的add、remove次数
private int modCount = 0;
...此处是属性部分
私有方法:
这里使用私有方法是因为这两个方法返回值是Node,外部并不能获取到,而我们也应该刻意隐藏Node的实现,使用户只需要操作数据,而不关心内部实现。
//获取指定节点
private Node getNode(int idx,int low,int upper) {
if(idx<low||idx>upper) {
throw new RuntimeException();
}
//索引位于节点前半段,从头节点开始遍历
if(idx<size()/2) {
Node n = beginNode.next;
for(int i=0;i<idx;i++) {
n = n.next;
}
return n;
}else {//索引位于后半段,从尾节点开始遍历
Node n = endNode;
for(int i=size();i>idx;i--) {
n = n.pre;
}
return n;
}
}
//在指定节点前插入节点
private Node addBefore(T val,Node node) {
Node n = new Node(val, null, null);
//n的前置节点指向原节点的前置节点(这里就是前文提到的四步操作)
n.pre = node.pre;
//n的后置节点指向原节点
n.next = node;
//将原节点的前置节点的后置节点指向n
node.pre.next = n;
//原节点的前置节点指向n
node.pre = n;
size++;
modCount++;
return n;
}
公有方法:
//向指定位置添加节点
public T add(int idx,T val) {
Node node = getNode(idx,0,size());
return addBefore(val, node).val;
}
//移除指定位置节点
public T remove(int idx) {
if(idx<0||idx>size()-1) {
throw new RuntimeException();
}
Node node = getNode(idx,0,size()-1);
//前置节点的后置节点指向原节点的后置节点
node.pre.next = node.next;
//后置节点的前置节点指向原节点的前置节点
node.next.pre = node.pre;
size--;
modCount++;
return node.val;
}
//获取指定位置的值
public T get(int idx) {
return getNode(idx,0,size()-1).val;
}
//设置指定位置值
public void set(int idx,T val) {
getNode(idx,0,size()-1).val = val;
}
//返回元素个数
public int size() {
return size;
}
//返回迭代器
public Iterator<T> iterator(){
return new MyIterator();
}
//构造方法
public MyLinkedList() {
beginNode = new Node(null,null,endNode);
endNode = new Node(null,beginNode,null);
}
//节点类
private class Node{
...
}
//迭代器
private class MyIterator implements Iterator<T>{
.....
}
}
到此就是全部代码啦!