单向链表与双向链表
单向链表结构:
双向链表结构:
在之前的文章中已经完成了对单向链表的实现:用Java语言实现单向链表(有/无虚拟头节点) ,那么实现双向链表只需要在单链表的基础上做一些更改即可。
改动位置 | 改动操作 |
private Node < E > last | 为LinkedList添加尾节点 |
private Node < E > prev | 为节点Node添加前驱节点 |
private Node node(int index) | 私有方法,获取索引为index的节点,增加从后向前查找节点 |
public void add(int index, E element) | 修改增加节点时前后节点的指向操作 |
public E remove(int index) | 修改删除节点时前后节点的指向操作 |
具体实现
last节点:
private Node<E> last;//尾节点
内部节点类Node :
//定义普通内部类Node
private class Node<E>{
private E element;
private Node<E> next;
private Node<E> prev;//前驱节点
public Node(Node<E> prev,E element, Node<E> next) {
this.prev=prev;
this.element = element;
this.next = next;
}
@Override
public String toString(){
//用于打印链表的所有连接关系
StringBuilder sb=new StringBuilder();
if(prev!=null)
sb.append(prev.element);
else
sb.append("null");
sb.append("_").append(element).append("_");
if(next!=null)
sb.append(next.element);
else
sb.append("null");
return sb.toString();
}
}
private Node node(int index),用于获取索引为index的节点:
/获取索引为index的节点
private Node<E> node(int index){
rangeCheck(index);//index的取值只能是0-size-1
//判断从头往后还是从后往前获取节点更快
if(index<(size>>1)){
Node<E> node=this.first;
for(int i=0;i<index;i++)
node=node.next;
return node;
}else{
Node<E> node=this.last;
for(int i=size-1;i>index;i--)
node=node.prev;
return node;
}
}
add方法:
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);//index合理范围0-size
if(index==size){//从尾部插入节点
Node<E> oldLast=last;//获取前节点
last=new Node<E>(oldLast,element,null);
if(oldLast==null)//若是插入的第一个节点
first=last;
else
oldLast.next=last;
}else{
Node<E> next=node(index);//获取插入后的后节点
Node<E> prev=next.prev;//获取前节点
Node<E> node=new Node<E>(prev,element,next);
next.prev=node;
if(prev==null)//前节点为空,相当于从头插
first=node;
else
prev.next=node;
}
size++;
}
remove方法:
@Override
public E remove(int index) {
rangeCheck(index);
//获取要删除的节点
Node<E> node=node(index);
//前驱节点
Node<E> prev=node.prev;
//后继节点
Node<E> next=node.next;
if(prev==null)//如果是第一个节点
first=next;//修改头节点位置
else
prev.next=next;
if(next==null)//如果是最后一个节点
last=prev;
else
next.prev=prev;
size--;
return node.element;
}
整体:
public class DoubleLinkedList<E> extends AbstractList<E>{
//定义普通内部类Node
private class Node<E>{
private E element;
private Node<E> next;
private Node<E> prev;
public Node(Node<E> prev,E element, Node<E> next) {
this.prev=prev;
this.element = element;
this.next = next;
}
@Override
public String toString(){
//用于打印链表的所有连接关系
StringBuilder sb=new StringBuilder();
if(prev!=null)
sb.append(prev.element);
else
sb.append("null");
sb.append("_").append(element).append("_");
if(next!=null)
sb.append(next.element);
else
sb.append("null");
return sb.toString();
}
}
private Node<E> first;//头节点
private Node<E> last;//尾节点
@Override
public int indexOf(E element) {
Node<E> node=first;
//如果元素为空,只需要考虑链表元素为空
if(element==null){
for(int i=0;i<size;i++){
if(node.element==null)return i;
node=node.next;
}
/*while(node!=null){
...
node=node.next;
}*/
}else{//元素不为空则调用equals方法判断
for(int i=0;i<size;i++) {
if (element.equals(node.element)) return i;
node=node.next;
}
}
//未找到则返回常量-1
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
//将头尾指针置空,所有节点对象将不被gc root对象引用
//gc root对象,如被栈指针引用的对象
last=null;
first=null;
size=0;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);//index合理范围0-size
if(index==size){//从尾部插入节点
Node<E> oldLast=last;//获取前节点
last=new Node<E>(oldLast,element,null);
if(oldLast==null)//若是插入的第一个节点
first=last;
else
oldLast.next=last;
}else{
Node<E> next=node(index);//获取插入后的后节点
Node<E> prev=next.prev;//获取前节点
Node<E> node=new Node<E>(prev,element,next);
next.prev=node;
if(prev==null)//前节点为空,相当于从头插
first=node;
else
prev.next=node;
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
//获取要删除的节点
Node<E> node=node(index);
//前驱节点
Node<E> prev=node.prev;
//后继节点
Node<E> next=node.next;
if(prev==null)
first=next;//修改头节点位置
else
prev.next=next;
if(next==null)
last=prev;
else
next.prev=prev;
size--;
return node.element;
}
@Override
public E get(int index) {
Node<E> node=node(index);
return node.element;
}
@Override
public E set(int index, E element) {
Node<E> node=node(index);
E oldElement=node.element;
node.element=element;
return oldElement;
}
//获取索引为index的节点
private Node<E> node(int index){
rangeCheck(index);//index的取值只能是0-size-1
//判断从头往后还是从后往前获取节点更快
if(index<(size>>1)){
Node<E> node=this.first;
for(int i=0;i<index;i++)
node=node.next;
return node;
}else{
Node<E> node=this.last;
for(int i=size-1;i>index;i--)
node=node.prev;
return node;
}
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder("[");
Node<E> node=first;
while(node!=null){
//判断是否是最后一个节点
if(node.next!=null)
sb.append(node).append(" ,");
else
sb.append(node);
node=node.next;
}
sb.append("]");
return sb.toString();
}
}
其中接口List和抽象类AbstractList见单链表:用Java语言实现单向链表(有/无虚拟头节点)
检验链表的正确性
此外在检验双向链表正确与否时,可以选择将所有节点的连接关系都打印出来,这样就可以很直观得判断链表结构是否有误。因此需要重写链表的toString方法与节点得toString方法。如下:
节点的toString方法:
@Override
public String toString(){
//用于打印链表的所有连接关系
StringBuilder sb=new StringBuilder();
if(prev!=null)
sb.append(prev.element);
else
sb.append("null");
//用'_'表示与前后的连接
sb.append("_").append(element).append("_");
if(next!=null)
sb.append(next.element);
else
sb.append("null");
return sb.toString();
}
链表的toString方法:
@Override
public String toString() {
StringBuilder sb=new StringBuilder("[");
Node<E> node=first;
while(node!=null){
//判断是否是最后一个节点
if(node.next!=null)
sb.append(node).append(" ,");
else
sb.append(node);
node=node.next;
}
sb.append("]");
return sb.toString();
}
经过测试可看到结果如下: