DoubleLinkedList
双链表基本概念
- 双链表指的是节点有next 和prev 两个指针变量,前者指向前驱节点, 后者指向后继节点
- 单链表删除与插入一个节点需要找到这个节点的前驱节点, 双链表删除一个节点和插入一个节点只需要找到相应的这个节点即可
- 双链表其他操作与单链表一致, 事实上, 双链表是单链表的一种, 可以直接复用单链表的任何代码, 但是删除与插入代码使用prev会更简单
- 双链表如果再加上一个附加尾节点 , 功能将更好
对于双链表而言, 如果加上尾节点, 那么尾插和尾删都会变为O(1), 其他基本操作性能基本不变
实现代码略, 因为后面会实现双向循环链表时
java LinkedList 用的是没有哨兵节点的带head 和 end的双向链表
LoopLinkedList
循环单链表基本概念
- 循环单链表有头节点和尾节点, head 和 end
- 尾节点的next 始终指向头节点
- 通过链表中任意一个节点都可以遍历所有的节点
- 遍历终止条件是 i.next == end (有附加尾节点)或 i.next == head (有数据尾节点)
对于循环单链表而言, 其性能与单链表相同。这里只针对基本操作的性能,实际上, 会有许多其他操作, 使用循环单链表的性能会变得很好
LoopDoubleLinkedList
循环双链表基本概念
- 循环双链表相当于循环单链表加上双链表
- 节点具有next 和prev, 链表有head 和end , 并且 end.next = head, head.prev = end
循环双链表的性能兼具上面二者
实现循环双链表
数据成员与构造函数
package com.weinijuan;
public class LoopDoubleLinkedList<T> implements List<T>
{
private class Node
{
private T data;
private Node next;
private Node prev;
public Node(T data, Node next, Node prev)
{
this.data = data;
this.next = next;
this.prev = prev;
}
}
private Node head;
private Node end;
private int size;
public LoopDoubleLinkedList()
{
head = new Node(null, null, null);
end = new Node(null, head, head);
head.next = end;
head.prev = end;
}
}
增加add方法
文字描述:
- 找到对应位置的节点 (target), 在前半段就用next , 后半段就用prev
- new 一个节点, 它的next 是 target, 它的prev 是 target.prev
- 更改target. next 和 target.prev的指向
- 特别注意, add 的index的取值范围是[0, size], 0是最前面, size 是最后面。这一点不同于delete/set/get
- 根据不同情况选择是向后插入还是向前插入元素
@Override
public void add(int index, T data)
{
if (index < 0 || index > size)
{
throw new IndexOutOfBoundsException();
}
Node target ;
if (index <= size/2)
{
target = head.next;
for (int i = 0; i < index; i++)
{
target = target.next;
}
target.prev.next = new Node(data, target, target.prev);
target.prev = target.prev.next;
}
else
{
target = end.prev;
for (int i = 0; i < size - index; i++)
{
target = target.prev;
}
target.next = new Node(data, target.next, target);
target.next.next.prev = target.next;
}
size++;
}
@Override
public void addFirst(T data)
{
add(0, data);
}
@Override
public void addLast(T data)
{
add(size, data);
}
删除操作
上图中data2对应的节点的next 和prev 可以不用修改。
当target的前驱节点 指向 target的后继节点 , target节点的后继节点指向target的前驱节点, 则没人指向target节点, 那么它将被垃圾回收器回收
@Override
public T delete(int index)
{
if (index < 0 || index >= size)
{
throw new IndexOutOfBoundsException();
}
Node target ;
if (index < size/2)
{
target = head.next;
for (int i = 0; i < index; i++)
{
target = target.next;
}
}
else
{
target = end.prev;
for (int i = 0; i < size - index - 1; i++)
{
target = target.prev;
}
}
T retData = target.data;
target.prev.next = target.next;
target.next.prev = target.prev;
size--;
return retData;
}
set/get
核心都是找到对应位置的节点
@Override
public void set(int index, T data)
{
if (index < 0 || index >= size)
{
throw new IndexOutOfBoundsException();
}
Node target ;
if (index < size/2)
{
target = head.next;
for (int i = 0; i < index; i++)
{
target = target.next;
}
}
else
{
target = end.prev;
for (int i = 0; i < size - index - 1; i++)
{
target = target.prev;
}
}
target.data = data;
}
@Override
public T get(int index)
{
if (index < 0 || index >= size)
{
throw new IndexOutOfBoundsException();
}
Node target ;
if (index < size/2)
{
target = head.next;
for (int i = 0; i < index; i++)
{
target = target.next;
}
}
else
{
target = end.prev;
for (int i = 0; i < size - index - 1; i++)
{
target = target.prev;
}
}
return target.data;
}
size/traverse/getIndex
和单链表一摸一样
测试
使用单链表的测试文件即可
性能
性能总结
(双表示双链表性质, 循环为循环链表性质)
操作 | 时间复杂度 | 备注 |
---|---|---|
addFirst | O (1) | |
addLast | O(1) | |
add | O(N) | 单链表一半(双) |
set | O(N) | 单链表一半(双) |
get | O(N) | 单链表一半(双) |
getIndex | O(N) | 单链表一半(双) |
traverse | O(N) | 任意节点都可遍历(循环) |
size | O(1) |