【数据结构】链表LinkedList

1.ArrayList的缺陷

2.单链表的实现

3.LinkedList的使用(模拟实现)

我们之前介绍过ArrayList了,它的底层是数组,数组是一段连续的空间,当我们想要插入或者删除数据的时候,插入元素,就要让插入位置的元素整体都往后移动,删除元素同样要让后面的元素往前移动,当要进行元素很多的插入或者删除的时候,ArrayList是效率很低的,所以当我们要大量插入元素或者大量删除元素,不推荐使用ArrayList

(由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景)

因此:java集合中又引入了LinkedList,即链表结构。

(1)链表的概念:链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 

链表我们之前也介绍过,它在物理空间上是不连续,但在逻辑上连续,也就是说它的元素所在的存储空间中不是连在一起的,不是说元素1空间后面就是元素2的空间,但它在逻辑上连续,元素1跟元素2相当于拿一根绳子连接,虽然不是紧挨着的,但通过元素1就能找到元素2,这就像我们生活中的火车,或火车的车厢都是链在一起的

【数据结构】链表LinkedList_第1张图片

 链表中的元素我们称为节点,一个节点包含着本身的值,和下一个节点的信息(下一个节点的地址),分别是value和next(对于单链表而言)


实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

(1)带头不带头

(2)单向双向

(3)循环非循环

我们把他们排列组合一下就能排列出8中情况,我们重要学习的链表结构有两种

1.不带头单向非循环

2.不带头双向非循环

自然这两种是面试笔试中常考的,当然也是比较难懂的,因为给的条件不多嘛~

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多

无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

这里解释一下这几个词的含义

带头 | | 不带头:头的意思就是头节点的意思,头节点的值一般没有什么含义,它与第一个元素链接,它拥有第一个元素的地址,只要有头,我们就能轻松找到第一个元素,你可以把它当做一个虚拟节点(虚拟节点的操作经常用来解题)

单向 || 双向:什么叫做单向,我们不是说了嘛,每一个节点它都包含下一个节点的地址,单向顾名思义就是一个方向,你只能从1找到2,而不能返回来从2找到1,因为1有2的地址,2只有3的地址,没有1的地址,所以说它是单向的;那么双向就是可以从1找到2,也可以从2找到1啦,双向链表的节点中,包含了两个地址,一个是下一个节点的地址,另外一个是上一个节点的地址

【数据结构】链表LinkedList_第2张图片

 上图就是单链表和双链表的图解

循环 | | 非循环:循环的意思就是这个链表围成了一个圈圈,下面给图来看

【数据结构】链表LinkedList_第3张图片

这图一目了然吧~上图就是循环,非循环自然不多解释


 接下来来自定义实现一个单链表

下面直接给出源码

package LinkedList内容;

import ArrayList内容.PosIndexNotLegalException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86152
 * Date: 2022-11-12
 * Time: 20:46
 */
public class SingleList {
    //创建节点:包含值和地址两部分
    static class Node{
        public int val;
        public Node next;
        public Node(int val){
            this.val = val;
        }
    }

    public Node head;//创建头节点

    //用穷举的方式创建链表
    public void createList(){
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }


    //头插法:时间复杂度O(1)
    public void addHead(int value){
        Node node = new Node(value);
        node.next = head;
        head = node;
    }

    //尾插法:时间复杂度O(n)
    public void addTail(int value){
        Node node = new Node(value);
        //处理没有节点的情况
        if(head == null){
            head = node;
        }else {
            Node cur = head;
            while (cur.next != null){
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    //遍历打印链表
    public void display(){
        Node cur = this.head;
        while (cur != null){
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    //判断链表中是否存在key值
    public boolean contains(int key){
        Node cur = head;
        while (cur != null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //链表长度
    public int size(){
        int count = 0;
        Node cur = head;
        while (cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    }

    //在指定位置插入数据,要判断位置合法性

    private void checkIndex(int index){
        if(index < 0 || index > size()){
            throw new IndexNotLegalException();
        }
    }
    public void addIndex(int index, int value){
        checkIndex(index);
        if(index == 0){
            addHead(value);
            return;
        }
        if(index == size()){
            addTail(value);
            return;
        }
        Node node = new Node(value);
        Node cur = head;
        for(int i = 0; i < index-1; i++){
            cur = cur.next;
        }
        node.next = cur.next;
        cur.next = node;
    }

    //删除第一次出现key的节点
    //先找到前驱
    private Node prevKey(int key){
        Node cur = this.head;
        while (cur.next != null){
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    public void remove(int key){

        //头节点要单独处理
        if(head.val == key){
            head = head.next;
            return;
        }

        Node cur = prevKey(key);
        if(cur == null){
            return;
        }
        Node del = cur.next;
        cur.next = del.next;
        //cur.next = cur.next.next;
    }

    //删除所有值为key的节点
    //先处理其他节点,最后再处理头节点
    //对于头节点的处理得放在后面处理,放在前面最后不好处理
    public void removeAllKey(int key){
        if(head == null){
            return;
        }

        Node prev = head;
        Node cur = head.next;
        while (cur != null){
            if(cur.val == key){
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        if(head.val == key){
            head = head.next;
        }

    }

    //清空链表的所有节点
    public void clear(){
        //this.head = null; 简单粗暴

        Node cur = head;
        while (cur != null){
            Node curNext = cur.next;
            cur.next = null;
            cur = curNext;
        }
        head = null;
    }

    //递归实现链表的打印
    public void printList(Node head){
        if(head == null){
            return;
        }
        if(head.next == null){
            System.out.print(head.val+" ");
            return;
        }
        printList(head.next);
        System.out.print(head.val+" ");
    }

}

很多方法的细节都写在了注释里了~


我们下面来介绍一下LinkedList,我们在写题目的时候,通常少不了使用

LinkedList底层就是一个双向链表

双向链表的实现这里就不实现了,无非就是在单链表的基础上多加上一个prev,然后还有last(这是尾节点,和头节点head对应)

LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

【数据结构】链表LinkedList_第4张图片

【说明】

 LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问 


下面介绍一下LinkedList的使用

这边直接给代码了,解释写在了注释里面

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86152
 * Date: 2022-11-14
 * Time: 17:36
 */
public class LinkedListTest {


    //三种遍历方法
    public static void main(String[] args) {
        List list = new LinkedList<>();
        list.add(1);
        list.add(1);
        list.add(1);
        list.add(1);
        list.add(1);
        list.add(19);
        list.add(10);
        System.out.println("======for=========");
        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
        System.out.println("========foreach========");
        for(Integer x : list){
            System.out.print(x + " ");
        }
        System.out.println();
        System.out.println("==========迭代器========");
        ListIterator it = list.listIterator();
        while (it.hasNext()){
            System.out.print(it.next() + " ");
        }
        System.out.println();
        //反向迭代器打印
        ListIterator rit = list.listIterator(list.size());
        while (rit.hasPrevious()){
            System.out.print(rit.previous() + " ");
        }
    }

    /**
     * LinkedList常用api
     * @param args
     */
    public static void main2(String[] args) {
        LinkedList list = new LinkedList<>();
        list.add(1);
        list.add(10);
        list.add(18);
        list.add(13);
        list.add(2);
        list.add(3);//add默认尾插
        System.out.println(list);

        list.add(0,2);//add的构造方法,支持在指定元素插入
        System.out.println(list);

        //删除操作remove
        //list.remove();//不带参数的remove默认删除第一个元素
        //list.remove(1);//带一个参数表示删除指定位置元素
        //list.removeFirst();
        //list.removeLast();
        //System.out.println(list);

        //查询contains 判断元素是否存在
        System.out.println(list.contains(18));

        //找到第一次出现元素的下标
        System.out.println(list.indexOf(2));//找第一次出现的位置
        System.out.println(list.lastIndexOf(3));//找最后一次出现的位置

        //获取指定位置元素
        System.out.println(list.get(0));

        //更新指定位置元素
        list.set(0,99);
        System.out.println(list);

        //截取元素
        System.out.println(list.subList(0, 2));//区间是前闭后开

        //还可以用截取的元素重新创建一个链表
        List list2 = list.subList(0,3);
        System.out.println(list2);

        //清空元素
        list.clear();
        System.out.println(list.size());

    }

    /**
     * 构造方法
     * @param args
     */
    public static void main1(String[] args) {
        //一种是无参的构造方法
        LinkedList list = new LinkedList<>();

        //第二种:LinkedList的构造方法可以传入ArrayList
        ArrayList arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);

        LinkedList linkedList = new LinkedList<>(arrayList);
        System.out.println(linkedList);

    }
}

【数据结构】链表LinkedList_第5张图片

 (部分图片来源:比特高博)

你可能感兴趣的:(java,数据结构,链表)