LinkedList与链表(有源码剖析)(二)

简介: LinkedList与链表(有源码剖析)(二)

LinkedList与链表(有源码剖析)(一)+https://developer.aliyun.com/article/1413517

3.任意位置插入

给指定的位置index,在指定位置插入一个节点(第一节点对应的index为0)

    public void addIndex(int index,int data) {
        // 先检查index是否合法
        if(index < 0 || index > size()) {
            throw new posOutOfBoundException(index + "位置不合法");
        }
        if (index == 0) addFirst(data);
        if (index == size()) addLast(data);
        // 让cur走到要删除结点的前驱节点  走index - 1步
        ListNode cur = head;
        for (int i = 0; i < index-1; i++) {
            cur = cur.next;
        }
        ListNode newNode = new ListNode(data);
        newNode.next = cur.next;
        cur.next = newNode;
    }

3.查找是否包含关键字key的结点

    // 判断是否包含
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

4.求链表的长度

    // 返回长度
    public int size() {
        ListNode cur = head;
        int cnt = 0;// 设置计数器
        while (cur != null) {
            cnt++;
            cur = cur.next;
        }
        return cnt;
    }

5. 删除第一次出现关键字为key的节点

要进行此删除操作,关键还是要在删除对应节点之后仍然连接之后的结点

    public void remove(int key) {
        if(head == null) {
            throw new RuntimeException("链表为空 无法删除");
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        // 让cur走到要删除节点的前驱节点
        ListNode cur = findPrevNode(key);
        if(cur == null) {
            System.out.println("没有你要删除的结点");
            return;
        }
        cur.next = cur.next.next;
    }
    // 找要删除结点的前驱结点
    private ListNode findPrevNode(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

6.删除所有值为key的节点

    public void removeAll(int key) {
        if(head == null) {
            throw new RuntimeException("链表为空 无法删除");
        }
        // 处理头节点是key的情况
        while (head.val == key) {
            head = head.next;
        }
        // 设置两个结点 前驱节点prev  和用于遍历的结点cur
        ListNode prev = head;
        ListNode cur = head.next;
        while (cur != null) {
            // 等于  进行删除
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
                continue;
            }
            prev = prev.next;
            cur = cur.next;
        }
    }

注意考虑当头节点是要删除的结点

7.链表的打印

    // 打印链表
    public void display() {
        // 要始终保持head结点是头节点  此处使用一个临时结点cur用于遍历整个链表
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val);
            cur = cur.next;
        }
    }

四.LinkedList

1.什么是LinkedList

 LinkedList是Java中使用双向链表实现的一中数据结构,每个节点都含有三个属性:

  • elem:存储元素的值
  • next:保存当前节点的下一个结点
  • prev:保存当前结点的上一个结点

在集合框架中,LinkedList也实现了List接口

说明:

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

2.LinkedList适合用于大量进行数据增加/删除的操作

2.LinkedList的模拟实现

1.LinkedList的结构

LinkedList是一个双向的链表,且有两个"标记结点"head,last,分别用于标记链表的头/尾

2.代码实现

public class MyLinkedList {
    static class ListNode {
        private int data;
        private ListNode prev;
        private ListNode next;
        public ListNode(int data) {
            this.data = data;
        }
    }
    public ListNode head;
    public ListNode last;
    /**
     * 前三个方法和prev没关系
     * 只需遍历整个链表即可
     * @return
     */
    //得到单链表的长度
    public int size(){
        int cnt = 0;
        ListNode cur = head;
        while (cur != null) {
            cnt++;
            cur = cur.next;
        }
        return cnt;
    }
    // 打印链表
    public void display(){
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            if (cur.data == key) {
                return true;
            }
            cur = cur.next;
        }
        System.out.println("链表不包含该元素");
        return false;
    }
    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        // 为空,直接赋值
        if (head == null) {
            head = last = node;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }
    //尾插法  最方便的一集  因为本来就有尾结点
    public void addLast(int data){
        ListNode node = new ListNode(data);
        // 为空,直接赋值
        if (head == null) {
            head = last = node;
        }else {
           last.next = node;
           node.prev = last;
           last = node;
        }
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index<0 || index>size()) {
            throw new RuntimeException(index + "位置异常");
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()){
            addLast(data);
            return;
        }
        ListNode cur = searchIndex(index);
        ListNode node  = new ListNode(data);
        // 插入  对于单链表来说需要cur走到Index位置的前一个结点  而双向链表不需要  直接走到index位置的结点即可
        // 在index位置的插入都是先绑定后面的
        node.next = cur;
        cur.prev.next = node;
        // 这两行的顺序不能改变
        node.prev = cur.prev;
        cur.prev = node;
    }
    private ListNode searchIndex(int index) {
        ListNode cur = head;
        while (index > 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    /**
     * 我写的这个删除的代码将寻找data==Key的这个过程封装成一个方法  思路很好  但是不利于写删除所有的key值的方法
     * 因为想要删除所有的key值,不可避免的是要遍历整个链表,而不是得到某一个节点
     * @param key
     * @return
     */
/*    //删除第一次出现关键字为key的节点
    public void remove(int key){
        // 不能这样写,因为要求的是删除第一次出现的key
//        if(last.data == key) {
//            last = last.prev;
//            last.next = null;
//            return;
//        }
        // 使cur为data为key的结点
        ListNode cur = findIndexOf(key);
        if (cur == null) {
            System.out.println("没有你要删除的结点");
            return;
        }
        if (cur == head) {
            head = head.next;
            if (head != null) {
                head.prev = null;
            }else {
                // 处理只有一个结点的情况  head直接为空了  此时只需把last也置空即可
                last = null;
            }
        }else {
            // 处理中间结点和尾巴结点  尾巴结点要单独处理(后继为null)
            if (cur.next != null) {
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
            }else {
                cur.prev.next = null;
                last = cur.prev;
            }
        }
    }
    private ListNode findIndexOf(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.data == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }*/
    /**
     * 重写remove方法
     * @param key
     * @return
     */
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.data == key) {
                // 等于key也要分两种情况
                if (cur == head) {
                    head = head.next;
                    if (head == null) {
                        last = null;
                    }else {
                        head.prev = null;
                    }
                }else {
                    // 处理中间结点和尾结点
                    if (cur.next != null) {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }else {
                        cur.prev.next = null;
                        last = cur.prev;
                    }
                }
                return;
            }else {
                cur = cur.next;
            }
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            if (cur.data == key) {
                // 等于key也要分两种情况
                if (cur == head) {
                    head = head.next;
                    if (head == null) {
                        last = null;
                    }else {
                        head.prev = null;
                    }
                }else {
                    // 处理中间结点和尾结点
                    if (cur.next != null) {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }else {
                        cur.prev.next = null;
                        last = cur.prev;
                    }
                }
                cur = cur.next;
                //return;
            }else {
                cur = cur.next;
            }
        }
    }
    public void clear(){
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = cur.next;
        }
        // 头尾结点要单独置空
        head = null;
        last = null;
//        // 把每一个链表都指控
//        ListNode cur = head;
//        while (cur != null) {
//            cur.prev = null;
//            cur = cur.next;
//            if (cur != null) {
//                cur.prev.next = null;
//            }
//        }
    }
}

3.LinkedList的使用

1.构造方法
  • LinkedList() 无参构造
  • public LinkedList(Collection<? estends E> c) 使用其他集合容器中元素构造List
    // 无参的构造方法
    public LinkedList() {
    }
    // 使用其他集合来构建一个链表
    public LinkedList(Collection<? extends E> c) {
        this();// 调用上面的构造方法
        addAll(c);// 将集合c中的所有元素填入到创建的链表中
    }
2.其他方法
1.add

boolean add(E e) 尾插 e

    // This method is equivalent to addLast. 这个方法和addLast是等价的
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    // 尾插
    void linkLast(E e) {
        final LinkedList.Node<E> l = last;// 让l指向最后一个结点
        // 创建一个新的结点
        final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;// 用于记录集合被修改的次数
    }

void add(int index, E element) 将 e 插入到 index 位置

    public void add(int index, E element) {
        checkPositionIndex(index);// 检查index是否合法
        // 尾插
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    // Inserts element e before non-null Node succ.  在succ之前添加一个非空的结点e
    void linkBefore(E e, LinkedList.Node<E> succ) {
        // assert succ != null;
        // 设置pred为succ的前驱节点
        final LinkedList.Node<E> pred = succ.prev;
        final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

boolean addAll(Collection c) 尾插 c 中的元素

    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);
        // 将集合c转变为数组  并求出他的长度
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        LinkedList.Node<E> pred, succ;
        if (index == size) {
            // 对list进行尾插集合c
            succ = null;
            pred = last;
        } else {
            // 在指定位置的list中进行c集合的插入
            succ = node(index);
            pred = succ.prev;
        }
        // 遍历集合  进行插入
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }
        // 将新加入的结点和原来的结点连接起来
        if (succ == null) {
            // 此处处理的是尾插
            last = pred;
        } else {
            // 处理在指定位置的插入
            pred.next = succ;
            succ.prev = pred;
        }
        size += numNew;
        modCount++;
        return true;
    }
2.remove

E remove(int index) 删除 index 位置元素

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    E unlink(LinkedList.Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final LinkedList.Node<E> next = x.next;
        final LinkedList.Node<E> prev = x.prev;
        // 先处理x.prev
        if (prev == null) {
            // 要删除的结点为头结点  直接让x的下一个结点作为头结点
            first = next;
        } else {
            // 不是头结点  连接x的下一个节点
            prev.next = next;
            x.prev = null;
        }
        // 处理x.next
        if (next == null) {
            // 要删除的结点是尾结点
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }

以下操作都十分简单,读者可自行查阅使用,这里不做过多的介绍

3.get

E get(int index) 获取下标 index 位置元素

4.set

E set(int index, E element) 将下标 index 位置元素设置为 element

5.contains

boolean contains(Object o) 判断 o 是否在线性表中

6.indexOf

int indexOf(Object o) 返回第一个 o 所在下标

7.subList

List subList(int fromIndex, int toIndex) 截取部分 list

3. LinkedList的遍历

LinkedList的遍历的遍历一般有两种遍历方式

  • 迭代器
  • for each循环
    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);
        // for each循环遍历
        for (Integer i : list) {
            System.out.print(i + " ");
        }
        // 迭代器
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

五. ArrayList和LinkedList的区别

  1. 存储空间:ArrayList物理地址连续        LinkedList物理地址不连续
  2. 随机访问(RandomAccess接口):ArrayList支持随机访问    LinkedList不支持
  3. 头插:ArrayList需要挪动数据    LinkedList不需要挪动数据
  4. 扩容:ArrayList空间不够需要使用grow进行1.5倍扩容    LinkedList没有容量的概念
  5. 应用场景:ArrayList频繁的查询    LinkedList频繁的进行数据的删除/插入
目录
相关文章
|
24天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
50 4
|
1月前
|
存储 Java
HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)
本文详细解析了Java HashMap中链表转红黑树的机制,包括树化条件(链表长度达8且数组长度≥64)及转换流程,确保高效处理大量数据。
91 1
|
24天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
40 0
|
3月前
|
存储 Java
|
3月前
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)
93 4
|
3月前
|
存储 测试技术
【初阶数据结构篇】单链表的实现(附源码)
在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。
38 0
|
6月前
|
数据采集 Java 数据处理
Java流与链表:探索java.util.stream与LinkedList的交汇点
本文探讨了Java中流(Streams)与链表(LinkedList)的结合使用,展示了如何通过流处理LinkedList以实现高效数据操作。示例代码包括LinkedList的基本操作、使用Stream进行过滤和映射,以及结合HttpClient和代理IP实现网络爬虫。代理IP有助于绕过反爬机制,提高爬取效率。通过结合这些技术,开发者能编写出更简洁、高效的代码。
Java流与链表:探索java.util.stream与LinkedList的交汇点
|
6月前
|
算法 测试技术
【数据结构与算法 | 基础篇】模拟LinkedList实现的双向循环链表
【数据结构与算法 | 基础篇】模拟LinkedList实现的双向循环链表
|
6月前
|
存储 算法 Java
【数据结构与算法 | 基础篇】模拟LinkedList实现的双向链表
【数据结构与算法 | 基础篇】模拟LinkedList实现的双向链表
|
6月前
|
存储 Python
链表(Linked List)详解
链表(Linked List)详解
51 0