数据结构之链表 - 超详细的教程,手把手教你认识并运用链表

这篇具有很好参考价值的文章主要介绍了数据结构之链表 - 超详细的教程,手把手教你认识并运用链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. ArrayList的缺陷

顺序表只适合静态的查找和更新,不适合插入和删除元素,

因为在ArrayList中插入和删除元素时,由于需要将后序元素往前后者往后移动,所以时间复杂度会相当高,能达到O(N)。

为了解决这一问题,java 引入了 LinkedList(链表)。

2. 链表

2.1 链表的概念以及结构

链表是一种逻辑上连续,物理上不连续的存储结构。

链表是由一个个节点连接构成的,一个节点包含 val 域和 next 域。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

逻辑上连续是因为链表有一个 next 域,这个 next 域会指向下一个节点。

每个节点都是一个对象,因此他们都会有属于自己的地址。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

上图就是一个不带头单向非循环的链表。

其实,链表的结构有很多种。

1. 带头和不带头

此处的带头和不带头指的是,是否有一个虚拟的头节点,该头节点的 val 是无效值,该头节点的下一个节点才是链表的第一个节点,head 会固定指向该虚拟的头节点,不能修改 head 的指向。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

2. 单向和双向

单向和双向,顾名思义,就是只能指向一个方向的和能够指向两个方向的

单向的链表只有两个域(val + next),而双向的链表则比单向的链表多一个域 prev

单向的链表只能向后(next)走,而双向的链表既能向后(next)走,也能向前(prev)走

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

3. 循环和非循环

顾名思义,就是循环的链表和不是循环的链表咯

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

虽然链表的种类有这么多,但是我们只用重点掌握两种:单向不带头非循环和双向不带头非循环。

单向不带头非循环:结构简单,面试题考的多。

双向不带头非循环:LinkedList 的底层实现结构就是双向不带头非循环链表。

2.2 链表的实现

此处链表的实现,实现的是单向不带头非循环链表。

要实现一个链表,我们就得先创建一个 MySingleList 类,我们要实现的就是 MySingleList 类。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

由刚刚学过的知识可以知道,链表是由一个个节点组成的,节点是链表的一部分

所以我们还需要定义一个静态内部类 ListNode 作为节点。

一个节点 = val + next ,所以 ListNode 有两个成员变量,

我们顺便再把 ListNode 的构造方法写出来。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

而链表本身自带一个 head 节点指向头节点,所以我们还需要定义一个成员变量 head

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

 因为没有一个完整的链表,所以我们可以写一个方法,手动创建一个链表,这样方便我们测试这个链表方法写对没。
数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

要实现链表,则需要实现以下方法

    //头插法
    public void addFirst(int data) {
    }

    //尾插法
    public void addLast(int data) {
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        return false;
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
    }

    //得到单链表的长度
    public int size() {
        return -1;
    }

    //清空链表
    public void clear() {
    }

    //打印链表
    public void display() {
    }

我们可以先挑选简单的实现,可以选择先实现 size,display,contains 和 clear

那我们就先来实现 size 方法,求链表的长度。

思路就是遍历链表遇到一个节点,count就++

那我们就可以定义一个 cur ,cur 指向 head,然后遍历即可

不能直接用 head 来遍历链表,因为如果 head 走了,链表就不知道从哪里开始了。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

然后我们再来实现 display ,打印链表

 同理,我们要把链表的每一个数字都打印出来,就需要遍历链表,

遍历到一个节点,就打印一个节点。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 contains 方法,查找 key 是否在链表中存在

这个也一样,遍历链表,找到了就返回 true , 遍历完链表了还找不到,那就返回 false

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 clear,这不简单,只要将头节点置 null 了,链表也就清空了 

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 addFirst ,头插法

头插法的意思就是,新增一个节点,将该节点插入到链表的第一个,也就是头节点的位置。

分两种情况,第一种,链表为空,第二种,链表不为空。

如果链表为空,说明链表里面没有节点,那新增的这个 node 节点就是链表的第一个节点,也就是头节点。

如果链表不为空,那我们需要牢记这一点:插入节点的时候,先绑定后面的数据,再来插入节点

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java 如上图解

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 addLast,尾插法,与头插法类似

尾插法,就是把新增的节点插入到链表的尾巴,也就是链表最后一个节点的位置 。

同理,尾插法也分为两种情况,

第一种,链表为空,说明链表里面一个节点都没有,所以新增的节点,就相当于链表的第一个节点,也就是头节点。

第二种,链表不为空,新增的节点要插入到链表的尾巴,则需要先找到链表的尾巴节点,再来插入。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

由上图,我们不难发现,链表尾巴节点和其他节点的区别:那就是尾巴节点的 next 域 为 null

所以,我们就能借助这点,找到链表的尾巴节点。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java 数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来写 addIndex指定位置插入 

因为 addIndex 方法含有参数 index,所以根据 index ,分为四种情况

第一种,非法情况,如果下标小于0或者大于链表的长度,则不合法,不合法就不用插了。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

第二种,下标刚好是0,说明是头插法,直接调用我们刚刚写好的 addFirst 即可。

第三种,下标刚好是链表长度,说明是插在最后一个位置,尾插法,直接调用 addLast 即可。

第四种,index 不是以上三种情况,说明 index 位置是在链表中间位置。

则需要我们先找到下标位置的前一个节点,然后绑定数据,再来插入

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java 数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

然后我们再来实现 remove 方法删除第一个关键字为 key 的节点

那很简单,首先要找到关键字为 key 的节点,再来删除。

如果链表为 null ,说明没有节点给你删除

如果 head.val == key,说明删除头节点

因为要删除 key 的节点,所以得先找到 关键字为 key 的节点的前一个节点,才能进行删除

所以我们可以单独写一个方法,来找,关键字为 key 的节点的前一个节点。

遍历链表,找 key 的节点 的前一个,找到了就返回该节点,找不到就返回 null 

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

找到了 prev(key节点的前一个节点)之后,就直接删除 prev.next 即可

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

 再来实现 removeAllKey 方法,删除所有值为 key 的节点数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

public class MySingleList {

    static class ListNode {
        //一个节点包含 val 和 next
        public int val;//节点的值域
        public ListNode next;//下一个节点的地址


        //提供构造方法
        public ListNode(int val) {
            this.val = val;
        }
    }

    //对于链表本身来说,必须得有一个头结点
    public ListNode head;//指向当前链表的头节点

    public void createList() {
        //创建链表,让每个节点连起来即可
        ListNode n1 = new ListNode(12);
        ListNode n2 = new ListNode(23);
        ListNode n3 = new ListNode(34);
        ListNode n4 = new ListNode(45);
        ListNode n5 = new ListNode(56);

        //通过 next 建立节点之间的联系
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;

        this.head = n1;
    }

    //得到单链表的长度
    public int size() {
        int count = 0;

        //用 cur 来遍历链表
        ListNode cur = head;

        //cur == null说明链表遍历完了
        while (cur != null) {
            count++;
            cur = cur.next;//让 cur 指向下一个节点
        }

        return count;
    }

    //打印链表
    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) {
            //遍历链表,找到了就返回 true
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        //找不到就返回 false
        return false;
    }

    //清空链表
    public void clear() {
        this.head = null;//头节点为null,就找不到其他结点了
    }


    //头插法
    public void addFirst(int data) {
        ListNode node = new ListNode(data);

        if (head == null) {
            head = node;
            return;
        }
        //绑定后面的数据
        node.next = head;
        //更新头
        head = node;
    }

    //尾插法
    public void addLast(int data) {

        ListNode node = new ListNode(data);
        //如果 head 为 null ,说明链表没有结点
        if (head == null) {
            head = node;
            return;
        }

        ListNode cur = head;
        //找到链表的尾巴节点
        while (cur.next != null) {
            cur = cur.next;
        }
        //到了这里说明 cur 指向了尾巴节点
        //插入
        cur.next = node;

    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        // index 不合法,则不能插入
        if (index < 0 || index > size()) {
            return;
        }
        if (index == 0) {
            //头插法
            addFirst(data);
            return;
        }
        if (index == size()) {
            //尾插法
            addLast(data);
            return;
        }
        //中间位置
        ListNode cur = head;
        //走 index - 1 步,找到要删除结点位置的前一个结点
        int step = index - 1;
        while (step != 0) {
            cur = cur.next;
            step--;
        }
        //先绑定后面的数据
        ListNode node = new ListNode(data);
        node.next = cur.next;
        //再来插入
        cur.next = node;
    }


    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        //空表删不了
        if (head == null) {
            return;
        }
        //如果是头节点
        if (head.val == key) {
            head = head.next;
            return;
        }
        //找到 key 节点的前一个节点
        ListNode prev = findKeySubOne(key);
        if (prev == null) {
            //说明不存在 key 节点的前一个节点
            return;
        } else {
            ListNode target = prev.next;// target 为要删除的节点
            //删除
            prev.next = target.next;
        }

    }

    private ListNode findKeySubOne(int key) {
        //空表
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        //找到要删除的前一个结点
        //必须得判断 cur.next 是否为空,不然有可能会空指针异常
        //如果 cur.next == null,那么 cur 已经是尾巴结点,就没有继续判断的必要了
        while (cur.next != null) {
            if (cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
        //空表
        if (head == null) {
            return;
        }

        ListNode prev = head;// prev 为 del 的前一个
        ListNode del = head.next;// del 表示要删除的元素
        // del 不为空,说明还有,有可能要删除的节点
        while (del != null) {
            if (del.val == key) {
                //删除
                prev.next = del.next;
                //del往后走
                del = del.next;
            } else {
                //两个都往后走
                prev = del;
                del = del.next;
            }
        }
        //最后再来判断头节点
        if (head.val == key) {
            head = head.next;
        }
    }

}

 3. 链表练习题

203. 移除链表元素

 数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

刚刚写过删除所有值为 key 的节点,直接复制代码到 LeetCode 上,改下方法名字即可。

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        ListNode prev = head;
        ListNode del = head.next;
        while (del != null) {
            if (del.val == val) {
                prev.next = del.next;
                del = del.next;
            } else {
                prev = del;
                del = del.next;
            }
        }
        if (head.val == val) {
            head = head.next;
        }
        return head;
    }
}

206. 反转链表

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

这道题,思路就是头插法,从第二个节点开始遍历链表,对链表的每个元素进行头插法即可。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head.next;//从第二个节点开始反转
        ListNode prev = head;// prev 相当于新头
        head.next = null;//置空,防止循环
        while (cur != null) {
            ListNode curNext = cur.next;
            //头插法
            cur.next = prev;//头插
            prev = cur;//更新

            cur = curNext;//cur继续往后走
        }
        return prev;
    }
}

  

876. 链表的中间结点

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:快慢指针,相同时间,速度两倍,路程两倍

class Solution {
    //找中间结点
    public ListNode middleNode(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        //遍历链表,防止空指针异常
        while (fast != null && fast.next != null) {
            fast = fast.next.next;//一次走两步
            slow = slow.next;//一次走一步
        }
        return slow;
    }
}

链表中倒数第k个结点

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

 思路:还是利用快慢指针,先让 fast 走 k-1步,再让 fast 和 slow 一起走,等 fast 走到尾巴节点, slow 就是倒数第 k 个节点

public class Solution {
    public ListNode FindKthToTail(ListNode head, int k) {
        // k 不合法或者链表为空
        if (head == null || k <= 0) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (k - 1 != 0) {
            fast = fast.next;
            if (fast == null) {
                //说明k比链表长度还大
                return null;
            }
            k--;
        }
        //你先走,然后我们同时同速走,距离不变,就是k-1步,差k个节点
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

​​​​​​21. 合并两个有序链表

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:与合并2个有序数组类似,定义一个dummy虚拟头节点,如果链表1的节点比链表2的节点小,那就链表1的节点加进来,如果不是,那就链表2的节点加进来,然后对应的链表往后走,遍历完之后,看看哪个链表不为空,不为空的链表就连接在后面,最后返回 dummy.next 即可。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null && list2 == null) {
            return null;
        }
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if (list1 != null) {
            cur.next = list1;
        }
        if (list2 != null) {
            cur.next = list2;
        }

        return dummy.next;
    }
}

CM11 链表分割

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:与合并有序链表类似,但又不完全类似,定义bs,be,as,ae,两个部分

小于x放到 b 中,大于的放到 a 中,最后让 be 和 as 连接即可

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        //定义 bs,be,as,ae
        // 小于 x 放到 b ,大于 x 放到 a 中
        //最后连接 be 和 as
        ListNode bs = null;
        ListNode be = null;
        ListNode as = null;
        ListNode ae = null;

        ListNode cur = pHead;
        //遍历链表
        while (cur != null) {
            //说明是第一次放元素
            if (cur.val < x) {
                if (bs == null) {
                    bs = cur;
                    be = cur;
                } else {
                    be.next = cur;
                    be = cur;
                }

            } else {
                //第一次
                if (as == null) {
                    as = cur;
                    ae = cur;
                } else {
                    ae.next = cur;
                    ae = cur;
                }
            }
            cur = cur.next;
        }
        //如果第一段为空
        if (bs == null) {
            return as;
        }
        //如果a段存在,ae.next需要置空,防止形成循环
        if (as != null) {
            ae.next = null;
        }
        //连接a段和b段
        be.next = as;
        return bs;
    }
}

OR36 链表的回文结构

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:1. 找到链表的中间节点 。 2. 反转中间节点之后的节点。 3. 比较

public class PalindromeList {
    public boolean chkPalindrome(ListNode head) {
        if (head == null) {
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        //1. 找到中间节点
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //2. 反转中间节点之后的节点
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curNext = cur.next;//记录cur的下一个节点

            cur.next = slow;//反转
            slow = cur;//更新头

            cur = curNext;//cur往后走
        }
        //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;
    }
}

160. 相交链表

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:

1.相交主要是Y字型

2.两个链表的长度不一样 主要体现在相交之前

3.可以让最长的链表先走它们的差值步,然后两个链表再一起走

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null && headB == null) {
            return null;
        }
        //1.分别求出两个链表的长度,然后做差
        ListNode pL = headA;//假设A是较长的
        ListNode pS = headB;
        int lenA = 0;
        int lenB = 0;
        while (pL != null) {
            lenA++;
            pL = pL.next;
        }
        while (pS != null) {
            lenB++;
            pS = pS.next;
        }
        int len = lenA - lenB;//记录差值
        pL = headA;//重新回到头结点
        pS = headB;
        //进行修正
        if (len < 0) {
            pL = headB;
            pS = headA;
            len = lenB - lenA;
        }
        //2.让较长的链表先走差值步
        while (len != 0) {
            pL = pL.next;
            len--;
        }
        //3.然后两个链表再一起走
        while (pL != null && pS != null) {
            if (pL == pS) {
                return pL;
            }
            pL = pL.next;
            pS = pS.next;
        }
        return null;
    }
}

141. 环形链表

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:利用快慢指针,fast 一次走2步,slow 一次走一步,如果有环,那么能追上,没环就肯定追不上

public class Solution {
    //是否有环
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;//一次走两步
            slow = slow.next;//一次走一步
            //相当于追及问题,slow在追fast,有环就能追上,没环就追不上
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

142. 环形链表 II

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

思路:快慢指针,当 fast 与 slow 相遇时,让 fast 从 head 开始,和 slow 同速一起走,当 slow

 和 fast 再次相遇时,该相遇点就是环的入口点

public class Solution {
    //返回环的入口点
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                //相遇后,
                //相遇点到入口点的距离与起始点到入口点的距离相等
                fast = head;
                while (fast != null && slow != null) {
                    if (fast == slow) {
                        return fast;//如果fast与slow再次相遇,则该点为入口点
                    }
                    fast = fast.next;
                    slow = slow.next;

                }
            }
        }
        return null;
    }
}

 4. LinkedList的模拟实现

和单链表同理,只是双向无头非循环链表多了一个 prev 域

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

与单链表相同,双向链表也由一个个节点组成,所以同理,我们要定义一个静态内部类,相比于单链表,双链表还多了一个尾巴节点,尾巴节点 last 会指向双链表的最后一个节点

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

双向链表与单链表相同,也要实现以下方法

    //头插法
    public void addFirst(int data) {
    }

    //尾插法
    public void addLast(int data) {
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
    }

    //查找是否包含关键字key是否在链表当中
    public boolean contains(int key) {
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
    }

    //得到链表的长度
    public int size() {
    }

    public void display() {
    }

    public void clear() {
    }

同样的,我们先选择实现 size,display,contains 方法

这些方法的实现与单链表没什么区别,直接按照你写单链表的方式来写上述三个方法即可。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 clear 方法,需要注意,每一个节点的 prev 和 next 都要手动置空,last 和 head 也要置空

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现头插法,与单链表类似,分两种情况

第一种,链表为空,说明新增的节点既是头节点又是尾巴节点

第二种,链表不为空,则先需要绑定后面的数据,再来插入

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现尾插法,同样与单链表类似,分两种情况

第一种,链表为空,相当于头插

第二种,链表不为空,先绑定数据,再来插入

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 addIndex,与单链表类似,分四种情况

第一种,index 不合法,不能插入

第二种,index = 0,相当于头插法

第三种,index = size() ,相当于尾插法

第四种,不是以上三种情况,首先先找到 index 下标的节点,先绑定数据,再进行插入

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

第四种情况与单链表类似,先让 cur 走 index 步,然后绑定数据,进行插入。

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

然后我们再来实现 remove 方法,

思路:遍历链表,如果要删除的是头节点,就需要考虑该链表是否只有一个节点,

如果要删除的不是头节点,那就得判断要删除的节点是尾巴节点,还是中间节点

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

再来实现 removeAllKey 方法,思路与 remove 方法类似,只不过 remove 方法是删一次就走人了,而 removeAllKey 方法,是要删除多次

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

// 无头双向链表实现
public 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 int size() {
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

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

    //查找是否包含关键字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 clear() {
        ListNode cur = head;
        //要将链表的每一个元素手动置空
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        this.head = null;
        this.last = null;
    }


    //头插法
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            //先绑定数据
            node.next = head;
            head.prev = node;
            //再来插入
            head = node;///更新头
        }
    }

    //尾插法
    public void addLast(int data) {
        if (head == null) {
            addFirst(data);
        } else {
            ListNode node = new ListNode(data);
            //先绑定数据
            node.prev = last;
            last.next = node;
            //再进行插入
            last = node;
        }
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            return;
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        ListNode node = new ListNode(data);

        ListNode prev = cur.prev;
        //先绑定数据
        node.prev = prev;
        node.next = cur;
        //再来插入
        prev.next = node;
        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;
                    //考虑只有一个节点的情况
                    if (head == null) {
                        last = null;
                    } else {
                        head.prev = null;
                    }
                } else {
                    //是否是尾巴节点
                    if (cur.next == null) {
                        //删除尾巴节点
                        last = last.prev;
                        last.next = null;
                    } else {
                        //删除中间节点
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }

                }
                //删完之后就走人
                return;
            }
            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;
                    //考虑只有一个节点的情况
                    if (head == null) {
                        last = null;
                    } else {
                        head.prev = null;
                    }
                } else {
                    //是否是尾巴节点
                    if (cur.next == null) {
                        //删除尾巴节点
                        last = last.prev;
                        last.next = null;
                    } else {
                        //删除中间节点
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
                cur = cur.next;//继续删
            } else {
                cur = cur.next;
            }
        }
    }

}

 5. LinkedList的使用

LinkedList 的底层结构是双向链表,由于任意位置插入和删除时不需要挪动元素,所以效率较高。
在集合框架中, LinkedList 也实现了 List 接口。
1. LinkedList 实现了 List 接口
2. LinkedList 的底层使用了双向链表
3. LinkedList 没有实现 RandomAccess 接口,因此 LinkedList 不支持随机访问
4. LinkedList 的任意位置插入和删除元素时效率比较高,时间复杂度为 O(1)
5. LinkedList 比较适合任意位置插入的场景

5.1 LinkedList的构造

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

5.2 LinkedList的其他常用方法介绍

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java

刷题的时候直接用就行

5.3 LinkedList的遍历

可以使用 foreach 循环遍历,或者使用迭代器遍历

    public static void main(String[] args) {
        List<Integer> list = new LinkedList<>();
        list.add(1);
        // 1.使用 foreach 遍历
        for (int x : list) {
            System.out.print(x + " ");
        }
        // 2.使用迭代器遍历
        Iterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }

6. ArrayList和LinkedList的区别

数据结构之链表 - 超详细的教程,手把手教你认识并运用链表,数据结构,数据结构,链表,算法,java文章来源地址https://www.toymoban.com/news/detail-696437.html

到了这里,关于数据结构之链表 - 超详细的教程,手把手教你认识并运用链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】线性表之链表

    上一篇文章讲述了线性表中的顺序表,这篇文章讲述关于链表的定义、类别、实现、多种不同链表的优缺点和链表与顺序表的优缺点。 关于上一篇文章的链接:线性表之顺序表 链表是一种物理存储结构上 非连续、非顺序 的存储结构,数据元素的逻辑顺序是通过链表中的指针

    2024年02月05日
    浏览(32)
  • C语言数据结构之链表

    在上一篇博客中我们提到,线性表包括顺序表和链表,顺序表在上篇博客中已经介绍,本篇博客介绍一下另一种线性表—— 链表 。 概念:链表是⼀种 物理存储结构上⾮连续、⾮顺序 的存储结构,数据元素的 逻辑顺序是通过链表中的指针链接次序实现的 。 链表的结构跟⽕

    2024年04月22日
    浏览(21)
  • C++数据结构之链表(详解)

    主要参考文章地址 01.链表基础知识 | 算法通关手册 (itcharge.cn)) 本次内容是对链表的总结,可以看了上面的文章之后。 在看我下面的内容,做一个简短的复习,且本内容的代码均用C++实现,而参考资料的代码则为python。 每一个标题都有一个完整的链接,也可以点击下面的链

    2024年02月15日
    浏览(35)
  • C语言进阶——数据结构之链表(续)

    hello,大家好呀,我是Humble,本篇博客承接之前的 C语言进阶——数据结构之链表 的内容 (没看过的小伙伴可以从我创建的专栏C语言进阶之数据结构 找到那篇文章并阅读后在回来哦~) ,上次我们重点说了链表中的 单链表 ,即 不带头单向不循环链表 还说到了链表的分类虽

    2024年01月25日
    浏览(22)
  • 【023】C/C++数据结构之链表及其实战应用

    💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。 👉 🎖️ CSDN实力新星,社区专家博主 👉 🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、

    2024年02月08日
    浏览(22)
  • C/C++数据结构之链表题目答案与解析

    个人主页:点我进入主页 专栏分类:C语言初阶      C语言程序设计————KTV       C语言小游戏     C语言进阶 C语言刷题       数据结构初阶 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂。 目录 1.前言  2.题目解析 2.1 移除链表元素 2.2反转链表 2.3链表的中

    2024年02月05日
    浏览(21)
  • 数据结构之栈和队列 - 超详细的教程,手把手教你认识并运用栈和队列

    栈:后进先出 队列:先进先出 栈:是一种特殊的 线性表 , 只允许在固定的一端插入或者删除元素 ,一个栈包含了栈顶和栈底。只能在栈顶插入或者删除元素。 栈的底层 是由 数组 实现的。 栈遵循先入后出原则,也就是先插入的元素得到后面才能删除,后面插入的元素比

    2024年02月07日
    浏览(24)
  • 【图解数据结构】顺序表实战指南:手把手教你详细实现(超详细解析)

    🌈个人主页: 聆风吟 🔥系列专栏: 图解数据结构、算法模板 🔖少年有梦不应止于心动,更要付诸行动。 线性表(linear list):线性表是一种数据结构,由n个具有相同数据类型的元素构成一个有限序列。 线性表可以用数组、链表、栈等方式实现,常见的线性表有数组、链

    2024年01月22日
    浏览(31)
  • 【数据结构】—手把手带你用C语言实现栈和队列(超详细!)

                                       食用指南:本文在有C基础的情况下食用更佳                                     🔥 这就不得不推荐此专栏了:C语言                                   ♈️ 今日夜电波:Tell me —milet                    

    2024年02月14日
    浏览(25)
  • 【数据结构篇】手写双向链表、单向链表(超详细)

    什么是链表 ? 链表(Linked List)是用链式存储结构实现的线性表。链表示意图: 链表的组成 : 数据域 + 引用域 (数据域和引用域合称结点或元素) 数据域存放数据元素自身的数据 引用域存放相邻结点的地址 链表的特点 : 链表中元素的联系依靠引用域 具有线性结构的特

    2024年02月11日
    浏览(20)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包