双链表是单链表的进阶版,单链表是1-2-3-4 一个个排排坐链接,只管向后拉手,其主要思想是当前节点与下一节点的关系,那么双链表就多了一层关系,当前节点不仅和一下一点连起来,也要和上一节点串联起来。与前与后都要拉手。
如果对单链表不熟悉,建议先去看看单链表实现方式,链接如下
单链表实现代码以及解析
咱们还是先以定义节点开始,单链表是有当前节点和下一个节点的定义,那双向链表就需要有上一节点的定义,那就可以如下代码表现:
public class DoubleLinked<E> {
// 定义一个内部节点类
private static class Node<E> {
// 当前节点
E item;
// 上一节点
Node<E> prev;
// 下一节点
Node<E> next;
public Node(E item, Node<E> prev,Node<E> next) {
this.item = item;
this.prev = prev;
this.next = next;
}
}
}
1.添加节点
有了节点的定义就可以操作了,咱们先来添加操作
添加节点默认添加到尾部,所以调用linkLast方法,我们假如要添加1-2-3,我们要考虑添加了1以后,需要把2添加到1后,添加到3以后需要考虑2添加到3前等情况,咱们来逐个分析。
存储第一个节点时
1.我们需要实例化一个1的节点,因为不知道上一个节点值所以prev需要为空,也因为不知道下一个节点值所以next值需要为空,这样第一个值也就设置好了,那么设置好的值存储在哪里呢?才能把后面的值串连在一起,这时我们用全局变量first节点做这个事情
存储中间节点时
假设第一个节点存储好,那么添加第二个节点为2的值,操作又是什么?
当前节点为2的时候需要知道上一个节点值是1,下一个节点仍然不知道所以为空,所以构造方法就是 new Node(2,1,null),但是怎么知道上一个节点的值呢,这时候就需要添加个全局变量last,用来存储最后一个值,当第一个节点1存储进来时将1存储到last,第二个节点2过来的时候则直接用last也就是1就可, 用完后将last改为2,后面都以此种方式,就保证了last相比最新值时为上一个值。
// 首值
private Node first;
// 最后值
private Node last;
private int size;
public boolean add(E e) {
linkLast(e);
return true;
}
// 1-2-3
void linkLast(E e) {
// 上一个值,在第一次存储时为空
Node l = last;
// 新节点
Node newNode = new Node(e, last, null);
// 将新节点赋值给last
last = newNode;
// 第一次存储直接赋值给first即可
if (l == null) {
first = newNode;
} else {
// 通过引用类型的概念,此种方式则可让first串起来
// 第二次及以后的值需要将上一个值的next设置为当前新的节点
l.next = newNode;
}
size++;
}
// 打印链表数据
private void printData() {
Node f = null;
for (int i = 0; i < size; i++) {
if (i == 0) {
System.out.print(first.item);
f = first.next;
} else {
System.out.print("-" + f.item);
f = f.next;
}
}
System.out.println("");
}
public static void main(String[] args) {
DoubleLinked doubleLinked = new DoubleLinked();
doubleLinked.add("1");
doubleLinked.add("2");
doubleLinked.add("3");
doubleLinked.printData();
System.out.println("添加完毕-----------");
}
结果:
2.查询元素
根据小标索引查询链表数据,它这里用了将当前的链表长度除2,如果你要搜索的索引小于我计算过后的数字就证明你要找的位置比较靠前,所以就从头开始向后找(也就是说从first全局变量里循环取出next的数据),如果相反则证明你要找的数据在中后部分,需要从后向前找(也就是从last全局变量循环取出prev数据),还算蛮简单的哈。
// 根据索引获取链表元素
public E get(int index) {
return node(index).item;
}
Node<E> node(int index) {
// 右移1位相当于除以2
int i = size >> 1;
// 如果要找的索引不大于i,则从前往后找最快
if (index < i) {
Node find = first;
for (int i1 = 0; i1 < index; i1++) {
find = find.next;
}
return find;
} else {
// 如果要找的索引大于i,则从后往前找更快
Node find = last;
for (int i1 = size - 1; i1 > index; i1--) {
find = find.prev;
}
return find;
}
}
3.删除
删除节点,毋庸置疑我们要移动前后节点,挤出待删除节点。
待删除为中间节点的话:
比如1-2-3-4-5 我需要将2删除,索引就是1,需要将2的前一个节点也就是1找到,然后将1的下一个节点改为3,将2的下一个节点3找到,将3的上一个节点改为1,这样2的这个节点自然就删除了,这是删除中间的链表
待删除是头节点第一个的话:
假如1-2-3-4-5 删除的是1,索引就是0,此时1的节点上一个是空,这时需要找到当前节点的下一个节点直接赋值头节点即可。
待删除是最后一个节点的话:
假如1-2-3-4-5 删除的是5,索引就是4,此时下一个节点就是空,这时需要找到当前节点的上一个节点置为空即可。
// 1-2-3-4-5
public boolean remove(int index) {
// 得到待删除的节点
Node node = node(index);
// 得到删除的节点前一个节点
Node prev = node.prev;
// 得到删除的节点后一个节点
Node next = node.next;
// 当前节点前一个为空,则证明要删除第一个,直接将当前值下一个节点覆盖first即可
if (prev == null) {
first = next;
} else {
// 给上一个节点的next节点改成当前节点的下一个
prev.next = next;
if (next != null) {
// 给下一个节点的prev节点改成当前节点的上一个
next.prev = prev;
}
}
size--;
return true;
}
public static void main(String[] args) {
DoubleLinked doubleLinked = new DoubleLinked();
doubleLinked.add("1");
doubleLinked.add("2");
doubleLinked.add("3");
doubleLinked.add("4");
doubleLinked.add("5");
doubleLinked.printData();
System.out.println("添加完毕-----------");
System.out.println("查找元素2:" + doubleLinked.get(1));
System.out.println("查找元素1:" + doubleLinked.get(0));
System.out.println("查找元素3:" + doubleLinked.get(2));
System.out.println("删除2:" + doubleLinked.remove(1));
System.out.println("删除3:" + doubleLinked.remove(1));
System.out.println("删除1:" + doubleLinked.remove(0));
doubleLinked.printData();
}