【详解LinkedList与链表】

这篇具有很好参考价值的文章主要介绍了【详解LinkedList与链表】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🌠作者:@TheMythWS.

🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。

【详解LinkedList与链表】

目录

链表

概念

图解链表

链表的实现 

1.创建链表

2.遍历链表 

3.查找是否包含关键字key是否在单链表当中 

4.获取单链表的长度 

5.头插法 

6.尾插法 

7.任意位置插入(假如第一个数据结点为0下标) 

8.删除第一次出现关键字key的结点 

9.清空链表 

10.删除所有值为key的结点 

※链表OJ面试题   

LinkedList的模拟实现 

头插法: 

尾插法: 

任意位置插入元素: 

删除第一次出现关键字key的结点: 

删除所有关键字key的结点: 

清空双链表: 

LinkedList的使用

什么是LinkedList?

LinkedList的使用 

1.LinkedList的构造  

2. LinkedList的其他常用方法介绍   

3. LinkedList的遍历 

ArrayList和LinkedList的区别   

 文章来源地址https://www.toymoban.com/news/detail-463988.html


 

链表

概念

        是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的,也就是线性表用链式存储结构形成的表。
特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示(指针)其直接后继(即直接后继的存储位置)这两部分信息组成数据元素ai的存储映像,称为节点/结点(node),它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继位置的域称为指针域。指针域存储的信息称作指针或链。n个结点(ai(1≤i≤n)的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构。如果此链表的每个结点中只包含一个指针域,故又称此链表为线性链表或单链表

        根据链表结点所含指针个数、指针指向和指针链接方式,可以将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等等。
        其中单链表、循环链表、双向链表用于实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构。
        类似我们生活中的火车,一节一节相连的。        

【详解LinkedList与链表】

下面用单链表作为例子: 

【详解LinkedList与链表】

图解链表

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
分为单向和双向,其次是带头和不带头、循环和非循环 

【详解LinkedList与链表】

所以排列组合有2^3=8种,如下图所示:

【详解LinkedList与链表】

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

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

注意:
单向就是一个方向一直走。
双向就是两个方向都走。 

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

链表的实现 

不带头 非循环 单链表作为例子,下面我们先要明白链表和结点是怎么用代码实现的:

【详解LinkedList与链表】

1.创建链表

public class MySingleLinkedList {
    static class Node {//加static说明创建这个结点的时候不依赖外部对象
        public int val;//存储的数据
        public Node next;//存储下一个结点的地址
        /*
            给val构造方法,因为如果不设置(相当于没有这个结点)那么我们不知道结点的next域要存什么地址,
            为什么不给next赋值呢? 因为next是一个引用类型,默认是null,这就是结点类
             */
        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;//代表当前链表的头结点的引用
    //创建结点
    public void create() {
        Node node1 = new Node(10);
        Node node2 = new Node(12);
        Node node3 = new Node(11);
        Node node4 = new Node(9);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }
}
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
    }
}

通过断点调试查看链表是否创建成功: 

【详解LinkedList与链表】

2.遍历链表 

/*
    遍历链表
    注意点1:
    如果是遍历完成整个链表,那么head == null
    如果是遍历到链表的最后一个结点(尾结点),那么head.next == null
     */
    public void display() {
        Node cur = head;//注意点2:让head引用指向头结点,cur来临时指向每个结点,如果不设置的话,那么head = null,连续打印两次链表,只能显示第一次打印的链表
        while (cur != null) {//如果判断条件是head.next != null,因为无法进入循环则不能打印最后结点的val
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

3.查找是否包含关键字key是否在单链表当中 

//查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        Node cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
    }
}

【详解LinkedList与链表】

4.获取单链表的长度 

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

【详解LinkedList与链表】

5.头插法 

【详解LinkedList与链表】

【详解LinkedList与链表】

//头插法
    public void addFirst(int data) {
        Node node = new Node(data);//1.插入结点,必须先得有结点
        node.next = head;//2.先连
        head = node;//3.后断
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

6.尾插法 

【详解LinkedList与链表】

//尾插法 O(N)
    public void addLast(int data) {
        Node node = new Node(data);
        if (head == null) {//该链表是否是第一次插入数据
            head = node;
            return;
        }
        Node cur = head;
        while (cur.next != null) {//cur指向最后一个结点(尾结点)
            cur = cur.next;
        }
        cur.next = node;
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

7.任意位置插入(假如第一个数据结点为0下标) 

【详解LinkedList与链表】

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) throws ListIndexOutOfException{
        checkIndex(index);
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = findIndexSubOne2(index);
        ListNode listNode = new ListNode(data);
        listNode.next = cur.next;
        cur.next = listNode;
    }

    /**
     * 找到 index-1位置的节点的地址
     * @param index
     * @return
     */
    private ListNode findIndexSubOne(int index) {
        ListNode cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    private ListNode findIndexSubOne2(int index) {
        ListNode cur = head;
        while (index - 1 != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    private void checkIndex(int index) throws ListIndexOutOfException{
        if(index < 0 || index > size()) {
            throw new ListIndexOutOfException("index位置不合法");
        }
    }
public class LinekdListIndexOutOfException extends RuntimeException {
    public LinekdListIndexOutOfException() {
    }
    public LinekdListIndexOutOfException(String message) {
        super(message);
    }
}
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
        System.out.println("《---测试插入---》");
        mySingleLinkedList.addIndex(3, 11);
        mySingleLinkedList.addIndex(0, 7);
        mySingleLinkedList.addIndex(10, 44);
        try {
            mySingleLinkedList.addIndex(12, 44);
        } catch (LinekdListIndexOutOfException e) {
            e.printStackTrace();
        }
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

8.删除第一次出现关键字key的结点 

【详解LinkedList与链表】

//删除第一次出现关键字为key的结点
    public void remove(int key) {
        if (head == null) {
            return;//一个结点也没有
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        Node cur = searchPrev(key);//key的前驱结点
        if (cur == null) {//没有找到key是要被删除的
            return;
        }
        Node del = cur.next;//要删除的结点
        cur.next = del.next;
        //cur.next = cur.next.next;
    }
    //找到关键字key的前一个结点
    private Node searchPrev(int key) {
        Node cur = head;
        while (cur.next != null) {//cur.next == null,它没有下一个结点也就不可能是要找的key结点的前驱,这样就说明了一直没有找到要删除的结点
            if (cur.next.val == key) {//如果cur指向的下一个结点的值和key相等,就返回当前cur,当前的cur指向的结点就是key的前一个结点(前驱结点)
                return cur;
            }
            cur = cur.next;
        }
        return null;//没有要删除的结点
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");0
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
        System.out.println("《---测试插入---》");
        mySingleLinkedList.addIndex(3, 11);
        mySingleLinkedList.addIndex(0, 7);
        mySingleLinkedList.addIndex(10, 44);
        /*try {
            mySingleLinkedList.addIndex(12, 44);
        } catch (LinekdListIndexOutOfException e) {
            e.printStackTrace();
        }*/
        mySingleLinkedList.display();
        System.out.println("《---测试删除---》");
        mySingleLinkedList.remove(7);
        mySingleLinkedList.remove(8);
        mySingleLinkedList.remove(9);
        mySingleLinkedList.remove(11);
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

9.清空链表 

//清空链表
    public void clear() {
        head = null;
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
        System.out.println("《---测试插入---》");
        mySingleLinkedList.addIndex(3, 11);
        mySingleLinkedList.addIndex(0, 7);
        mySingleLinkedList.addIndex(10, 44);
        /*try {
            mySingleLinkedList.addIndex(12, 44);
        } catch (LinekdListIndexOutOfException e) {
            e.printStackTrace();
        }*/
        mySingleLinkedList.display();
        System.out.println("《---测试删除---》");
        mySingleLinkedList.remove(7);
        mySingleLinkedList.remove(8);
        mySingleLinkedList.remove(9);
        mySingleLinkedList.remove(11);
        mySingleLinkedList.display();
        System.out.println("《---清空链表---》");
        mySingleLinkedList.clear();
        mySingleLinkedList.display();
        System.out.println("《---更新数据---》");
        mySingleLinkedList.addIndex(0 ,11);
        mySingleLinkedList.addIndex(1 ,22);
        mySingleLinkedList.addIndex(2 ,33);
        mySingleLinkedList.addIndex(3 ,22);
        mySingleLinkedList.addIndex(4 ,44);
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

10.删除所有值为key的结点 

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

【详解LinkedList与链表】

//删除所有值为key的结点
    public void removeAllKey(int key) {
        if (head == null) {
            return;
        }
        Node prev = head;
        Node cur = head.next;
        while (cur != null) {
            /*while (head.val == key) {
                head = head.next;
            }*/
            if (cur.val == key) {
                prev.next = cur.next;
                //cur = cur.next;
            } else {
                prev = cur;
                //cur = cur.next;
            }
            cur = cur.next;
        }
        if (head.val == key) {//处理头结点也是key的情况
            head = head.next;
        }
    }
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
        System.out.println("《---测试插入---》");
        mySingleLinkedList.addIndex(3, 11);
        mySingleLinkedList.addIndex(0, 7);
        mySingleLinkedList.addIndex(10, 44);
        /*try {
            mySingleLinkedList.addIndex(12, 44);
        } catch (LinekdListIndexOutOfException e) {
            e.printStackTrace();
        }*/
        mySingleLinkedList.display();
        System.out.println("《---测试删除---》");
        mySingleLinkedList.remove(7);
        mySingleLinkedList.remove(8);
        mySingleLinkedList.remove(9);
        mySingleLinkedList.remove(11);
        mySingleLinkedList.display();
        System.out.println("《---清空链表---》");
        mySingleLinkedList.clear();
        mySingleLinkedList.display();
        System.out.println("《---更新数据---》");
        mySingleLinkedList.addIndex(0 ,11);
        mySingleLinkedList.addIndex(1 ,22);
        mySingleLinkedList.addIndex(2 ,33);
        mySingleLinkedList.addIndex(3 ,22);
        mySingleLinkedList.addIndex(4 ,44);
        mySingleLinkedList.display();
        System.out.println("《---删除所有值为key的结点---》");
        mySingleLinkedList.removeAllKey(22);
        mySingleLinkedList.display();
    }
}

【详解LinkedList与链表】

总的代码: 

public class MySingleLinkedList {
    static class Node {//加static说明创建这个结点的时候不依赖外部对象
        public int val;//存储的数据
        public Node next;//存储下一个结点的地址
        /*
            给val构造方法,因为如果不设置,那么我们不知道结点的next域要存什么地址,
            为什么不给next赋值呢? 因为next是一个引用类型,默认是null,这就是结点类
             */
        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;//代表当前链表的头结点的引用
    //创建结点
    public void create() {
        Node node1 = new Node(10);
        Node node2 = new Node(12);
        Node node3 = new Node(11);
        Node node4 = new Node(9);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }
    /*
    遍历链表
    注意点1:
    如果是遍历完成整个链表,那么head == null
    如果是遍历到链表的最后一个结点(尾结点),那么head.next == null
     */
    public void display() {
        Node cur = head;//注意点2:让head引用指向头结点,cur来临时指向每个结点,如果不设置的话,那么head = null,连续打印两次链表,只能显示第一次打印的链表
        while (cur != null) {//如果判断条件是head.next != null,因为无法进入循环则不能打印最后结点的val
            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;
    }
    //得到单链表的长度 O(N)
    public int size() {
        int count = 0;
        Node cur = head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    //头插法 O(1)
    public void addFirst(int data) {
        Node node = new Node(data);//1.插入结点,必须先得有结点
        node.next = head;//2.先连
        head = node;//3.后断
    }
    //尾插法 O(N)
    public void addLast(int data) {
        Node node = new Node(data);
        if (head == null) {//该链表是否是第一次插入数据
            head = node;
            return;
        }
        Node cur = head;
        while (cur.next != null) {//cur指向最后一个结点(尾结点)
            cur = cur.next;
        }
        cur.next = node;
        /*node = cur;无意义*/
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) throws LinekdListIndexOutOfException {
        checkIndex(index);
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        Node cur = findIndexSubOne(index);
        Node node = new Node(data);
        node.next = cur.next;
        cur.next = node;
    }
    //找到index - 1位置的结点的地址
    private Node findIndexSubOne(int index) {
        Node cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    private void checkIndex(int index) throws LinekdListIndexOutOfException {
        if (index < 0 || index > size()) {
            throw new LinekdListIndexOutOfException("index位置不合法!");
        }
    }
    //删除第一次出现关键字为key的结点
    public void remove(int key) {
        if (head == null) {
            return;//一个结点也没有
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        Node cur = searchPrev(key);//key的前驱结点
        if (cur == null) {
            return;
        }
        Node del = cur.next;//要删除的结点
        cur.next = del.next;
        //cur.next = cur.next.next;
    }
    //找到关键字key的前一个结点
    private Node searchPrev(int key) {
        Node cur = head;
        while (cur.next != null) {//cur.next == null说明一直没有要删除的结点
            if (cur.next.val == key) {//如果cur指向的下一个结点的值和key相等,就返回当前cur,当前的cur指向的结点就是key的前一个结点(前驱结点)
                return cur;
            }
            cur = cur.next;
        }
        return null;//没有要删除的结点
    }
    //删除所有值为key的结点
    public void removeAllKey(int key) {
        if (head == null) {
            return;
        }
        Node prev = head;
        Node cur = head.next;
        while (cur != null) {
            /*while (head.val == key) {
                head = head.next;
            }*/
            if (cur.val == key) {
                prev.next = cur.next;
                //cur = cur.next;
            } else {
                prev = cur;
                //cur = cur.next;
            }
            cur = cur.next;
        }
        if (head.val == key) {//处理头结点也是key的情况
            head = head.next;
        }
    }
    //清空链表
    public void clear() {
        head = null;
    }
}
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
        mySingleLinkedList.create();
        mySingleLinkedList.display();
        System.out.println(mySingleLinkedList.contains(9));
        System.out.println(mySingleLinkedList.size());
        System.out.println("《---测试头插法---》");
        mySingleLinkedList.addFirst(9);
        mySingleLinkedList.addFirst(8);
        mySingleLinkedList.display();
        System.out.println("《---测试尾插法---》");
        mySingleLinkedList.addLast(22);
        mySingleLinkedList.addLast(33);
        mySingleLinkedList.display();
        System.out.println("《---测试插入---》");
        mySingleLinkedList.addIndex(3, 11);
        mySingleLinkedList.addIndex(0, 7);
        mySingleLinkedList.addIndex(10, 44);
        /*try {
            mySingleLinkedList.addIndex(12, 44);
        } catch (LinekdListIndexOutOfException e) {
            e.printStackTrace();
        }*/
        mySingleLinkedList.display();
        System.out.println("《---测试删除---》");
        mySingleLinkedList.remove(7);
        mySingleLinkedList.remove(8);
        mySingleLinkedList.remove(9);
        mySingleLinkedList.remove(11);
        mySingleLinkedList.display();
        System.out.println("《---清空链表---》");
        mySingleLinkedList.clear();
        mySingleLinkedList.display();
        System.out.println("《---更新数据---》");
        mySingleLinkedList.addIndex(0 ,11);
        mySingleLinkedList.addIndex(1 ,22);
        mySingleLinkedList.addIndex(2 ,33);
        mySingleLinkedList.addIndex(3 ,22);
        mySingleLinkedList.addIndex(4 ,44);
        mySingleLinkedList.display();
        System.out.println("《---删除所有值为key的结点---》");
        mySingleLinkedList.removeAllKey(22);
        mySingleLinkedList.display();
    }
}

※链表OJ面试题   

注意:从这儿开始将前面代码里面Node全部替换了ListNode,方便后续刷题改进代码

1. 删除链表中等于给定值 val 的所有节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        while (cur != null) {
            /*while (head.val == key) {
                head = head.next;
            }*/
            if (cur.val == val) {
                prev.next = cur.next;
                //cur = cur.next;
            } else {
                prev = cur;
                //cur = cur.next;
            }
            cur = cur.next;
        }
        if (head.val == val) {
                head = head.next;
            }
        return head;
    }
}

2. 反转一个单链表。

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return head;//直接返回null也可以
        }
        if (head.next == null) {
            return head;
        }
        ListNode cur = head.next;//用cur来遍历每个结点
        head.next = null;//先将头结点置空
        while (cur != null) {
            ListNode curNext = cur.next;//指向cur的下一个要遍历的结点,这个结点必须放在循环内要每次更新curNext的值,因为curNext也要往后遍历,如果放在循环外,每次cur = curNext的时候,curNext的值还是最开始的值没有更新
            //头插法 插入cur
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
    }
}

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。 

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {//偶数结点和奇数结点, fast走到的位置不一样, 所以都要考虑
            fast = fast.next.next;//fast走两步
            slow = slow.next;//slow走一步
        }
        return slow;//此时slow指向的结点就是中间结点
    }
}

4.输入一个链表,输出该链表中倒数第k个结点。 

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
//        if (k <= 0 || k > size() || head == null) {//判断是否合法
//            return null;
//        }
        //优化条件:去掉k > size()的条件
        if (k <= 0 || head == null) {//判断是否合法
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        //第一步:让fast走k-1步
        while (k - 1 != 0) {
            fast = fast.next;
            //优化条件
            if (fast == null) {//没走完k-1步之前fast==null,说明k给大了.
                return null;
            }
            k--;
        }
//        for (k = k - 1; k > 0; k--) {
//            fast = fast.next;
//            //优化条件
//            if (fast == null) {//没走完k-1步之前fast==null,说明k给大了.
//                return null;
//            }
//        }
        //第二步 和 第三步
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

5. (重点!!!)将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
        ListNode newHead = new ListNode(0);
        ListNode tmp = newHead;
        while (head1 != null && head2 != null) {
            if (head1.val < head2.val) {
                tmp.next = head1;
                head1 = head1.next;
                //tmp = tmp.next;
            } else {
                tmp.next = head2;
                head2 = head2.next;
                //tmp = tmp.next;
            }
            tmp = tmp.next;
        }
        if (head1 != null) {//说明head2 == null
            tmp.next = head1;
        }
        if (head2 != null) {//说明head1 == null
            tmp.next = head2;
        }
        return newHead.next;
    }
}

6.力扣链接 :编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。牛客链接:n链表分割_牛客题霸_牛客网 (nowcoder.com) 

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        // write code here
        // bs : before start, be: before end
        // as : after start, ae : after end
        ListNode bs = null;//相当于左半部分的头结点, 不能动
        ListNode be = null;
        ListNode as = null;//相当于右半部分的头结点, 不能动
        ListNode ae = null;
        ListNode cur = head;
        while (cur != null) {
            if (cur.val < x) {
                if (bs == null) {//左半部分第一次插入
                    bs = cur;
                    be = cur;
                    //cur = cur.next;
                } else { //不是第一次插入了
                    be.next = cur;
                    be = be.next;
                    //cur = cur.next;
                }
            } else {//cur.val >= x
                if (as == null) {//右半部分第一次插入
                    as = cur;
                    ae = cur;
                    //cur = cur.next;
                } else {//不是第一次插入了
                    ae.next = cur;
                    ae = ae.next;
                    //cur = cur.next;
                }
            }
            cur = cur.next;
        }
        //此时链表已经遍历完成了, 且分成了两部分, 左半边部分<x, 右半部分>=x
        //需要考虑ae是否需要置空???
        //有可能不会同时存在小于x 和 大于等于x的数据, 所以需要考虑所有的值都小于x 或者 所有的值都大于等于x的情况.
        //注意:下面这个if判断只能判断左半部分不为空, 可以拿到右半部分的数据
//        if (bs != null) {//说明左半部分一定有值<x的.
//            be.next = as;
//        }
        //而下面这种更好的写法可以判断左半部分为空和右半部分为空的情况, 从而分别拿到右半部分和左半部分的数据
        if (bs == null) {//说明左半部分为空, 只有右半部分的值,即数据都是大于等于x的.
            return as;//直接返回右半部分的头结点, 也包含了右半部分为空的情况.如果as == null, 也是返回null.
        }
        //左半部分不为空
        be.next = as;
        //右半部分不为空
        if (as != null) {
            ae.next = null;
        }
        return bs;
    }
}

7. 链表的回文结构。

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return false;
        }
        if (head.next == null) {//一个结点
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        //1.找中间结点
        while (fast != null && fast.next != null) {//奇数结点和偶数结点, fast走到的位置不一样, 所以都要考虑
            fast = fast.next.next;//fast走两步
            slow = slow.next;//slow走一步
        }
        //此时slow指向的结点就是中间结点,从这个结点开始反转,相当于要反转的头结点了
        //2.反转
        ListNode cur = slow.next;//cur代表当前要反转的结点
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //3.一个从头往后, 一个从后往前遍历, 比较值
        while (slow != head) {//直到slow和head相遇 slow == head
            if (head.val != slow.val) {
                return false;
            }
            //偶数结点的情况:
            if (head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }
}

8. 输入两个链表,找出它们的第一个公共结点。 

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        //1.分别两个链表的长度.
        int lenA = 0;
        int lenB = 0;
        ListNode pl = headA;//假设pl是指向长的链表
        ListNode ps = headB;//假设ps是指向短的链表

        while (pl != null) {
            lenA++;
            pl = pl.next;
        }
        while (ps != null) {
            lenB++;
            ps = ps.next;
        }

        //因为上面判断链表的长度, pl 和 ps都指向null, 所以需要将它们指回来.
        pl = headA;
        ps = headB;

        //3.根据len的值 修改指向.
        int len = lenA - lenB;
        if (len < 0) {
            pl = headB;
            ps = headA;
            len = lenB - lenA;
        }
        /*
        通过上述步骤得到:
        1.len一定是一个正数.
        2.pl指向的链表一定是最长的, ps指向的链表一定是最短的.
         */
        //4.让 pl 走 len 步
        while (len != 0) {
            pl = pl.next;
            len--;
        }
        while (pl != ps) {
            pl = pl.next;
            ps = ps.next;
        }
          //pl == ps
//        if (pl == ps && pl == null) {//不需要考虑ps == null, 因为pl已经和ps相等了.
//            return null;
//        }

        //优化:
        /*if (pl == null) {//上面while结束之后, 已经是pl == ps的条件了
            return null;
        }*/
        //或者直接注释, 因为返回null, 就说明没有相交.
        return pl;
    }
}

9. 给定一个链表,判断链表中是否有环。 

【详解LinkedList与链表】

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        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;
        /*ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {//条件1
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {//条件2
                break;
            }
        }
        //走到下面if语句有两种情况, 一种是不满足条件1, 另一种是不满足条件2
        if (fast == null || fast.next == null) {
            return false;
        }
        //其余情况: fast == slow 条件2
        return true;*/
    }
}

10.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

【详解LinkedList与链表】

【详解LinkedList与链表】

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        if (fast == null || fast.next == null) {
            return null;
        }
        //找到相遇点之后, 让slow指向起始点.
        slow = head;
        //fast和slow同时每次走1步.
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;//返回fast和slow都一样的
        //优化写法:
        /*ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                slow = head;//找到相遇点之后, 让slow指向起始点, 并结束循环.
                break;
            }
        }
        if (fast == null || fast.next == null) {
            return null;
        }
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;//返回fast和slow都一样*/
    }
}

LinkedList的模拟实现 

底层是:双向 不带头 非循环 链表 

【详解LinkedList与链表】

【详解LinkedList与链表】

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

头插法

【详解LinkedList与链表】

//头插法 O(1)
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        //第一次插入
        if (head == null) {
            head = node;
            last = node;
        }
        //说明不是第一次插入
        node.next = head;
        head.prev = node;
        head = node;
    }

尾插法

【详解LinkedList与链表】

//尾插法 O(1)
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        //第一次插入
        if (head == null) {
            head = node;
            last = node;
        }
        //说明不是第一次插入
        last.next = node;
        node.prev = last;
        last = node;
    }

任意位置插入元素

【详解LinkedList与链表】

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

删除第一次出现关键字key的结点

【详解LinkedList与链表】

//删除第一次出现关键字为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) {
                        head.prev = null;
                    }
                } else {
                    //删除中间或者尾巴结点
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        //删除中间结点
                        cur.next.prev = cur.prev;
                    } else {
                        //删除尾巴结点
                        last = last.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }

删除所有关键字key的结点

//删除所有值为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) {
                        head.prev = null;
                    }
                } else {
                    //删除中间或者尾巴结点
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        //删除中间结点
                        cur.next.prev = cur.prev;
                    } else {
                        //删除尾巴结点
                        last = last.prev;
                    }
                }
                //return; 去掉return这样就可以接着删
            }
            cur = cur.next;
        }
    }

清空双链表

【详解LinkedList与链表】

//清空双链表
    public void clear() {
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }

LinkedList的使用

什么是LinkedList

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

在集合框架中,LinkedList也实现了List接口,具体如下:

【详解LinkedList与链表】

【说明】

  1. LinkedList实现了List接口
  2. LinkedList的底层使用了双向链表
  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问  
  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
  5. LinkedList比较适合任意位置插入的场景   

LinkedList的使用 

1.LinkedList的构造  

【详解LinkedList与链表】

2. LinkedList的其他常用方法介绍   

【详解LinkedList与链表】

public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // add(elem): 表示尾插
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        System.out.println(list.size());
        System.out.println(list);
        // 在起始位置插入0
        list.add(0, 0); // add(index, elem): 在index位置插入元素elem
        System.out.println(list);
        list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
        list.removeFirst(); // removeFirst(): 删除第一个元素
        list.removeLast(); // removeLast(): 删除最后元素
        list.remove(1); // remove(index): 删除index位置的元素
        System.out.println(list);
        // contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
        if (!list.contains(1)) {
            list.add(0, 1);
        }
        list.add(1);
        System.out.println(list);
        System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置
        System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置
        int elem = list.get(0); // get(index): 获取指定位置元素
        list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
        System.out.println(list);
        // subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
        List<Integer> copy = list.subList(0, 3);
        System.out.println(list);
        System.out.println(copy);
        list.clear(); // 将list中元素清空
        System.out.println(list.size());
    }

【详解LinkedList与链表】

3. LinkedList的遍历 

public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // add(elem): 表示尾插
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        System.out.println(list.size());
        // foreach遍历
        for (Integer e : list) {
            System.out.print(e + " ");
        }
        System.out.println();
        // 使用迭代器遍历---正向遍历
        ListIterator<Integer> it = list.listIterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }
        System.out.println();
        // 使用反向迭代器---反向遍历
        ListIterator<Integer> rit = list.listIterator(list.size());
        while (rit.hasPrevious()) {
            System.out.print(rit.previous() + " ");
        }
        System.out.println();
    }

【详解LinkedList与链表】

ArrayList和LinkedList的区别   

【详解LinkedList与链表】

 

到了这里,关于【详解LinkedList与链表】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 数据结构(Java实现)LinkedList与链表(上)

    链表 逻辑结构 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。 链表的实现 创建一个链表 遍历单链表 、 得到

    2024年02月11日
    浏览(41)
  • 【数据结构】_4.List接口实现类LinkedList与链表

    目录 1.链表的结构与特点 1.1 链表的结构: 1.2 链表的特点: 2. 不带头单链表的模拟实现 3. 单链表OJ 3.1 题目1:移除链表元素:  3.2 题目2:反转一个单链表 3.3 题目3:返回链表的中间结点 3.4 题目4:链表的倒数第k个结点 3.5 题目5:合并两个有序链表 3.6 题目6:链表的回文结构

    2024年02月15日
    浏览(35)
  • C语言链表的含义与链表数据操作代码详解!

            在讲解开始前,我们先来看一张图片:         如图我们可以看到一列火车,它由车头和车厢组成,同时由链条连接,从整个火车我们可以看出,前一节的车厢尾总有着一个链条,让它紧密与后一个车厢相连。这样,如果我们找到了前一个车厢,那么我们就可以同

    2024年04月23日
    浏览(27)
  • C语言双向链表的含义与链表数据操作代码详解!

    引言: 于本文中,我们将讲到C语言数据结构中的双向链表的含义,以及对于双向链表的增删查改函数。该函数相比于单向链表,反而还更加的简单。希望这篇文章可以对你有帮助,也希望同样热爱代码编程的你能给我支持,您的支持就是我最大的动力! 关于顺序表以及单链

    2024年04月17日
    浏览(31)
  • 【数据结构二】链表和LinkedList详解

    目录 链表和LinkedList  1.链表的实现 2.LinkedList的使用 3.ArrayList和LinkedList的区别 4.链表OJ题训练         当 在 ArrayList 任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后 搬移,时间复杂度为 O(n) ,效率比较低,因此 ArrayList 不适合做任意位置插入和删除比较多

    2024年01月20日
    浏览(36)
  • 【数据结构与算法】顺序表与链表(单链表和双链表)超详解图示与源码。

                                                       大家好,今天我们来学习数据结构中的顺序表与链表!源码在最后附上 首先我们先来认识一下 顺序表 :                                       **如上图所示:很多人会以为数组就是顺序表,顺序表就是数组,这

    2024年02月21日
    浏览(36)
  • [JS与链表]双向链表

    阅读本文前请先阅读 [JS与链表]普通链表_AI3D_WebEngineer的博客-CSDN博客 类的继承可以使用extends,让子类继承父类的属性和方法。 而在子类内部(构造函数constructor)必须调用super()实现继承( super()代表父类构造函数 ) 双向链表与普通链表的区别在于:普通链表的节点是链向下

    2024年02月05日
    浏览(40)
  • 顺序表与链表

    思维导图:   顺序表与链表都是两种线性表,但是两者之间又有着许多的不同。顺序表是一种连续的空间,实际上就是数组。链表是不连续的空间,链表的空间是一块一块的开辟出来的。 两者的优点与缺点 : 顺序表: 优点: 1.顺序表的空间是连续的,所以能够支持下标的

    2024年02月12日
    浏览(48)
  • 数组与链表的区别

    数组是连续存储,数组在创建时需要一个整块的空间。 链表是链式存储,链表在内存空间中不一定是连续的。 数组一般创建在栈区,链表一般创建在堆区,在增加节点时需要new或malloc新节点,相较于数组长度不固定,自由度高。 数组可以通过下标随机访问,单向链表只能通

    2024年02月05日
    浏览(63)
  • 顺序表与链表的区别

    目录 一、顺序表和链表的比较 顺序表 优点: 缺点: 链表 优点 缺点 二、顺序表和链表的区别 1.顺序表和链表都具有增、删、查、改功能,但算法复杂度却不相同。 2、从数据元素存储的内存角度来看 三、顺序表与链表选取方案 顺序表的特点是逻辑上相邻数据元素,物理存

    2024年02月08日
    浏览(40)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包