链表与LinkedList

一:链表

(1)概念

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

💛(链表属于常见线性表的一种)

(2)组成

由节点组成

💙(链表中节点与节点之间通过存储的地址来操作数据)


链表的存储在逻辑上是连续的,在物理上是不连续的

💜(顺序表在逻辑上和物理上都是连续存储的)

(3)节点

①一个节点有三个域,分别是val、prev、next

💓val:存数据

💓prev存储上一个节点的地址

💓next:存储下一个节点的地址


②一个节点至少有两个域,即val和next

💚(对于双向链表,就必须得带有三个域)


③通过head引用来记录头节点的地址

💚(只要知道了头节点head,接下来每个节点都能知道,可以想象成火车头)


④默认来说,最后一个节点的next无地址,用null代替


⑤默认来说,第一个节点的prev无地址,用null代替


⑥每个节点实际是个对象,带有地址的对象


⑦每个节点都是单独存在的,且地址不连续


(4)链表的分类

1.单向与双向

①单向:只能朝着一个方向,根据next存储的位置前进


②双向:既能通过prev往前一个节点走,也能通过next往后一个节点走

2.带头与不带头

①带头:头节点head不可变,插入数据不能改变头节点的位置

简单理解就是专门有个头节点head放在最开始

⭐head此时就像个“哨兵”

⭐val=1所在的节点才叫做链表的首节点


 ②不带头:头节点head可变,如果插入数据此时头节点就变成了新节点

头节点也是首节点

3.循环与非循环

 ①循环:尾节点的next有地址


 ②非循环:尾节点的next为null

4.总结类型大全

🌟八种类型的链表


1.单向带头循环

2.单向带头非循环

3.单向不带头循环

4.单向不带头非循环(⇦重点常考,大部分题型)


5.双向带头循环

6.双向带头非循环

7.双向不带头循环

8.双向不带头非循环(⇦LinkedList的底层实现)

(5)链表的优缺点

1.优点

①插入数据效率高,直接new对象,然后修改指向即可

②删除数据效率高,只需要修改指向即可

💖(不用像顺序表那样还要移动、覆盖,满了还得扩容)

2.缺点

查找数据效率低,需要遍历链表,极端情况下如果查找的数据为尾节点,要遍历整个链表

💔(无法像顺序表一样通过下标查找)

3.总结

💗链表更适合去插入数据或者删除数据

(至于查找数据和更新数据,更推荐使用ArrayList)

(6)单链表的实现(模拟)

💗模拟实现单向不带头非循环链表

import java.util.Stack;

public class MySingleList {
    //定义一个内部类ListNode,表示一个节点
    static class ListNode {
        public int val;//值
        public ListNode next;//存储下一个节点

        //构造方法只能初始化val值,因为下一个节点地址未知
        public ListNode(int val) {
            this.val = val;
        }
    }


    //定义一个head属性
    //头节点是属于链表属性,而非节点的属性
    //因此将head定义在ListNode内部类外面
    public ListNode head;


    //(1)创建一个链表
    //很low的创建方法,但是能更好的去理解链表
    //学到头插法、尾插法之后就用它们创建链表
    public void createList() {
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);
        ListNode node5 = new ListNode(56);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;

        this.head = node1;
    }



    //(2)遍历单链表
    //无参
    public void show() {
        //这里不是定义了一个节点 这里只是一个引用
        //将cur指向head指向的对象,不改变head,而是交给cur去操作
        //如果使用head操作,等到后面head就为空了,就回不来了
        ListNode cur = head;
        //不可以写成cur.next!=null
        //因为最后一个节点你还得打印它的val值
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }
    //(2)遍历单链表
    //有参,给出指定节点开始打印
    public void show(ListNode newHead) {
        //这里不是定义了一个节点 这里只是一个引用
        //将cur指向head指向的对象,不改变head,而是交给cur去操作
        //如果使用head操作,等到后面head就为空了,就回不来了
        ListNode cur = newHead;
        //不可以写成cur.next!=null
        //因为最后一个节点你还得打印它的val值
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }



    //(3)得到单链表的长度,就是计算链表中节点的个数
    public int size(){
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }



    //(4)查找是否包含关键字key在单链表当中,即找出指定的val值
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            //如果val值是引用类型
            //那么这里得用equals来进行比较!
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }



    //(5)头插法,每次都往最前面插入
    //这个时候打印的话是倒着打印的,因为每次都是头插
    //例如add(1),add(2),add(3),打印就是3 2 1
    //1.需要让新插入的节点其next存储之前的首节点
    //2.然后head指向新插入的节点
    //顺序先1后2,不可乱
    //这个时候即使一个节点都没有也没关系,一样可以创建
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }



    //(6)尾插法,每次都往最后面插入
    //关键在于如何找到最后一个节点,即尾节点
    public void addLast(int data){
        ListNode node = new ListNode(data);
        //这里需要判断如果插入时一个节点都没有的清空
        //此时第一个插入的节点就是我的头节点
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        //注意啊!!这里就是cur.next!=null而不是cur!=null了
        //因为这样才能定位到最后一个节点
        while (cur.next != null) {
            cur = cur.next;
        }
        //此时cur指向的节点就是尾节点
        //用这个尾节点存储新插入的节点即可
        cur.next = node;
    }



    //(7)任意位置插入,第一个数据节点为0号下标
    //插入新节点到index位置,只需要让cur走index-1步(cur是新插入节点的前一个节点)
    //然后新节点的next存储cur的next即原本的下一个节点
    //再让cur的next存储新插入的节点
    public void addIndex(int index,int data){
        int len = size();
        //判断index位置的合法性
        if(index < 0 || index > len) {
            throw new IndexOutOfBounds("任意位置插入数据的时候,index位置不合法: "+index);
        }
        //0位置插入就相当于头插法
        if(index == 0) {
            addFirst(data);
            return;
        }
        //位置刚好是节点个数长度,就尾插法
        if(index == len) {
            addLast(data);
            return;
        }
        //先找到index-1位置的节点
        ListNode cur = findIndex(index);
        //进行插入
        ListNode node = new ListNode(data);
        node.next = cur.next;
        cur.next = node;
    }
    //找到index-1位置的节点
    private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index - 1 != 0) {
            cur = cur.next;
            index--;
        }
        return cur; //此时cur就是index-1位置的节点
    }



    //(8)删除第一次出现关键字为key的节点
    public void remove(int key){
        //判断头节点为空即一个节点都没有的情况
        if(head == null) {
            return;
        }
        //如果删除的刚好是头节点
        if(head.val == key) {
            head = head.next;
            return;
        }
        //得到要删除节点的前一个节点prev
        ListNode prev = searchPrev(key);
        //判断prev是否为null
        if(prev == null) {
            System.out.println("没有这个数据!");
            return;
        }
        //删除
        ListNode del = prev.next;
        prev.next = del.next;
    }
    //找到要删除节点的前一个节点prev
    private ListNode searchPrev(int key) {
        ListNode prev = head;
        while (prev.next != null) {
            if(prev.next.val == key) {
                return prev;
            }else {
                prev = prev.next;
            }
        }
        return null;
    }



    //(9)删除所有值为key的节点(面试题:要求只遍历链表一遍)
    //双指针法:cur表示要删除的节点;prev表示当前节点的前一个节点
    public void removeAllKey(int key){
        if(head == null) {
            return;
        }
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null) {
            if(cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        //删完所有key(除头节点外)
        //万一head.val也是要删除的值
        //把头节点key放最后删除
        if(head.val ==  key) {
            head = head.next;
        }
    }



    //(10)清空链表
    public void clear() {
        //方法一:直接将头节点head设置为空
        //this.head = null;
        //方法二:每个节点都设置为空
        while (head != null) {
            //headNext的作用是记录下一个节点的位置
            //因为当你将一个节点设置为空后,你就找不到下一个节点的位置了
            ListNode headNext = head.next;
            head.next = null;
            head = headNext;
        }
    }
}

🌟重要代码理解

①while(cur.next!=null)与while(cur!=null) 


②cur=cur.next 

二:链表题目

(1)删除链表中所有值为key的节点

💛题目链接:删除链表中所有值为key的节点

💓(要求只遍历链表一遍)


①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


②方法代码:
    //双指针法:cur表示要删除的节点;prev表示当前节点的前一个节点
    public void removeAllKey(int key){
        //判断当前是否一个节点都没有的情况
        if(head == null) {
            return;
        }
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null) {
            if(cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        //删完所有key(除头节点外)
        //万一head.val也是要删除的值
        //把头节点key放最后删除
        if(head.val ==  key) {
            head = head.next;
        }
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleList = new MySingleList();
        mySingleList.addLast(9);
        mySingleList.addLast(56);
        mySingleList.addLast(56);
        mySingleList.addLast(99);
        System.out.println("删除前:");
        mySingleList.show();

        mySingleList.removeAllKey(56);
        System.out.println("删除后:");
        mySingleList.show();
    }
}

(2)反转链表

💛题目链接:反转链表


①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


②方法代码:

    //题目:反转链表
    public ListNode reverseList() {
        //情况:一个节点都没有
        if(head == null) {
            return null;
        }
        //情况:只有一个节点
        if(head.next == null) {
            return head;
        }
        //情况:有节点,真正开始翻转
        //cur代表当前需要翻转的节点
        ListNode cur = head.next;
        head.next = null;
        while(cur != null) {
            //curNext记录当前需要翻转节点的下一个节点
            ListNode curNext = cur.next;
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleList = new MySingleList();
        mySingleList.addLast(12);
        mySingleList.addLast(66);
        mySingleList.addLast(43);
        mySingleList.addLast(77);
        System.out.println("反转前:");
        mySingleList.show();


        System.out.println("反转后:");
        mySingleList.reverseList();
        mySingleList.show();
    }
}

(3)链表的中间节点

💛题目链接:链表的中间节点


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


 ②方法代码:

    //找出中间节点
    public ListNode middleNode(ListNode head) {
        //判断当前没有节点的情况
        if(head == null) {
            return null;
        }
        //判断当前只有一个节点的情况
        if(head.next == null) {
            return head;
        }
        //定义快慢指针
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //slow此时就是中间节点
        return slow;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleList1 = new MySingleList();
        mySingleList1.addLast(12);
        mySingleList1.addLast(66);
        mySingleList1.addLast(43);
        mySingleList1.addLast(77);
        System.out.print("偶数节点情况下的输入:"+" ");
        mySingleList1.show();

        MySingleList.ListNode ret1 = mySingleList1.middleNode(mySingleList1.head);
        System.out.println("偶数节点情况下中间节点:"+ret1.val);
        System.out.print("偶数节点情况下的输出:"+" ");
        mySingleList1.show(ret1);

        System.out.println("");

        MySingleList mySingleList2 = new MySingleList();
        mySingleList2.addLast(12);
        mySingleList2.addLast(23);
        mySingleList2.addLast(34);
        mySingleList2.addLast(45);
        mySingleList2.addLast(77);
        System.out.print("奇数节点情况下的输入:"+" ");
        mySingleList2.show();

        MySingleList.ListNode ret2 = mySingleList2.middleNode(mySingleList2.head);
        System.out.println("奇数节点情况下中间节点:"+ret2.val);
        System.out.print("奇数节点情况下的输出:"+" ");
        mySingleList1.show(ret2);
    }
}

(4)链表中倒数第k个结点

💛题目链接:输出该链表中倒数第k个结点


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


 ②方法代码:

   //找出倒数第K个节点
    public ListNode findKthToTail(int k) {
        //判断k的合法性
        if(k <= 0 || head == null) {
            return  null;
        }
        //定义快慢指针
        ListNode fast = head;
        ListNode slow = head;
        //1、先让fast走k-1步
        for (int i = 0; i < k-1; i++) {
            fast = fast.next;
            if(fast == null) {
                return null;
            }
        }
        //2、同时走 走到fast.next==null的时候停止! 返回slow
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //slow此时就是我要找的倒数第k个节点
        return slow;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleList  = new MySingleList();
        mySingleList.addLast(12);
        mySingleList.addLast(23);
        mySingleList.addLast(34);
        mySingleList.addLast(45);
        mySingleList.addLast(77);
        System.out.print("输入:"+" ");
        mySingleList.show();

        int k =4;
        MySingleList.ListNode ret1 = mySingleList.findKthToTail(k);
        System.out.println("倒数第"+k+"个节点:"+"  "+ret1.val);
        System.out.print("输出:"+" ");
        mySingleList.show(ret1);
    }
}

(5)合并两个有序链表

💛题目链接:合并两个有序链表


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


②方法代码:

    //合并有序链表
    public ListNode mergeTwoLists(ListNode headA , ListNode headB){
        //定义一个newHead和一个tmpH
        ListNode newHead = new ListNode();
        ListNode tmpH = newHead;
        //循环合并过程
        while(headA!=null && headB!=null){
            if(headB.val<headA.val){
                tmpH.next=headB;
                //tmpH指向小的值也可以写成:tmpH=headB;
                tmpH=tmpH.next;
                headB=headB.next;
            }else{
                tmpH.next=headA;
                //tmpH指向小的值也可以写成:tmpH=headA;
                tmpH=tmpH.next;
                headA=headA.next;
            }
        }
        //假设此时headB已经遍历完了,headA没有
        if(headA!=null){
            tmpH.next=headA;
        }
        //假设此时headA已经遍历完了,headB没有
        if (headB!=null){
            tmpH.next=headB;
        }
        //此时就是一个合并且有序的新链表
        return newHead.next;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleListT  = new MySingleList();

        MySingleList mySingleList1  = new MySingleList();
        mySingleList1.addLast(12);
        mySingleList1.addLast(23);
        mySingleList1.addLast(34);
        mySingleList1.addLast(45);
        mySingleList1.addLast(77);
        System.out.print("链表1:"+" ");
        mySingleList1.show();

        MySingleList mySingleList2  = new MySingleList();
        mySingleList2.addLast(9);
        mySingleList2.addLast(15);
        mySingleList2.addLast(25);
        mySingleList2.addLast(30);
        System.out.print("链表2:"+" ");
        mySingleList2.show();

       MySingleList.ListNode ret = mySingleListT.mergeTwoLists(mySingleList1.head,mySingleList2.head);
       mySingleListT.show(ret);
    }
}

(6)链表分割

💛题目链接:链表分割


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)  


②方法代码:

//分割链表
    public ListNode partition( int x) {
        // 定义两个段
        ListNode bs = null;
        ListNode be = null;
        ListNode as = null;
        ListNode ae = null;
        ListNode cur = head;
        while (cur != null) {
            //小于x的情况
            if(cur.val < x) {
                //第一次插入的时候
                if(bs == null) {
                    bs = cur;
                    be = cur;
                }else {
                    be.next = cur;
                    //be往后走一步也可以是:be=be.next
                    be = cur;
                }
            }
            //大于或等于x的情况
            else {
                //第一次插入的时候
                if(as == null) {
                    as = cur;
                    ae = cur;
                }else {
                    ae.next = cur;
                    //ae往后走一步也可以是:ae=ae.next
                    ae = cur;
                }
            }
            cur = cur.next;
        }

        //要考虑: 难道2个段当中 都有数据吗?
        // 第一个段没有数据 直接返回第2个段!!
        // 小于x的段没有数据了,直接返回大于x的段
        if(bs == null) {
            return as;
        }

        //将两个段进行连接
        be.next = as;

        //如果大于x的段有节点,需要将ae.next设置为空
        //因为ae肯定是作为链表的最后一个节点
        if(as != null) {
            ae.next = null;
        }

        //作用一: 大于x的段没有数据了,直接返回小于x的段
        //作用二: 返回已经分好段的链表
        return bs;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {
        MySingleList mySingleListT  = new MySingleList();

        MySingleList mySingleList  = new MySingleList();
        mySingleList.addLast(45);
        mySingleList.addLast(12);
        mySingleList.addLast(34);
        mySingleList.addLast(23);
        mySingleList.addLast(77);
        System.out.print("输入链表:"+" ");
        mySingleList.show();


       MySingleList.ListNode ret = mySingleList.partition(25);
        System.out.print("分割链表:"+" ");
        mySingleList.show(ret);
    }
}

(7)链表的回文结构

💛题目链接:链表的回文结构


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


②方法代码:

   public boolean chkPalindrome() {
        //0.判断一个节点都没有的情况
        if(head == null) return false;

        //1、找中间节点
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //此时slow所指的位置就是中间节点

        //2、开始翻转
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //此时翻转完成

        //3、开始判断是否为回文
        while (head != slow) {
            if(head.val != slow.val) {
                return false;
            }
            //针对偶数节点
            if(head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }

③测试代码:

public class TestMySingleList {
    public static void main(String[] args) {

        MySingleList mySingleList  = new MySingleList();
        mySingleList.addLast(12);
        mySingleList.addLast(23);
        mySingleList.addLast(34);
        mySingleList.addLast(23);
        mySingleList.addLast(12);
        System.out.print("输入链表:"+" ");
        mySingleList.show();


         boolean ret = mySingleList.chkPalindrome();
        System.out.println(ret);
    }
}

(8)两个链表,找出它们第一个公共节点

💛题目链接:输入两个链表,找出它们的第一个公共节点


①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


 ②方法代码:

 public ListNode getIntersectionNode(ListNode headA,ListNode headB) {
        //判断其中有一个链表为空的情况
        if(headA==null && headB!=null){
            return null;
        }
        if(headA!=null && headB==null){
            return null;
        }

        //假设链表A长(假设的不一定就是长的,后面需要作出调整)
        MySingleList.ListNode plong = headA;
        MySingleList.ListNode pshort = headB;

        //1、分别求两个链表的长度
        int len1 = 0;
        int len2 = 0;
        //求链表A的长度
        while (plong != null) {
            len1++;
            plong = plong.next;
        }
        //求链表B的长度
        while (pshort != null) {
            len2++;
            pshort = pshort.next;
        }
        //求完长度再回到首节点
        //(没有这一步,上面求完长度的Plong和Pshort就全都为null了)
        plong = headA;
        pshort = headB;


        //2、求差值步的len
        int len = len1 - len2;
        //假设错误的情况,就会导致len<0,需要作出调整
        if(len < 0) {
            plong = headB;
            pshort = headA;
            len = len2 - len1;
        }
        //此时就保证了plong一定指向最长的链表;pshort一定指向最短的链表;len一定是一个正数


        //3、哪个链表长就先走len步
        while (len != 0) {
            plong = plong.next;
            len--;
        }


        //4、一起走;直到相遇!
        while (plong != pshort) {
            plong = plong.next;
            pshort = pshort.next;
        }
        return plong;
    }

 (9)环形链表I

💛题目链接:环形链表


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


 ②方法代码:

//判断链表是否形成环
    public boolean hasCycle(ListNode head) {
        //判断一个节点都没有的情况
        if(head == null) {
            return false;
        }
        
        //定义快指针fast和慢指针slow
        ListNode fast = head;
        ListNode slow = head;
        
        //有环则相遇
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                return true;
            }
        }
        return false;
    }

(10)环形链表II

💛题目链接:给定一个链表,返回链表开始入环的第一个节点给定一个链表,返回链表开始入环的第一个节点


 ①思路与代码分析:

(图片很长,建议右键图像,然后“标签页打开图像”放大看)


 ②方法代码:

​
    public ListNode detectCycle(ListNode head) {
        if(head == null) return null;
        //定义fast和slow
        ListNode fast = head;
        ListNode slow = head;
        //判环
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                break;
            }
        }
        //说明没有环,直接返回null
        if(fast == null || fast.next == null) {
            return null;
        }
        //注:这里必须要让fast回到头节点,因为在上面判环是fast和slow处于相遇点了
        //我们的目的:fast从头节点出发,slow从相遇点出发,slow就不用管了
        fast = head;
        //fast和slow一次走一步
        while(fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        //跳出循环就代表fast==slow,此时相遇了,就返回slow即可
        return slow;
    }

​

三:LinkedList

 (1)LinkedList的定义

💗LinkedList是一个集合类,实现了List接口


(2)LinkedList的本质

💓LinkedList的底层是双向链表

(3)LinkedList的细节

1.LinkedList实现了List接口


2.LinkedList底层实现了双向链表


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


4.LinkedList的任意位置插入和删除元素时效率较高

(不适合查找和修改,查找和修改最好使用ArrayList)

(4)LinkedList的创建对象

①可调用LinkedList的所有方法

LinkedList<Integer> linkedlist = new LinkedList<>();

②向上转型,List是父类,发生了动态绑定,只能调用子类重写父类List接口的方法,不能调用子类特有的方法

List<Integer> linkedlist = new LinkedList<>();

🌟实例化的时候推荐使用泛型<>;指定一下存放的数据类型,避免数据类型杂乱无章

(5)LinkedList的构造方法

①LinkedList():无参构造方法


②LinkedList(Collection<? extends E> c):利用其他 Collection构建 ArrayList

1.必须是实现了Collection接口的

2.<? extends E>:带有泛型;说明你要传入的数据,它的泛型参数必须是E或E的子类

(与之前发布的ArrayList文章相似,具体看目录四(5):顺序表与ArrayList)

(6)LinkedList的普通方法

(7)LinkedList的四种遍历

1.System输出

System.out.println( )

💛(原因在于LinkedList父类重写了toString方法)


import java.util.LinkedList;
import java.util.List;

public class TestLinkedList {
    public static void main(String[] args) {
        List<String> linkedlist = new LinkedList<>();
        linkedlist.add("hlizoo");
        linkedlist.add("777777");
        System.out.println(linkedlist);
    }
}

2.for-each循环(常用)

①冒号的右边是要遍历的集合/数组


②冒号的左边是遍历集合/数组中的类型+元素


import java.util.LinkedList;
import java.util.List;

public class TestLinkedList {
    public static void main(String[] args) {
        List<String> linkedlist = new LinkedList<>();
        linkedlist.add("I");
        linkedlist.add("like");
        linkedlist.add("java");
        for (String x:linkedlist) {
            System.out.print(x+" ");
        }
    }
}

3.迭代器正向遍历

①通过调用LinkedList集合的iterator或listIterator方法获取迭代器对象


②boolean hasNext():hasNext 方法用于检查是否还有下一个元素


③E next():next 方法用于获取下一个元素的值


import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class TestLinkedList {
    public static void main(String[] args) {
        List<String> linkedlist = new LinkedList<>();
        linkedlist.add("I");
        linkedlist.add("like");
        linkedlist.add("java");

        Iterator<String> iterator = linkedlist.listIterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next()+" ");
        }
        System.out.println();
    }
}

4.迭代器反向遍历

①通过调用LinkedList集合的iterator或listIterator方法获取迭代器对象

(参数就是从指定位置开始打印)


②boolean hasPrevious():hasPrevious方法用于检查是否还有上一个元素


③E previous():previous 方法用于获取上一个元素的值


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

public class TestLinkedList {
    public static void main(String[] args) {
        List<String> linkedlist = new LinkedList<>();
        linkedlist.add("I");
        linkedlist.add("like");
        linkedlist.add("java");

        ListIterator<String> iterator = linkedlist.listIterator(linkedlist.size());
        while(iterator.hasPrevious()){
            System.out.print(iterator.previous()+" ");
        }
        System.out.println();
    }
}

(8)LinkedList的模拟实现

🌟这里实际上也就是双向链表的模拟实现

public class MyLinkedList {
    //一个节点就包括了val、prev、next
    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 int size(){
        int len = 0;
        ListNode cur = head;
        while (cur != null) {
            cur = cur.next;
            len++;
        }
        return len;
    }


    //打印双向链表(跟单向代码一样,与是否双向无关)
    public void display(){
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }


    //查找是否包含关键字key是否在链表当中(跟单向代码一样,与是否双向无关)
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }


    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
            return;
        }
        node.next = head;
        head.prev = node;
        head = node;
    }


    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
            return;
        }
        last.next = node;
        node.prev = last;
        last = node;    //或者last = last.next;
    }


    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        //求链表长度
        int size = size();
        //判断index下标合法性
        if(index < 0 || index > size) {
            throw new IndexOutOfBounds("双向链表index不合法!");
        }
        //index为0,头插法
        if(index == 0) {
            addFirst(data);
            return;
        }
        //index为链表长度,尾插法
        if(index == size) {
            addLast(data);
            return;
        }
        //给一个cur,目的是来到插入下标的位置
        //(与单链表不同,这里双链表需要让cur走到index位置,而单链表的cur只需要走index-1步)
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        //正式插入
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }


    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                //开始删
                if(cur == head) {
                    //如果要删除的是头节点
                    head = head.next;
                    //只有1个节点的时候走else
                    if(head != null) {
                        head.prev = null;
                    }else {
                        last = null;
                    }
                }else {
                    //删除尾节点
                    //是尾节点走else
                    cur.prev.next = cur.next;
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        last = last.prev;
                    }
                }
                return;
            }else {
                cur = cur.next;
            }
        }
    }


    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                //开始删
                if (cur == head) {
                    //如果要删除的是头节点
                    head = head.next;
                    //只有1个节点的时候走else
                    if (head != null) {
                        head.prev = null;
                    }else {
                        last = null;
                    }
                } else {
                    //删除尾节点
                    //是尾节点走else
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        //cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    } else {
                        //cur.prev.next = cur.next;
                        last = last.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }


    public void clear(){
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            //cur.val = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }
}

(9)LinkedList与ArrayList的区别

1.存储空间

①ArrayList:底层是数组,物理上和逻辑上存储均连续

②LinkedList:底层是双向链表,逻辑上存储连续,物理上存储不连续


2.插入

①ArrayList:需要移动元素才可插入,效率低,时间复杂度O(N)

②LinkedList:只需修改指向即可插入,效率高,时间复杂度O(1)


3.查询

①ArrayList:通过下标直接查询,效率高

②LinkedList:只能通过遍历,效率低


4.插入空间不够

①ArrayList:空间不够需要扩容

②LinkedList:没有容量大小之说


5.随机访问

①ArrayList:可以

②LinkedList:不可以


6.应用场景

①ArrayList:查改

②LinkedList:增删

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值