双向链表,顾名思义,就是每个节点都有两个指针,分别指向当前节点的前一个节点及后一个节点。
下面给出具体实现:
import java.util.StringJoiner;
class LinkedList<Z> {
private static class Node<Z> {
Z data;
Node<Z> next;
Node<Z> prev;
}
private final Node<Z> head;
private final Node<Z> tail;
private int length = 0;
LinkedList() {
this.head = new Node<>();
this.tail = new Node<>();
head.next = tail;
head.prev = null;
tail.next = null;
tail.prev = head;
}
public int size() {
return length;
}
public boolean addFirst(Z data) {
return insert(0, data);
}
public boolean addLast(Z data) {
return insert(length, data);
}
public boolean removeFirst() {
return remove(0);
}
public boolean removeLast() {
return remove(length - 1);
}
public boolean remove(Z data) {
return remove(indexOf(data));
}
public void reverse() {
if (head.next == tail) {
return;
}
// _ 2 3 4 5 6 _
Node<Z> rh = new Node<>();
rh.next = tail;
Node<Z> cur = head.next;
Node<Z> cn;
while (cur != tail) {
cn = cur.next;
cur.next = rh.next;
rh.next.prev = cur;
cur.prev = rh;
rh.next = cur;
cur = cn;
}
rh.next.prev = head;
head.next = rh.next;
}
public Z get(int index) {
if (index < 0 || index >= length) {
return null;
}
int pos = -1;
Node<Z> h = head;
while (h.next != tail) {
h = h.next;
pos++;
if (index == pos) {
break;
}
}
return h.data;
}
public int indexOf(Z data) {
if (length == 0 || data == null) {
return -1;
}
boolean found = false;
int pos = -1;
Node<Z> h = head;
while (h.next != tail) {
h = h.next;
pos++;
if (data.equals(h.data)) {
found = true;
break;
}
}
return found ? pos : -1;
}
public int lastIndexOf(Z data) {
if (length == 0 || data == null) {
return -1;
}
int pos = length - 1;
Node<Z> t = tail.prev;
while (t != head) {
if (data.equals(t.data)) {
break;
}
t = t.prev;
pos--;
}
// if (pos == -1) { return -1; }
return pos;
}
public boolean insert(int index, Z data) {
if (index < 0 || index > length || data == null) {
return false;
}
// _ 4 [k] 7 _
// _ 3 5 4 1 [9] _
// _ 8 [x] _
// _ [x] 3 _
int pos = -1;
Node<Z> h = head;
while (h.next != tail) {
if (index - 1 == pos) {
break;
}
pos++;
h = h.next;
}
// now, let data's node as the next of h;
Node<Z> originNext = h.next;
Node<Z> item = new Node<>();
item.data = data;
item.next = originNext;
item.prev = h;
originNext.prev = item;
h.next = item;
length++;
return true;
}
public boolean remove(int index) {
if (length == 0 || index < 0 || index >= length) {
return false;
}
int pos = -1;
Node<Z> h = head;
while (h.next != tail) {
h = h.next;
pos++;
if (index == pos) {
break;
}
}
// now h is the target node, which is going to del;
h.prev.next = h.next;
h.next.prev = h.prev;
length--;
return true;
}
public void clear() {
while (size() > 0) {
removeFirst();
}
}
private String format(boolean hasIndex) {
StringBuilder sb = new StringBuilder("{ ");
int pos = -1;
Node<Z> h = head;
while (h.next != tail) {
h = h.next;
pos++;
if (pos == length) {
continue;
}
if (hasIndex) {
sb.append("[").append(pos).append("]-> ");
}
sb.append(h.data).append(", ");
}
int last = sb.lastIndexOf(", ");
if (last != -1) {
sb.delete(last, last + 2);
}
sb.append(" }");
return sb.toString().replaceAll(" {2}", "");
}
@Override
public String toString() {
return new StringJoiner(", ",
LinkedList.class.getSimpleName() + "[", "]")
.add("length=" + length)
.add("data=" + format(false))
.toString();
}
}
这里整体的实现思路是,定义头结点及尾结点作为标记,然后进行数据结点的增删改查。
然后说一下双向链表的反转:
public void reverse() {
if (head.next == tail) {
return;
}
// _ 2 3 4 5 6 _
Node<Z> rh = new Node<>();
rh.next = tail;
Node<Z> cur = head.next;
Node<Z> cn;
while (cur != tail) {
cn = cur.next;
cur.next = rh.next;
rh.next.prev = cur;
cur.prev = rh;
rh.next = cur;
cur = cn;
}
rh.next.prev = head;
head.next = rh.next;
}
这里的反转思路其实跟单链表的反转思路是一样的。
也是通过从头结点开始遍历这个链表,然后把取到的每个数据结点插入到临时头结点的后面;在遍历中不断去把取到的数据节点一直插入到同一个位置,就得到了一个由这个临时头结点链接的链表,然后再把当前链表的头结点关联到这个临时头结点的首个元素节点即可。
说一下反转的思路误区:
- 既然这个链表是双向的了,直接把头结点当成尾节点,尾节点当成头结点不就直接反转了吗?
从逻辑上看,这样确实就算是反转了。但是要注意,那么对于每个数据节点,之前的
prev
和next
的含义也正好相反了。那在执行遍历等操作的时候,就要反过来执行了。就是这样会影响到其他操作的内部实现。 为了避免对其他操作的影响,重新排列节点的位置会好一点。