05-链表 LinkedList

学习资源:慕课网liyubobobo老师的《玩儿转数据结构》


1、线性数据结构

image-20200607203911317

2、链表简介

  • 最简单的动态数据结构,
  • 线性的数据结构
  • 更深入的理解引用(或者指针)
  • 更深入的理解递归
  • 辅助组成其他数据结构
  • 数据存储在“结点”(Node)中,结点会指向下一个结点
  • 链表也有长度,最后一个结点指向null

image-20200426161343007

  • 优点:真正的动态,不需要处理固定容量的问题
  • 缺点:丧失了随机访问的能力

3、链表的实现

内部私有化一个Node类,存有当前结点的值当前结点指向的的下一个结点

显而易见,最后一个结点指向null

private class Node {
    
	E e;
	Node next;
}

3.1、从链表头部添加元素

添加元素的方向:从链表头部添加元素,更方便,只需让新结点的next指向原链表的头结点即可。

image-20200426174350095 image-20200426165001114 image-20200426165022913

3.2、链表中间插入元素

在链表某索引处插入一个元素,需要找到该索引的前一个结点prev

之后:让新添加结点指向prev原来指向的结点;再改变prev的指向:指向新添加结点。

存在的问题:索引0不适用,即头结点prev结点;头结点需要另外的判断处理。

image-20200426184114281

3.3、优化——为链表设立虚拟头结点

在插入元素时,首先需要判断一下是否为头结点。这样判断不够优雅,可以进行优化。

dummyHead的引入并不会打乱原来的添加逻辑,只会简化原有逻辑。

如下图,此时真正的头结点为dummyHeadnextdummyHead是一个虚拟结点,数据为null即可,对外不可知。

image-20200426211944963

3.4、删除链表中的元素

同添加操作相同,使用虚拟头结点可以简化删除逻辑。

image-20200427094820362

3.5、代码

package linkedList;

public class LinkedList<E> {

    //结点
    private class Node{

        public E e;
        public Node next;

        public Node(E e, Node next) {

            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {

            this.e = null;
            this.next = null;
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    //虚拟头结点
    private Node dummyHead;
    int size;

    public LinkedList() {

        dummyHead = new Node();
        size = 0;
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public void add2Head(E e){
        add(0, e);
    }

    public void add2Last(E e){
        add(size, e);
    }

    //在链表的index位置添加新的元素,范围:第一个元素前一个,最后一个元素后一个
    //在链表中不是一个常用的操作
    //添加操作,涉及到索引前后的数据,prev处理起来很有效果
    public void add(int index, E e){

        if(index<0 || index>size){
            throw new IllegalArgumentException("索引不合法");
        }

        Node prev = dummyHead;
        for(int i=0; i<index; i++){
            prev = prev.next;
        }

//        Node node = new Node(e);
//        node.next = prev.next;
//        prev.next = node;
        prev.next = new Node(e, prev.next);
        size++;
    }

    public E getHead(){
        return get(0);
    }

    public E getLast(){
        return get(size-1);
    }

    public E get(int index){

        if(index<0 || index>size){
            throw new IllegalArgumentException("获取失败,索引不合法");
        }

        Node cur = dummyHead.next;
        for(int i=0; i<index; i++){
            cur = cur.next;
        }
        return cur.e;
    }

    public void set(int index, E newE){

        if(index<0 || index>size){
            throw new IllegalArgumentException("修改失败,索引不合法");
        }

        Node cur = dummyHead.next;
        for(int i=0; i<index; i++){
            cur = cur.next;
        }
        cur.e = newE;
    }

    public E remove(int index){

        Node prev = dummyHead;
        for(int i=0; i<index; i++){
            prev = prev.next;
        }

        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size--;

        return retNode.e;
    }

    public E removeLast(){
        return remove(size-1);
    }

    public E removeHead(){
        return remove(0);
    }

    public void removeElement(E e){

        Node prev = dummyHead;
        while (prev.next != null){
            if(prev.next.e.equals(e)){
                break;
            }
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
        }
    }

    public boolean contains(E e){

        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e)){
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur!=null){
            builder.append(cur+"->");
            cur = cur.next;
        }
        builder.append("NULL");

        return builder.toString();
    }
}

3.6链表的时间复杂度分析

image-20200607231216834

4、基于链表的栈

分析链表的时间复杂度,易知,链表很容易实现栈的特性:从一端添加也从同一端删除,只有头结点对外暴露。

数组栈是一个静态栈,而链表栈是一个静态栈,在push时不存在扩容复制,但是链表栈会在push时会new对象。

4.1、代码

package stack;

import linkedList.LinkedList;

public class LinkedListStack<T> implements Stack<T> {

    private LinkedList<T> linkedList;

    public LinkedListStack() {
        linkedList = new LinkedList<>();
    }

    @Override
    public int getSize() {
        return linkedList.getSize();
    }

    @Override
    public boolean isEmpty() {
        return linkedList.isEmpty();
    }

    @Override
    public void push(T t) {
        linkedList.addToHead(t);
    }

    @Override
    public T pop() {
        return linkedList.removeHead();
    }

    @Override
    public T peek() {
        return linkedList.getHead();
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();
        builder.append("Stack: top ");
        builder.append(linkedList);
        return builder.toString();
    }
}

4.2、测试

比对数组栈与链表栈:运行时间与次数有关。

@Test
public void ArrayVsLoop(){

    ArrayStack<Integer> arrayStack = new ArrayStack<>();
    LinkedListStack<Integer> listStack = new LinkedListStack<Integer>();

    int arrayTime = getRunTime(10000000, arrayStack);
    int listTime = getRunTime(10000000, listStack);

    System.out.printf("数组栈的执行时间是:%d毫秒\n",arrayTime);
    System.out.printf("链表栈的执行时间是:%d毫秒\n",listTime);
}

private int getRunTime(int times, Stack<Integer> stack){

    long startTime = System.currentTimeMillis();

    Random random = new Random();

    for(int i=0; i<times; i++){
        stack.push(random.nextInt(Integer.MAX_VALUE));
    }
    for(int i=0; i<times; i++){
        stack.pop();
    }
    long endTime = System.currentTimeMillis();
    long time = endTime - startTime;
    return (int)time;
}

5、基于链表的队列

因为队列是FIFO,所以基础的链表模型并不适用于作为队列底层。改进链表模型:在链表中增加一个tail结点。

队列:基于链表的结点,从tail入队,head出队。

image-20200427150758516

5.1、代码

package queue.linkedListQueue;

import queue.arrayQueue.Queue;

public class LinkedListQueue<T> implements Queue<T> {

    //结点
    private class Node{
        public T t;
        public Node next;

        public Node(T t, Node next) {
            this.t = t;
            this.next = next;
        }

        public Node(T t) {
            this(t, null);
        }

        public Node() {
            this.t = null;
            this.next = null;
        }

        @Override
        public String toString() {
            return t.toString();
        }
    }

    private Node head, tail;
    private int size;

    public LinkedListQueue() {
        
        head = tail = null;
        size = 0;
    }

    @Override
    public void enqueue(T t) {

        if(isEmpty()){
            tail = new Node(t);
            head = tail;
        }else {
            tail.next = new Node(t);
            tail = tail.next;
        }
        size++;
    }

    @Override
    public T dequeue() {
        if(isEmpty()){
            throw new IllegalArgumentException("出对失败,队列为空");
        }
        Node retNode = head;
        head = head.next;
        retNode.next = null;
        if(head==null){
            tail = null;
        }
        size--;
        return retNode.t;
    }

    @Override
    public T getFront() {
        return head.t;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size==0;
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();
        builder.append("Queue: front ");

        Node cur = head;
        while (cur != null){
            builder.append(cur+"->");
            cur = cur.next;
        }
        builder.append("NULL tail");
        return builder.toString();
    }
}

5.2、测试

比对循环队列与链表队列:运行时间与次数有关。

public static void main(String[] args) {
    LoopQueue<Integer> loopQueue = new LoopQueue<>();
    LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();

    int loopTime = getRunTime(100000, loopQueue);
    int linkedListTime = getRunTime(100000, linkedListQueue);

    System.out.printf("循环队列的执行时间是:%d毫秒\n",loopTime);
    System.out.printf("链表队列的执行时间是:%d毫秒\n",linkedListTime);
}

private static int getRunTime(int times, Queue<Integer> queue){

    long startTime = System.currentTimeMillis();

    Random random = new Random();

    for(int i=0; i<times; i++){
        queue.enqueue(random.nextInt(Integer.MAX_VALUE));
    }
    for(int i=0; i<times; i++){
        queue.dequeue();
    }
    long endTime = System.currentTimeMillis();
    long time = endTime - startTime;
    return (int)time;
}

6、其他形态的链表

6.1、双链表

image-20200428214141719

6.2、循环链表

image-20200428214451574

6.3、数组链表

image-20200428214617500

7、Java中的链表

LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    
}

API接口

image-20200607230620083 image-20200607230717709
posted @ 2020-06-08 23:23  卡文迪雨  阅读(171)  评论(0编辑  收藏  举报