Java数据结构漫谈-LinkedList
同样是List的数据结构,LinkedList是使用了前后指针,指明节点的方式来表示链表的,这与之前介绍的ArrayList
http://www.cnblogs.com/yakovchang/p/java_arraylist.html
中使用数组的方式是截然不同的。LinkedList中的存储节点被称作节点(Node),一个节点的定义如下所示:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
这是LinkedList的一个内部类,不需要使用LinkedList的程序员关心。
LinkedList正是通过next,prev这两个指针来串联起整个List的。
注意:Node节点在初始化的时候同时指明了初始化的节点的前后节点是什么,所以在之后的代码中,往往没有明显的写明新的节点的前后指针指向了哪里。
在LinkedList本身中仅仅记录了List的开始和结束节点,当然,也记录了size:
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
在开始节点上添加一个节点,需要先判断是否为空,如果为空,则直接first和last都指向这个节点,否则就需要把原来的first的prev指向新节点,把新节点作为新的first节点,时间复杂度是o(1):
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
在结束节点上添加一个新节点,需要先判断是否为空,如果为空,就直接把last和firs都指向这个节点,否则就把原来的last的next指向新节点,把新节点作为新的last节点,时间复杂度是o(1):
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
在一个非空节点之前加入一个节点,如果这个非空节点的prev是null,那么说明非空节点是first节点,直接让first节点指向新节点,否则就让这个非空节点的next指向新的节点,时间复杂度是o(1):
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
删除一个非空的first节点,是把指向这个节点的指针都移除,同时把first指向他的next节点,如果next节点也为空,说明这个节点是List中的最后一个节点,那么first和last都指向空,时间复杂度是o(1):
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
同样的,删除一个非空的last节点,也是把指向这个节点的指针都移除,同时把last指向他的prev节点,如果prev节点为空,说明这个节点是List中的最后一个节点,那么first和last都指向空,时间复杂度是o(1):
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
删除一个非空节点,则是具有较多的判断条件,主要是取出来当前节点的prev和next,让他们之间建立连接,当然还需要判断是否为空,如果prev是空说明是第一个节点,如果next是空说明是最后一个节点,如果两者为空,说明List中只有这一个节点,这个操作的时间复杂度是o(1):
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
获取first,last,删除first,last,添加first,last,都是基于上述的操作进行的,所以它们的时间复杂度也都是o(1):
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
以上说的都是时间复杂度较低的操作,但是下面会有时间复杂度较高的操作,这也正是LinkedList被吐槽的地方。
查找一个对象的index是多少,就是通过遍历对比的方式进行的,其时间复杂度是o(n):
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
而通过index去确定对应的节点,也是使用了便利的办法,当然,这里采取了一个巧妙的方式是判断index距离头部更近,还是尾部更近,然后选择从哪里开始进行遍历,其时间复杂度也是o(n):
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
所有使用了上面两个函数的地方,其时间复杂度都是o(n)。
事实上,由于LinkedList的特殊性,LinkedList本身支持了很多其它的数据结构特性:
实现队列操作:
public E peek() {//查看第一个,但不删除
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E element() {//查看第一个,但不删除
return getFirst();
}
public E poll() {//获取第一个,并且删除
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E remove() {//获取第一个并且删除
return removeFirst();
}
public boolean offer(E e) {//在队列尾部添加一个
return add(e);
}
实现双向队列操作:
public boolean offerFirst(E e) {//在队列头部添加一个
addFirst(e);
return true;
}
public boolean offerLast(E e) {//在队列尾部添加一个
addLast(e);
return true;
}
public E peekFirst() {//查看队列头部但不删除
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekLast() {//查看队列尾部但不删除
final Node<E> l = last;
return (l == null) ? null : l.item;
}
public E pollFirst() {//获取并且删除队列头部元素
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {//获取并且删除队列尾部元素
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
实现栈的操作:
public E poll() {//查看栈顶但不删除
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public void push(E e) {//压栈
addFirst(e);
}
public E pop() {//出栈
return removeFirst();
}
实现移除第一次发生和最后一次发生:
public boolean removeFirstOccurrence(Object o) {//移除第一次出现
return remove(o);
}
public boolean removeLastOccurrence(Object o) {//移除最后一次出现
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
LinkedList是很多数据结构的基础,在之后的很多数据结构里面还会看到。
LinkedList最大的好处在于头尾和已知节点的插入和删除时间复杂度都是o(1)。
但是涉及到先确定位置再操作的情况,则时间复杂度会变为o(n)。
当然,每个节点都需要保留prev和next指针也是经常被吐槽是浪费了空间。
Java数据结构漫谈-LinkedList的更多相关文章
- Java数据结构漫谈-Stack
Stack(栈)是一种比较典型的数据结构,其元素满足后进先出(LIFO)的特点. Java中Stack的实现继承自Vector,所以其天然的具有了一些Vector的特点,所以栈也是线程安全的. cla ...
- Java数据结构之LinkedList、ArrayList的效率分析
前言: 在我们平常开发中难免会用到List集合来存储数据,一般都会选择ArrayList和LinkedList,以前只是大致知道ArrayList查询效率高LinkedList插入删除效率高,今天来实 ...
- java数据结构之LinkedList
一.LinkedList源码注释 //LinkedList源码 jdk版本1.8.0_121 public class LinkedList<E> extends AbstractSequ ...
- Java数据结构漫谈-Vector
List除了ArrayList和LinkedList之外,还有一个最常用的就是Vector. Vector在中文的翻译是矢量,向量,所以大家喜欢把Vector叫做矢量数组,或者向量数组. 其实就底层实 ...
- Java数据结构漫谈-ArrayList
ArrayList是一个基于数组实现的链表(List),这一点可以从源码中看出: transient Object[] elementData; // non-private to simplify ...
- Java集合之LinkedList
一.LinkedList概述 1.初识LinkedList 上一篇中讲解了ArrayList,本篇文章讲解一下LinkedList的实现. LinkedList是基于链表实现的,所以先讲解一下什么是链 ...
- JAVA数据结构系列 栈
java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...
- Java数据结构之线性表(2)
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- Java数据结构之线性表
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
随机推荐
- js带箭头左右翻动控制
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- DOM 添加 / 更新 / 删除 XML (CURD)
获得Document /** * 获取文档 * 1.获得实例工厂 * 2.获得解析器 * 3.获得document */ 添加结点 /** * 1.获得 ...
- ISO3834认证所需的部分标准
SO9606-1 焊工考试——熔化焊——第一部分:钢 ISO9606-2 焊工考试——熔化焊——第二部分:铝及铝合金 ISO14732 焊接人员——金属材料全机械化及自动化焊接的熔化焊操作攻击电阻焊安 ...
- PHP页面中文乱码问题
首先纯html页要用meta标签声明编码<meta http-equiv="Content-Type" content="text/html; charset=&q ...
- python类方法与对象方法学习
class Test_Demo: TEST = 'test_value' def __init__(self,name,age): self.name = name self.age = age #s ...
- destoon系统商城加淘宝客按钮方法
destoon系统很多喜欢运营B2B的站长都在用,其中的商城模块常常被用来做淘宝客,其中的难点是如何把购买按钮做成淘宝客地址,这个问题的修改在论坛上被叫价50元,下面小编把这个实用的方法分享下,希望对 ...
- Python新手学习基础之运算符——赋值与逻辑运算
赋值也是一种运算符 我们在之前的章节,了解过,在Python里变量是不需要声明的,只要变量的标识符合法,就可以直接定义并赋值,而且Python也允许我们同时为多个变量赋值(包括为多个变量赋不同类型的值 ...
- C语言(按键获取与函数)
举一个简单的例子,如果有按键,就输出相关按键.否则,输出“.”.每隔 100 毫秒输出一次.按 ESC 退出.注:ESC 的 ASCII 码是 27. #include <stdio.h> ...
- 理解java的三大特性之多态(三)
摘自:http://cmsblogs.com/?p=52 面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已 ...
- MongoDB资料--Java驱动, Hadoop驱动, Spark使用
MongoDB数据库备份: mongodump -h 192.168.1.160 -d MapLoc -o /usr/local/myjar/mongo/MapLoc/数据库还原:mongoresto ...