前言
链表是一种线性表,常见的线性表还有栈、队列,本篇主要是分析双向链表的数据结构,以及如何使用java自己实现一个双向链表。在分析双向链表前,有必要先看看单向链表的数据结构图,方便对比双向链表数据结构。
1、单向链表
百度百科:单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点。
删除操作后:
2、双向链表
百度百科:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
删除操作后:
3、手写实现
public class CoolList<T> {
//节点个数
public int size;
//头节点
private SummerNode<T> first;
//尾节点
private SummerNode<T> last;
//定义节点类型
private static class SummerNode<T> {
//节点元素
T t;
//前一个节点地址
SummerNode<T> pre;
//后一个节点地址
SummerNode<T> next;
SummerNode(SummerNode<T> pre,T t,SummerNode<T> next){
this.t = t;
this.pre = pre;
this.next = next;
}
}
/**
* 更新指定位置的元素
* @param index 位置
* @param t 元素
* @return 返回旧值
*/
public T replace(int index,T t){
//检查节点是否存在
checkExistNode(index);
//获取指定位置节点
SummerNode<T> exist = node(index);
T oldValue = exist.t;
//替换新值
exist.t = t;
return oldValue;
}
/**
* 在指定位置之前插入元素
* @param index
* @param t
* @return 成功返回true
*/
public boolean beforeIndexInsert(int index,T t){
//检查节点是否存在
checkExistNode(index);
//获取节点
SummerNode<T> node = node(index);
//前一个节点
SummerNode<T> pre = node.pre;
//插入的节点在pre节点和node节点之间
final SummerNode<T> newNode = new SummerNode<>(pre, t, node);
node.pre = newNode;
if(pre == null){
//如果插入的位置是第一个节点前
first = newNode;
} else {
pre.next = newNode;
}
//元素+1
size++;
return true;
}
//在末尾插入
public boolean lastInsert(T t){
final SummerNode<T> l = last;
final SummerNode<T> newNode = new SummerNode<>(l, t, null);
last = newNode;
if(l == null){
//如果是首次插入元素,设置为第一个节点
first = newNode;
} else {
l.next = newNode;
}
size++;
return true;
}
private void checkExistNode(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size+";");
}
}
/**
* 获取指定位置节点元素
* @param index
* @return
*/
public T get(int index){
checkExistNode(index);
return node(index).t;
}
/**
* 获取指定位置的节点
* @param index
* @return
*/
SummerNode<T> node(int index){
//二分法查找,小于size/2,则顺序查找
if(index < (size >> 1)){
SummerNode<T> x = first;
for(int i=0; i<index; i++){
x = x.next;
}
return x;
} else {
SummerNode<T> x = last;
//倒序查找
for (int i = size-1; i > index; i--){
x = x.pre;
}
return x;
}
}
/**
* 移除指定位置的节点
* @param index
* @return
*/
public T remove(int index){
//查找节点是否存在
checkExistNode(index);
//删除节点
return unlink(node(index));
}
/**
* 移除指定元素的节点
* @param t
* @return
*/
public boolean remove(T t){
//为空
if(t == null){
for(SummerNode<T> x=first; x!=null; x=x.next){
//查出为空的元素,再删除
if(x.t == null){
unlink(x);
return true;
}
}
} else {
//不为空,查出不为空的元素,然后删除
for(SummerNode<T> x=first; x!=null; x=x.next){
if(t.equals(x.t)){
unlink(x);
return true;
}
}
}
return false;
}
/**
* 删除节点
* @param node
* @return
*/
private T unlink(SummerNode<T> node){
final T type = node.t;
//删除节点的前一个节点
final SummerNode<T> pre = node.pre;
//删除节点的后一个节点
final SummerNode<T> next = node.next;
//头结点
if(pre == null){
first = next;
} else {
pre.next = next;
node.pre = null;
}
//尾结点
if(next == null){
last = pre;
} else {
next.pre = pre;
node.next = null;
}
node.t = null;
size--;
return type;
}
}
测试代码:
public class CoolListTest {
public static void main(String[] args) {
CoolList<String> coolList = new CoolList<>();
//增加
coolList.lastInsert("cool");
coolList.lastInsert("moon");
coolList.lastInsert("单向链表");
coolList.lastInsert("的");
coolList.lastInsert("单向链表");
//删除
coolList.remove(2);
//修改
coolList.replace(coolList.size - 1,"双向链表");
//插入
coolList.beforeIndexInsert(1,"summer");
StringBuffer sb = new StringBuffer();
for (int i=0; i<coolList.size; i++){
//查找
sb.append(coolList.get(i)).append(" ");
}
System.out.println(sb);
}
}
控制台输出:
cool summer moon 的 双向链表
结束语
java中LinkedList容器的底层数据结构也是双向链表,直达地址:从源码分析java容器之LinkedList