链表
链表是一种真正意义上的动态线性结构, 因为他的底层是动态的. 而动态数组底层是静态ede, 依靠resize解决固定容量问题
为什么链表很重要
- 最简单的动态数据结构
- 更深入的理解引用
- 更深入的理解递归
- 辅助组成其他数据结构
特性
对于链表来说, 数据存储在一个节点中(Node), 需要通过一个指针的引用next寻找下一个节点, 所以丧失了随机访问数据的能力, 必须遍历寻找
java代码是这样的
class Node{
E e;
Node next;
}
数组和链表的对比
数组
- 数组最好用于索引有语义的情况. 比如scorse[2]
- 最大的优点, 支持快速查询
链表
- 链表不适合用于索引有语义的情况
- 最大的优点: 动态
链表的实现
代码实现
package pers.jssd.linkedlist;
public class LinkedList<E> {
/**
* 链表中存储数据的内部类
*/
private class Node {
/**
* 存储数据
*/
E e;
/**
* 指向下一个节点
*/
Node next;
Node() {
this(null, null);
}
Node(E e, Node next) {
this.e = e;
this.next = next;
}
}
/**
* 虚拟头节点
*/
private Node dummyHead;
/**
* 链表的大小, 存储的元素个数
*/
private int size;
/**
* 新建一个链表, 默认带有头节点
*/
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
/**
* 在索引为index(0-bases)的位置添加元素e
*
* @param index 要添加的元素位置
* @param e 添加的元素
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("index out of bounds");
}
Node previous = dummyHead;
for (int i = 0; i < index; i++) {
previous = previous.next;
}
previous.next = new Node(e, previous.next);
size ++;
}
/**
* 在链表头部添加元素
*
* @param e 添加的元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 在链表尾部添加元素
*
* @param e 要添加的元素
*/
public void addLast(E e) {
add(size, e);
}
/**
* 取得链表中元素的个数
*
* @return int 返回链表中元素的个数
*/
public int getSize() {
return size;
}
/**
* 查看链表是否为空
*
* @return true链表为空, false则链表不为空
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 查看链表中是否存在某个元素
*
* @param e 查看链表中是否存在的元素
* @return true则链表中存在此元素, false则链表中不存在此元素
*/
public boolean contain(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
/**
* 取得链表中下标为index(0-bases)的元素
*
* @param index 取得元素的下标 (0-base)
* @return 返回取得的元素
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get error, index out of bound");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
/**
* 取得链表头部元素
* @return 返回取得的元素
*/
public E getFirst() {
return get(0);
}
/**
* 返回链表尾部元素
* @return 返回取得的元素
*/
public E getLast() {
return get(size - 1);
}
/**
* 在索引为index的位置, 更改元素为e
* @param index 更改元素的位置
* @param e 应改为的目标元素
*/
public void set(int index, E e) {
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
/**
* 删除索引在index(0-base)位置的元素, 并返回
* @param index 要删除的元素位置(0-base)
* @return 返回删除的元素
*/
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("remove error, index is out of bound");
}
Node previous = dummyHead;
for (int i = 0; i < index; i++) {
previous = previous.next;
}
Node res = previous.next;
previous.next = res.next;
res.next = null;
size --;
return res.e;
}
/**
* 删除第一个元素
* @return 返回删除的元素
*/
public E removeFirst() {
return remove(0);
}
/**
* 删除最后一个元素
* @return 返回删除的元素
*/
public E removeLast() {
return remove(size - 1);
}
/**
* 删除指定元素
* @param e 要删除的指定元素
*/
public void removeElement(E e) {
Node pre = dummyHead;
while (pre.next != null) {
if (e.equals(pre.next.e)) {
Node delNode = pre.next;
pre = pre.next;
delNode.next = null;
size --;
return;
}
pre = pre.next;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("size = ").append(size).append(" ");
Node cur = dummyHead.next;
sb.append("[");
while (cur.next != null) {
sb.append(cur.e).append(", ");
cur = cur.next;
}
sb.append(cur.e).append("]");
return sb.toString();
}
}
复杂度分析
-
添加元素
在头部添加元素的时候, 直接添加就可以所以是O(1)
在尾部添加元素的时候, 需要遍历到链表的尾部, 所以复杂度是O(n)
在任意位置添加元素的时候, 平均复杂度是O(n/2), 所以是O(n)
综合上述, 添加元素的复杂度为O(n)
-
删除元素
删除元素与添加元素基本差不多, 所以复杂度为O(n)
-
获取元素
使用链表获取一个元素, 需要遍历查找, 所以复杂度为O(n)
-
是否包含一个元素 contain
使用链表查看是否包含一个元素, 需要遍历, 所以复杂度为O(n)
使用链表实现栈
代码实现
package pers.jssd.stack;
public class LinkStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkStack() {
list = new LinkedList<>();
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public void push(E e) {
list.addFirst(e);
}
@Override
public E pop() {
return list.removeFirst();
}
@Override
public E peek() {
return list.getFirst();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public String toString() {
return "LinkStack{" +
"list= --> TOP" + list +
'}';
}
}
与数组栈进行比较
package pers.jssd.test;
import pers.jssd.stack.ArrayStack;
import pers.jssd.stack.LinkStack;
import pers.jssd.stack.Stack;
public class CompareStack {
public static void main(String[] args) {
ArrayStack<Integer> arrayStack = new ArrayStack<>();
LinkStack<Integer> linkStack = new LinkStack<>();
double time1 = test(arrayStack, 100000);
double time2 = test(linkStack, 100000);
System.out.println("time1 = " + time1);
System.out.println("time2 = " + time2);
}
/**
* 测试队列执行一定次数使用的时间
*
* @param stack 执行的队列
* @param opCount 执行的次数
* @return 返回执行的时间
*/
@SuppressWarnings("unchecked")
private static double test(Stack stack, int opCount) {
long start = System.nanoTime();
for (int i = 0; i < opCount; i++) {
stack.push(i);
if (i % 3 == 2) {
stack.pop();
}
}
long end = System.nanoTime();
return (end - start) / 1000000.0;
}
}
结果为
time1 = 8.4901
time2 = 7.7555
使用链表实现队列
因为队列要先进后出, 所以, 我们对链表添加一个指向尾部的指针, 便于操作, 同时提高性能
代码实现
package pers.jssd.queue;
public class LinkedQueue<E> implements Queue<E> {
private class Node {
/**
* 存储数据
*/
E e;
/**
* 指向下一个节点
*/
Node next;
Node() {
this(null, null);
}
Node(E e, Node next) {
this.e = e;
this.next = next;
}
}
private Node head, tail;
private int size;
public LinkedQueue() {
head = tail = null;
size = 0;
}
@Override
public void enqueue(E e) {
if (tail == null) {
tail = new Node(e, null);
head = tail;
} else {
Node newNode = new Node(e, null);
tail.next = newNode;
tail = newNode;
}
size ++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("dequeue error");
}
Node res = head;
head = head.next;
if (head == null) {
tail = null;
}
size --;
return res.e;
}
@Override
public E getFront() {
return head.e;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("size = ").append(size);
sb.append(" tail-->[");
Node cur = head;
while (cur.next != null) {
sb.append(cur.e).append(", ");
cur = cur.next;
}
sb.append(cur.e).append("]<--head");
return sb.toString();
}
}
测试代码
package pers.jssd.test;
import pers.jssd.queue.ArrayQueue;
import pers.jssd.queue.LinkedQueue;
import pers.jssd.queue.Queue;
public class TestLinkedQueue {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println("queue = " + queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println("queue = " + queue);
}
}
}
}
与循环队列, 数组队列进行比较
package pers.jssd.test;
import pers.jssd.queue.ArrayQueue;
import pers.jssd.queue.LinkedQueue;
import pers.jssd.queue.LoopQueue;
import pers.jssd.queue.Queue;
/**
* @author jssd
* Create 2019-07-24 9:49
*/
public class CompareQueue {
public static void main(String[] args) {
int opCount = 1000000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
LoopQueue<Integer> loopQueue = new LoopQueue<>();
LinkedQueue<Integer> linkedQueue = new LinkedQueue<>();
System.out.println("arrayQueue use time = " + test(arrayQueue, opCount));
System.out.println("loopQueue use time = " + test(loopQueue, opCount));
System.out.println("linkedQueue use time = " + test(linkedQueue, opCount));
}
/**
* 测试队列执行一定次数使用的时间
* @param queue 执行的队列
* @param opCount 执行的次数
* @return 返回执行的时间
*/
@SuppressWarnings("unchecked")
private static double test(Queue queue, int opCount) {
long start = System.nanoTime();
for (int i = 0; i < opCount; i++) {
queue.enqueue(i);
if (i % 3 == 2) {
queue.dequeue();
}
}
long end = System.nanoTime();
return (end - start) / 1000000000.0;
}
}
结果如下
arrayQueue use time = 29.0617751
loopQueue use time = 0.0409683
linkedQueue use time = 0.0774326