1.双向循环链表
1)数组(ArrayList)与链表(LinkedList)的区别:
- 数组的优点: 直接通过下标来访问某个元素速度非常快。
- 数组的缺点: 插入或者删除某个元素比较慢(因为需要调整整个数组)。另外,需要有连续的地址空间。
- 链表的优点: 插入或者删除某个元素非常快(只需要改变指针的指向即可)。不需要连续的地址空间。
- 链表的缺点: 不能够通过下标直接访问某个元素(需要遍历)。占用的内存空间要比数组大(因为需要有保存地址的空间)。
结论: 如果事先大体知道数据个数,另外,查询比较多,而删除和插入操作比较少,用数组。反之用链表。
2)双向循环链表
每个节点包含有两部分内容,一部分是数据,另一部分是保存有指向前驱节点和后继节点地址的引用。另外,最后一个节点也保存有指向头节点的引用,头节点也保存有指向最后一个节点的引用。
jdk8.0之前的LinkedList是采用双向循环链表实现的,
jdk8.0之后,采用双向链表实现。
双向循环链表结构示意图
在尾部添加节点的过程
在指定下标处添加节点的过程
删除指定下标处的节点的过程
2.实现代码
/**
* 双向循环链表:
* 双向循环链表是这样一种数据结构:
* 每个节点包含有两部分内容,一部分是数据,另一部分是保存有指向前驱节点和后继节点地址的引用。
* 另外,最后一个节点也保存有指向头节点的引用,头节点也保存有指向最后一个节点的引用。
* JDK1.8之前的LinkedList是采用双向循环链表实现的,
* JDK1.8之后,采用双向链表实现
* @author dinghuang
*
* @param <E>
*/
public class LinkedList<E> {
//head保存链表头节点的引用
private Node head;
//保存节点(元素)的个数
private int size;
/**
* @return 返回链表中元素的个数
*/
public int size() {
return size;
}
/*
* Node用来描述一个节点,其中,
* E用来存放数据,prev存放前驱结点的引用,
* next存放后继节点的引用
*/
private class Node {
E data;
Node prev;
Node next;
public Node(E e) {
data = e;
}
}
/**
* 将元素添加到链表的末尾
* @param e 待添加的元素
* @return 添加成功,返回true
*/
public boolean add(E e) {
if (head == null) {
//链表为空,则当前新添加的节点为头节点。
head = new Node(e);
//如果只有一个元素,那么前驱节点和后继节点都指向自己
head.prev = head;
head.next = head;
size++;
return true;
}
/*
* 如果链表不为空,则先找到尾节点,
* 然后重新建立节点的引用关系
*/
//找到尾节点
Node last = head.prev;
//重新建立节点的引用关系
Node node = new Node(e);
last.next = node;
node.next = head;
head.prev = node;
node.prev = last;
size++;
return true;
}
/**
* 返回链表中各个元素的值,如果链表为空,返回"[]",
* 否则,多个元素使用","分隔。
*/
public String toString() {
if (head == null) {
return "[]";
}
StringBuilder builder = new StringBuilder();
//先将头节点的元素值添加进来
builder.append("[" + head.data);
//找到头节点的下一个节点
Node next = head.next;
/*
* 开始进行遍历,直到下一节点是头节点,则循环结束。
*/
while (next != head) {
builder.append(", " + next.data);
//找到下一个节点
next = next.next;
}
return builder.append("]").toString();
}
/**
* 返回指定下标的某个元素
* @param index 下标
* @return 对应下标的某个元素
*/
public E get(int index) {
if (index < 0 || index >= size) {
//当传入的下标不正确时抛出下标越界异常
throw new IndexOutOfBoundsException("下标越界");
}
Node node = getNode(index);
return node.data;
}
/*
* 返回指定下标对应的节点
*/
private Node getNode(int index) {
Node node = head;
//当下标小于链表长度的一半时,通过后继节点向后查找元素
if (index < size / 2) {
for (int i = 0; i < index; i++) {
node = node.next;
}
} else {
//当下表大于链表长度的一般时,通过前驱节点向前查找元素
for (int i = size; i > index; i--) {
node = node.prev;
}
}
return node;
}
/**
* 删除指定下标对应的元素
* @param index 下标
* @return 被删除的元素
*/
public E remove(int index) {
if (index < 0 || index >= size) {
//当传入的下标不正确时抛出下标越界异常
throw new IndexOutOfBoundsException("下标越界");
}
if (size == 1) {
E data = head.data;
head = null;
size--;
return data;
}
//找到被删除的节点
Node node = getNode(index);
//找到该节点的上一个节点和下一个节点
Node next = node.next;
Node prev = node.prev;
//上一个节点和下一个节点建立引用关系
next.prev = prev;
prev.next = next;
//如果要删除的是第一个节点(头节点),应该让第二个节点充当头节点
if (index == 0) {
head = node.next;
}
size--;
//返回被删除的元素
return node.data;
}
/**
* 将元素添加到指定下标处
* @param index 下标
* @param data 被添加的元素
*/
public void add(int index, E data) {
if (index < 0 || index > size) {
//当传入的下标不正确时抛出下标越界异常
throw new IndexOutOfBoundsException("下标越界");
}
if (index == size) {
//直接在尾部添加
add(data);
return;
}
Node node = new Node(data);
//找到对应下标处的节点
Node next = getNode(index);
//找到该节点的上一个节点
Node prev = next.prev;
//重新建立对应关系
next.prev = node;
node.prev = prev;
prev.next = node;
node.next = next;
//如果添加的下标是0, 则这个新节点为头节点
if (index == 0) {
head = node;
}
size++;
}
}