LinkedList与链表

在这里插入图片描述

🎉🎉🎉写在前面:
博主主页:🌹🌹🌹戳一戳,欢迎大佬指点!
博主秋秋:QQ:1477649017 欢迎志同道合的朋友一起加油喔💪
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
-----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------

在这里插入图片描述



LinkedList与链表

集合类的背后是数据结构,ArrayList的背后是数组,最基本的数据结构。LinkedList的背后是链表,也是数组结构。

一,链表

1.1,链表的概念和结构

链表是一种物理存储上的非连续的存储结构,数据元素的逻辑顺序是依靠引用连接次序来实现的。,链表形象的比喻类似于一个小火车,一节节的串起来。
在这里插入图片描述


在这里插入图片描述


head就是一个引用类型的变量,用来存储第一个节点的地址。这种存储形式叫做链式存储。

我们的实际中,链表形式有带头的,不带头的,单向的,双向的,循环的,非循环的,所以排列组合后,我们可以得到8种结构。

在这里插入图片描述


认识一下什么叫做带头和不带头:

在这里插入图片描述


由图可知,带头其实就是有一个专门节点作为头节点,这个头节点不用来存储值,只是用来永远的标识这是这个链表的头位置,不管后面的节点做任何的操作,头节点的位置永远是它不变(也就是head的值不变)。那么不带头其实就是没有一个专门的头节点,它只是默认第一个为头节点,那么在进行操作之后,例如删除节点,那么头节点的位置就会变化,没有一个固定的值(head没有固定值)。


1.2,模拟实现一个单链表集合类

public class SingleList {
    static class Node{//内部类,节点类
        public int val;
        public Node next;//next指向下一个节点

        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;//存储头结点的引用

    public void createList(){
        //创建四个节点,只赋值
        Node node1 = new Node(10);
        Node node2 = new Node(20);
        Node node3 = new Node(30);
        Node node4 = new Node(40);
        //下面把各个节点串起来
        node1.next = node2;//node2就是第二个节点的地址
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }

}

问题:我们如何去遍历一个链表?

在这里插入图片描述

public void displayList(){
    while(head != null){
        System.out.print(head.val + " ");
        head = head.next;
    }
}

程序运行结果:

在这里插入图片描述


上面虽然确实是已经把链表遍历并且输出了,但是存在一个问题,因为我们是直接动的head指针,所以最终遍历完之后head = null,但是链表中的元素是全都存在的,那你下次再要访问这个链表,但是head等于null,那是不是就有问题了,所以我们不能直接动head,要用一个中间变量去遍历。

【代码改进:】

public void displayList(){
    Node cur = this.head;
    while(cur != null){
        System.out.print(cur.val + " ");
        cur = cur.next;
    }
}

上面是一个链表的基本属性,那么下面我们就加上对其的操作方法,形成一个完整的集合类。

public class SingleList {
    static class Node{//内部类,节点类
        public int val;
        public Node next;//next指向下一个节点

        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;//存储头结点的引用

    public void createList(){
        //创建四个节点,只赋值
        Node node1 = new Node(10);
        Node node2 = new Node(10);
        Node node3 = new Node(10);
        Node node4 = new Node(40);
        //下面把各个节点串起来
        node1.next = node2;//node2就是第二个节点的地址
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }

    public void displayList(){
        Node cur = this.head;
        while(cur != null){
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    //头插法  时间复杂度O(1)
    public void addFirst(int data){
        Node node = new Node(data);
        node.next = this.head;
        this.head = node;
//        当你的链表里面没有元素时,头插其实也满足,因为空的时候head = null
    };
    //尾插法    时间复杂度O(n) 最坏情况下需要找到尾巴
    public void addLast(int data){
        Node node = new Node(data);
        if(this.head == null){
            head = node;
        }else{
            Node cur = this.head;
            //        先将cur移动到尾部
            while(cur.next != null){
                cur = cur.next;
            }
            cur.next = node;
        }

    };
    //任意位置插入,第一个数据节点为0号下标
    private boolean checkIndex(int index){
        if(index < 0 || index > size()){
            throw new IndexLegalException("插入下标不合法!");
        }
        return true;
    }

    private Node findIndexSubOne(int index){
//        找到index-1的节点
        Node cur = this.head;
        while(index - 1 != 0){
            cur = cur.next;
            index--;
        }
        return cur;
    }
    public void addIndex(int index,int data){
//        其他位置,先检验下标合法性
        try{
            checkIndex(index);
            if(index == 0){
//        那就是头插
                addFirst(data);
                return;
            }
            if(index == size()){
//            那就是尾插
                addLast(data);
                return;
            }
//            中间插入
            Node cur = findIndexSubOne(index);//找到index - 1位置的节点
            Node node = new Node(data);
            node.next = cur.next;
            cur.next = node;

        }catch (IndexLegalException e){
            e.printStackTrace();
        }

    };
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        Node cur = this.head;
        while(cur != null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    };
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        if(this.head == null){
            return;
        }
        if(this.head.val == key){
            head = head.next;
            return;//必须要return
        }
        Node cur = searchPrevOfKey(key);
        if(cur == null){
            return;//cur == null 就说明没有找到这么一个key值节点
        }
        cur.next = cur.next.next;
    };

    private Node searchPrevOfKey(int key) {
//        找到key值这个节点的前一个节点
        Node cur = this.head;
        while(cur.next != null){//遍历链表
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        if(this.head == null){
            return;//链表为空
        }
        Node node = new Node(-1);//虚拟节点
        node.next = this.head;
        Node prev = node;
        Node cur = this.head;
//        Node prev = this.head;
//        Node cur = this.head.next;
        while(cur != null){
            if(cur.val == key){
                cur = cur.next;
                prev.next = cur;
            }else{
                prev = cur;
                cur = cur.next;
            }
        }
        this.head = node.next;
        //特殊情况,头节点也为key
//        if(this.head.val == key){
//            head = head.next;
//        }
    };
    //得到单链表的长度
    public int size(){
        int count = 0;
        Node cur = this.head;
        while(cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    };
    public void clear(){
        this.head = null;//直接把头干掉,那么后续节点就没人引用,就连锁反应,一个个被回收
        //也可以遍历,一个个的置为null,但是相对比较麻烦,不是很有必要
    };
}

【头插图示解析:】

在这里插入图片描述


【尾插图示解析:】

在这里插入图片描述


【index位置插入图示解析:】

在这里插入图片描述


【删除第一次出现的key:】

在这里插入图片描述


【删除所有出现的key:】

在这里插入图片描述


三,LinkedList模拟实现

LinkedList背后的数据结构就是一个双向的链表。结构如下:

在这里插入图片描述


不得不说,双向链表可真是比单向链表舒服多了,既可以记录尾部的位置,还能记录前驱的地址,不要很强大了!

class MyLinkedList{
    static class ListNode{//内部节点类
        public int val;
        public ListNode prev;
        public ListNode next;
        public ListNode(int val){
            this.val = val;
        }
    }
    public ListNode head;//头节点
    public ListNode last;//尾节点

    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null){
            head = node;//原链表为空的情况下,头节点,尾节点都是node
            last = node;
        }else{
//            链表不为空的情况下,头插,node的prev为null,不需要考虑
            node.next = head;
            head.prev = node;
            head = node;//把head更新一下,尾节点是不需要更新的
        }


    };
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null){//如果刚开始链表就为null
            head = node;
            last = node;
        }else{
            last.next = node;
            node.prev = last;
            last = node;
        }
    }
    private boolean checkAddIndex(int index){
        if(index < 0 || index > size()){
            throw new IndexException("index不合法!");
        }
        return true;
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
//        先检查index的合法性
        try{
            checkAddIndex(index);
            ListNode node = new ListNode(data);
//        先找到index的元素
            if(index == 0){
                addFirst(data);
                return;//如果index == 0就是头插
            }
            if(index == size()){
                addLast(data);
                return;//就是尾插
            }
//        中间插入
            ListNode cur = head;
            while(index != 0){//cur往后走index步,找到index位置的元素
                cur = cur.next;
                index--;
            }
            node.next = cur;
            node.prev = cur.prev;
            cur.prev.next = node;
            cur.prev = node;
        }catch (IndexException e){
            e.printStackTrace();
        }
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = this.head;
        while(cur != null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while(cur != null){
            if(cur.val == key){
                if(cur == head){//如果是删除第一个节点
                    if(head.next == null){//并且链表就只存在第一个节点
                        head = null;
                    }else{
                        head = head.next;
                        head.prev = null;
                    }
                }else{//删除的不是第一个节点
                    if(cur == last) {//是最后一个节点
                        cur.prev.next = null;
                        last = cur.prev;
                    }else{
                        cur.next.prev = cur.prev;
                        cur.prev.next = cur.next;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
//        逻辑就是上面的删除首次出现,只不过就是每次删除了没有直接return掉
        ListNode cur = head;
        while(cur != null){
            if(cur.val == key){
                if(cur == head){//如果是删除第一个节点
                    if(head.next == null){//并且链表就只存在第一个节点
                        head = null;
                    }else{
                        head = head.next;
                        head.prev = null;
                    }
                }else{//删除的不是第一个节点
                    if(cur == last) {//是最后一个节点
                        cur.prev.next = null;
                        last = cur.prev;
                    }else{
                        cur.next.prev = cur.prev;
                        cur.prev.next = cur.next;
                    }
                }
            }
            cur = cur.next;
        }
    }
    //得到单链表的长度
    public int size(){
        ListNode cur = this.head;
        int count = 0;
        while(cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    }
    public void display(){
        ListNode cur = this.head;
        while(cur != null){
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
    public void clear(){
        ListNode cur = head;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = null;
            cur.pre = nnull;
            cur = curNext;
        }
        head = null;
        last = null;
    }

}

【头插图示:】

在这里插入图片描述


【尾插图示:】

在这里插入图片描述


【index位置插入:】

在这里插入图片描述


最后注意一下双向链表的clear()函数不能只单单把head,last置为null就可以了,因为每个节点的pre也算是引用着上一个节点,所以是需要我们遍历然后一个个置为空的,最后再把head与last置为空就完成了整个链表的清空


四,LinkedList的使用

LinkedList实现了List接口,方法众多。

4.1,LinkedList的多元化使用

我们知道,双向链表的使用是十分普遍的,也正因为它的功能的强大,所以对于LinkedList而言,它不仅仅可以作为双向链表使用,还可以作为栈,队列来使用,具体使用场景后面会介绍。

如此,我们可以发现,在LinkedList的源码中,有很多相似的方法。

在这里插入图片描述


由图,我们可以看到,光是插入的方式就多种多样,因为LinkedList作为不同的对象进行使用的时候,方法需要进行区分,尽管都只是对于链表进行操作。


4.2,LinkedList的使用示例

【构造方法】

ArrayList<Integer> arrayList = new ArrayList<>();
LinkedList<Integer> linkedList = new LinkedList<>();//无参构造
LinkedList<Integer> linkedList2 = new LinkedList<>(arrayList);//有参构造
//public LinkedList(Collection<? extends E> c)

有参构造的这种模式在之前的ArrayList里面就介绍过,只要是实现了Collection接口的类,并且类中参数类型只有E和E的子类的类,都可以直接作为构造参数传入LinkedList里面,进行辅助构造。


【其他方法】

class Person{
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        Person tmp = (Person) o;
        if(this.name.equals(((Person) o).name) && this.age == ((Person) o).age){
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return "name:" + this.name + " ; " + "age:"+ this.age;
    }
}
public class TestDemo220707 {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(9);
        arrayList.add(10);
        arrayList.add(20);


        LinkedList<Integer> linkedList = new LinkedList<>();//无参构造
        linkedList.addAll(arrayList);//尾插arraylist中的所有元素
        System.out.println(linkedList);
        List ret = linkedList.subList(0,2);
        System.out.println(ret);//注意你通过ret是可以修改到linkedlist中的内容的

        LinkedList<Person> linkedList1 = new LinkedList<>();
        linkedList1.add(new Person("xiaowang",18));
        linkedList1.add(new Person("xiaoli",19));
        System.out.println(linkedList1);
        linkedList1.remove(new Person("xiaoli",19));//Person类需要重写equals方法
        System.out.println(linkedList1);


    }
}

在这里插入图片描述


上面把几个相对于比较难一点的方法进行了示例,至于还有一些比较常规的操作方法,大家可以自行去查看帮助文档,然后进行实操。


【LinkedList的遍历】

public class TestDemo220707 {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();//无参构造
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);

        //1,直接输出
        System.out.println(linkedList);

        //2,foreach循环
        for (Integer x:linkedList) {
            System.out.print(x + " ");
        }
        System.out.println();

        //3,for循环
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.print(linkedList.get(i) + " ");
        }
        System.out.println();

        //4,迭代器
        Iterator<Integer> it = linkedList.iterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
        System.out.println();

        ListIterator<Integer> it2 = linkedList.listIterator(linkedList.size());//从后往前
        while(it2.hasPrevious()){
            System.out.print(it2.previous() + " ");
        }
        System.out.println();
    }
}

在这里插入图片描述


遍历的方法比较多,这里注重关注一个迭代器的方法, ListIterator it2 = linkedList.listIterator(linkedList.size());当我们传入链表的长度后,我们的迭代器对象会直接来到链表的尾部,开始从后面往前面打印,因为是双向链表,有前驱信息。


五,ArrayList与LinkedList对比

在这里插入图片描述


最后,今天的文章分享比较简单,就到这了,如果大家觉得还不错的话,还请帮忙点点赞咯,十分感谢!🥰🥰🥰
在这里插入图片描述

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习.java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值