0
点赞
收藏
分享

微信扫一扫

java集合图解源码系列【3】:从链表讲到LinkedList(含图解反转单链表算法)

大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!

||Data Structure||

​ 未来村村长正推出一系列【Data Structure】文章,将从解读数据结构的角度上分析Java集合的源码。因为CSDN上的大多数描述java集合的文章,关注点在于其源码和方法,很少从对数据结构的讲解为切入点进行分析。以此为契机,未来村村长希望能从数据结构开始讲起,分析java集合是如何使用和如何实现的。

文章目录

一、单向链表

​ 最基础的链表即为单向链表,链式结构也是后续数据结构的基础。在Java中,没有指针类型我们通过类来实现链式连接。实现单向链表,我们首先定义一个节点。

在这里插入图片描述

class Node<E>{
    E e;
    Node next;//存放下一个节点的地址
	
    //构造器
    public Node(E e){
        this.e = e;
        this.next = null;
    }
}

1、定义

​ 定义一个单向链表,有三个属性分别代表头结点、尾节点和链表大小。

在这里插入图片描述

public class LinkedList<E>{
    
    private Node<E> first;//定义头结点
    private Node<E> last;//定义尾节点
    private int szie;//链表存储的节点数量
    //链表为空判断
    public boolean isEmpty(){return first==null;}
}

2、尾插

在这里插入图片描述

public void add(E e){
    Node<E> newNode = new Node<>(e);//为传入的数据建立节点
    //若为空,则将插入的元素作为头尾节点
    if(isEmpty){
        first = newNode;
        last = newNode;
    }else{
        last.next = newNode;
        last = newNode;
    }
}

3、删除

在这里插入图片描述

在这里插入图片描述

public void delete(Node<E> delNode){
    Node<E> newNode;
    Node<E> tmp;
    //如果被删除节点为头结点
    if(first.e.equals(delNode.e)){
        first = first.next;
    }
    //如果被删除节点为尾节点
    else if(last.e.equals(delNode.e){
        newNode = first;
        while(newNode.next!=last) newNode = newNode.next;
        newNode.next = last.next;
        last = newNode;
    }
    //删除的是中间节点
    else{
        newNode = first;
        tmp = first;
        while(!newNode.e.equals(delNode.e)){
            tem = newNode;
            newNode = newNode.next;
        }
        tem.next = delNode.next;
    }
}

4、单向链表的反转(常考算法)

​ ①

在这里插入图片描述

​ ②

在这里插入图片描述

在这里插入图片描述

public void reverse(){
    Node<E> current = first;//定义游标节点
    Node<E> before = null;//定义指向游标节点之前的节点
    
    while(current!=null){
        last = before;
        before = current;
        current = current.next;
        before.next = last;
    }
    current = before;
    first = current;
    while(last.next!=null){
        last = last.next;
    }
}

​ 我们通过while循环,每次循环我们关注last和first的变化,第一次循环我们将last取出作为前一个节点(第一次为新头节点),before指向游标所在节点,然后游标找到下一个节点,我们让before所在节点指向我们取出的节点。就是不断地将最后一个节点拿出来,然后让先拿出的节点指向后拿出的节点。重点在于拿出再连接

二、循环链表

​ 循环链表的不同之处在于插入和删除,下面举例插入第一个元素时,循环链表的做法。

if(isEmpty()){
    first = newNode;
    last = newNode;
    last.next = first;
}

​ 我们只要在单链表的基础上,让last.next指向first而不是null即可。

三、双向链表

​ 双向链表就是可以从前往后,也可以从后往前的链表,LinkedList的数据结构基础就是双向链表。我们来看其节点声明。

class Node<E>{
    E e;
    Node<E> pre;
    Node<E> next;
	Node(E e){
        this.e = e;
        this.pre = null;
        this.next = null;
}

​ 因为LinkedList的基础为双向链表,所以我们直接到LinkedList去探究其源码实现。

四、LinkedList

​ 首先我们来看看LinkedList的基础使用。

public class IndexMain {

    public static void main(String args[]) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("未来村村长01");
        linkedList.add("未来村村长02");
        linkedList.add("未来村村长03");
        linkedList.add("未来村村长05");
        linkedList.add(3,"未来村村长04");
        linkedList.remove(3);
        linkedList.remove();
        Iterator<String> iterator = linkedList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

​ 输出的结果如下:

在这里插入图片描述

​ 这说明LinkedList插入默认为尾插,可以指定index进行插入。但是删除默认从头开始,即“先进先出”。这里有牵扯到队列的知识。从下面class LinkedList中可以知道,其实现了List和Deque接口,一个是对线性表的接口,一个是队列的接口,所以其删除默认为“先进先出”。

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

1、基本属性定义和方法

(1)基本属性

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;//链表大小

    transient Node<E> first;//头节点

    transient Node<E> last;//尾节点

    public LinkedList() {//无参构造
    }

    public LinkedList(Collection<? extends E> c) {//有参构造
        this();
        addAll(c);
    }

(2)节点定义

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;
        }
    }

2、插入

(1)add()

public boolean add(E e) {
        linkLast(e);
        return true;
}
linkLast():尾插
void linkLast(E e) {
        final Node<E> l = last;//用一个空间点赋值last
        final Node<E> newNode = new Node<>(l, e, null);//新节点
        last = newNode;//让last成为新节点
        if (l == null)
            first = newNode;//如果last是空,则让头结点也赋值新节点
        else
            l.next = newNode;//原来的last的next赋值新节点
        size++;
        modCount++;
}

(2)add(index,item)

    public void add(int index, E element) {
        checkPositionIndex(index);//判断该位置是否正确
        if (index == size)
            linkLast(element);//如果插入的位置是last,采用尾插
        else
            linkBefore(element, node(index));//如果不是,则寻找节点位置
    }
node(index)

​ LinkedList中将节点的定位封装起来了,使用循环找到对应位置的节点,并返回该节点。

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;
        }
}
linkBefore()
void linkBefore(E e, Node<E> succ) {
        final Node<E> pred = succ.prev;//pred为index节点的前驱节点
        final Node<E> newNode = new Node<>(pred, e, succ);//新节点定义构造赋值前驱为index节点的前驱节点,后驱为index
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;//index节点的前驱节点为newNode
        size++;
        modCount++;
}

(3)push()

​ 和add()方法相同。

public void push(E e) {addFirst(e);}
public void addFirst(E e) {linkFirst(e);}

3、删除

(1)remove()

public E remove() {return removeFirst();}
public E removeFirst() {
        final Node<E> f = first;//用空节点装旧的头节点
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
}
unlinkFirst:取消头节点的连接
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;//next节点赋值头节点的下一个节点
        f.item = null;
        f.next = null; // help GC
        first = next;//让下一个节点赋值头结点
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
}

(2)remove(index)

public E remove(int index) {
        checkElementIndex(index);//检查index是否正确
        return unlink(node(index));//node(index)同上,找到对应index的节点
}
unlink
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;//index的下一个节点
        final Node<E> prev = x.prev;//index的上一个节点

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;//上一个节点的下一个节点指向index的下一个节点
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;//下一个节点的上一个节点指向index的上一个节点
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
}

(3)pop()

public E pop() {return removeFirst();}
public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
}
举报

相关推荐

0 条评论