我们在上期学习了链表的概念与结构。了解到单向链表和单向循环链表,我们在这里继续扩展一下,有单向则就就有双向。所以我们这期学习双向循环链表。乃什么是什么循环链表呢?
概念
双向循环链表: 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
与单向循环链表不同的是每个节点都有自己的直接前驱。
图示
此处我们使用LinkedList类来实现双向循环链表
我们用这个双向循环链表可以实现我们的线性表,栈以及队列。就是实现各个接口就可以。让我们以及去分析一下这个LinkedList吧.
解析
对于链表肯定有节点。所以我肯定得先定义一个节点类。定义有它的类似数据域的值,直接前驱,直接后继。然后在去定义三个构造方法。一个是无参构造。一个是传数据的构造函数,一个是传数据,直接前驱,直接后继三个参数的构造函数,以及数据的toString()方法。我们把这个节点类写完就可以定义自己的头指针,尾指针和有效元素个数。再定义构造函数。再去定义
代码实现
private class Node {
E data;
Node pre; //直接前驱
Node next; //直接后继
public Node() {
this(null, null, null);
}
public Node(E data) {
this(data, null, null);
}
public Node(E data, Node pre, Node next) {
this.data = data;
this.pre = pre;
this.next = next;
}
@Override
public String toString() {
return data.toString();
}
}
private Node head;
private Node tail;
private int size;
public LinkedList() {
head = null;
tail = null;
size = 0;
}
上面属性定义完后就是实现我们所有接口的方法。我们先实现线性表List接口的方法。
首先是添加方法add();我们默认就直接在表尾添加。若如果是在指定索引处添加的话,首先肯定是判断传入索引的值是否合格。添加元素肯定是添加的是节点,所以我们先创建一个节点。再就是判断有效元素为0的话,则就是把头指针指向该节点,尾指针也指向该节点尾指针的下一跳指向头节点,头节点的前驱指向尾节点。如果是在头节点处添加元素,则让该节点的前驱指向指向头节点的前驱该节点的下一跳指向头节点,让头节点的前驱指向该节点,再让头指针指向该节点,最后让尾指针的下一跳指向头节点。如果是在表尾添加元素,则是让该节点的下一跳指向尾节点的下一跳,尾指针的下一跳指向该节点,让该节点的前驱指向尾节点,尾指针在指向该节点,然后让头节点的前驱指向尾节点。其实在表头和表尾添加元素的操作刚好是对称的。如果是在中间添加元素的话,我们是需要遍历的。但由于是双向的,所以我们得考虑是从左开始遍历还是从右开始遍历,就拿有效元素的个数除2进行判断。老样子,创建节点,但这里是创建两个。让p节点先指向头节点,遍历到要添加位置的前一个结点,该结点的后继结点则是要添加位置的后一个结点q。让p的后继指向添加结点,让添加结点的前指向p,让q的前驱前驱结点指向添加结点。最后让添加结点的后继指向q。如果从尾结点开始遍历的话,就p从指向尾节点开始,开始遍历,是往左开始遍历的。其大致思想与从左边开始遍历是一样的。只要添加元素我们的有效元素个数就要进行加一。
图示
代码实现
@Override
public void add(int index, E element) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add index out of range");
}
Node n = new Node(element);
if (size == 0) {
head = n;
tail = n;
tail.next = head;
head.pre = tail;
} else if (index == 0) {
n.pre = head.pre;
n.next = head;
head.pre = n;
head = n;
tail.next = head;
} else if (index == size) {
n.next = tail.next;
tail.next = n;
n.pre = tail;
tail = n;
head.pre = tail;
} else {
Node p, q;
if (index <= size / 2) {
p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
q = p.next;
p.next = n;
n.pre = p;
q.pre = n;
n.next = q;
} else {
p = tail;
for (int i = size - 1; i > index; i--) {
p = p.pre;
}
q = p.pre;
q.next = n;
n.pre = q;
n.next = p;
p.pre = n;
}
}
size++;
}
添加方法写完后,乃接下来就是删除方法了。
删除元素肯定先要判断是否包含该元素,我们调用后面的方法是否能得出索引值,要是返回的不是-1,则就能找到要删除的元素。
删除指定索引处的元素方法首先也是判断索引值是否满足条件。先创建一个结点,如果是只有一个元素,那就是删除此元素。就让头指针指向null,让尾指针指向null;如果是在表头删除,该结点指向头节点的后继,然后在让头结点的后继指向null,再让该节点的前驱指向头节点的前驱,再把头节点的头节点