链表之双向链表图例
- 图例是Java中对链表实现概念图。从图中可以知道,链表对基础单位是节点(Node),而节点(Node)又有三部分组成:首节点(First Node)、若干中间节点(Node)与尾节点(Last Node)。
- 节点是链表对基础单位,每个节点包含三部分前一个节点引用(Pre Node)、数据(Data)以及后一个节点引用(Next Node)。而首节点前一个节点引用(Pre Node)为空,同样尾节点后一个节点引用(Last Node)为空。java通过这种形式实现了自上向下或者自下向上遍历数据。
链表之双向链表添加、删除Node操作
- 当删除一个节点当时候,其后节点当前一节点引用(Pre Node)更新为该节点前一节点引用;而其前一节点当引用更新为该节点当后一节点引用。如图节点B删除后,尾节点当前一节点引用更新为NodeA,而NodeA节点更新其后一节点引用为Last Node。
- 而添加操作则正好是删除操作当反向处理。
- 当然链表添加、删除节点还要考虑添加在首节点之前与尾节点之后两种情况这里就加篇幅说明。
链表之代码实现(实现添加操作)
通过上述分析,我们可以实现双向链表当基本要素是节点。由此我们需要一个节点对象Node,而节点对象包含3个属性:前一节点引用(prev)、数据(data)、后一节点引用(next):
public class Node<E> {
public E data;
public Node<E> next;
public Node<E> prev;
}
通过对链表对分析与理解,只要告诉我们头节点(First Node)我们就可以自上而下遍历出所有节点,或者只要告诉我们尾节点(Last Node)我们就可以自下而上遍历出所有节点。
由此我们需要创建链表对象当时候,我们只需要知道头节点与尾节点即可。因此,链表中当成员也只要这两个节点。
/**
* 链表:这里继承AbstractList模版是为了使用其迭代器测试
*/
public class LQHLinkedList extends AbstractList<E>{
private int size = 0;// 节点个数
private Node<E> first;// 节点头部
private Node<E> last;// 节点尾部
/**
* 根据索引获取Node
*/
@Override
public E get(int index) {
if (index > size() || index < 0) {
throw new IndexOutOfBoundsException("index 超出范围");
}
Node<E> node = first;
for (int i = 0; i < size(); i++) {
if (index == i) {
return node.item;
}
node = node.next;
}
return null;
}
/**
* 根据索引index,添加Node
*/
@Override
public void add(int index, E element) {
if (index > size() || index < 0)
throw new IndexOutOfBoundsException("index 超出范围");
if (index == 0) {// 在头部添加节点
addFirst(element);
} else if (size() == index) {// 在尾部添加节点
addLast(element);
} else {
Node<E> indexNode = null;
if (index < (size() >> 1)) {// 正向查找:从头部开始查
indexNode = first;
for (int i = 0; i < index; i++) {
if (i == index) {
break;
}
indexNode = indexNode.next;
}
} else { // 反向查找:从尾部开始查
indexNode = last;
for (int i = size() - 1; i > index; i--) {
if (i == index) {
break;
}
indexNode = indexNode.prev;
}
}
// 当前节点、当前节点前一节点、当前节点后一节点引用替换
Node<E> insertNode = new Node<E>();
insertNode.item = element;
insertNode.next = indexNode;
insertNode.prev = indexNode.prev;
indexNode.prev.next = insertNode;
indexNode.prev = insertNode;
}
this.size++;
}
/**
* 头部添加节点操作
*/
private void addFirst(E element) {
Node<E> node = new Node<E>();
node.item = element;
node.next = first;
first = node;
}
/**
* 尾部添加节点操作
*/
private void addLast(E element) {
Node<E> newNode = new Node<E>();
newNode.item = element;
if (last == null) {
last = newNode;
last.prev = first;
first.next = last;
} else {
newNode.prev = this.last;
this.last.next = newNode;
this.last = newNode;
}
}
@Override
public int size() {
return this.size;
}
}
链表验证
public static void main(String[] args) {
LQHLinkedList<String> list = new LQHLinkedList<>();
list.add("第一个");
list.add("第二个");
list.add("第三个");
System.out.println(list.toString());// 输出(out):[第一个, 第二个, 第三个]
list.add(2, "我要当老三");
System.out.println(list);// 输出(out):[第一个, 第二个, 我要当老三, 第三个]
}
结束语
此篇博文主要是为了加深自己对链表对理解,以及对自己所学表述能力提升为目标写对。博文对与链表对细节描述并不详尽,旨在正确表达链表核心思想以及关键实现。后续还会抽时间分析与遍历链表息息相关对迭代器。