【数据结构】第四弹——LinkedList与链表

文章目录

  • [一. ArrayList 的缺陷](#一. ArrayList 的缺陷)
  • 二.链表
    • [2.1 链表的概念及结构](#2.1 链表的概念及结构)
    • [2.2 链表的结构](#2.2 链表的结构)
      • [2.2.1 单向或者双向](#2.2.1 单向或者双向)
      • [2.2.2 带头或者不带头](#2.2.2 带头或者不带头)
      • [2.2.3 循环非循环](#2.2.3 循环非循环)
    • [2.3 链表的实现](#2.3 链表的实现)
      • [1. IList接口](#1. IList接口)
      • [2. MySingleList 类中具体实现(不带头单向非循环链表)](#2. MySingleList 类中具体实现(不带头单向非循环链表))
        • [1. 节点抽象成内部类](#1. 节点抽象成内部类)
        • 手搓一个链表
        • [2. 头插法](#2. 头插法)
        • [3. 尾插法](#3. 尾插法)
        • [4. 指定位置插入](#4. 指定位置插入)
        • [5. 是否包含key值](#5. 是否包含key值)
        • [6. 删除指定值的节点](#6. 删除指定值的节点)
        • [7. 删除所有指定值所在节点](#7. 删除所有指定值所在节点)
        • [8. 获取链表长度](#8. 获取链表长度)
        • [9. 清空链表](#9. 清空链表)
        • [10. 打印链表](#10. 打印链表)
  • [三. 链表面试题](#三. 链表面试题)
  • [四. MyLinkedList 的模拟实现(不带头双向非循环链表)](#四. MyLinkedList 的模拟实现(不带头双向非循环链表))
  • [五. LinkedList 的使用](#五. LinkedList 的使用)
    • [5.1 什么是LinkedList](#5.1 什么是LinkedList)
    • [5.2 LinkedList 的使用](#5.2 LinkedList 的使用)
    • [5.2.1 LinkedList 的构造](#5.2.1 LinkedList 的构造)
    • [5.2.2 LinkedList的其他常用方法介绍](#5.2.2 LinkedList的其他常用方法介绍)
    • [5.3 LinkedList的遍历](#5.3 LinkedList的遍历)
      • [5.3.1 for遍历](#5.3.1 for遍历)
      • [5.3.2 foreach](#5.3.2 foreach)
      • [5.3.3 迭代器遍历(正向反向)](#5.3.3 迭代器遍历(正向反向))
  • [六. ArrayList和LinkedList的区别](#六. ArrayList和LinkedList的区别)

一. ArrayList 的缺陷

上篇文章已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素

ArrayList源码:

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 {
 
   // ...
   
   //  默认容量是10
    private static final int DEFAULT_CAPACITY = 10;
 
    //...
    
    // 数组:用来存储元素
    transient Object[] elementData; // non-private to simplify nested class access
    
    // 有效元素个数
    private int size;
 
 
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
         }
}

其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低 ,因此ArrayList不适合做任意位置插入和删除比较多的场景 。因此:java集合中又引入了LinkedList,即链表结构

二.链表

2.1 链表的概念及结构

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

1.从图中可以看出,链式结构在逻辑上是连续的 ,但是在物理上不一定连续

2.现实中的节点 一般都是从堆上申请出来的

3.从堆上申请的空间 ,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

2.2 链表的结构

以下情况组合起来就有8种链表结构:

2.2.1 单向或者双向

2.2.2 带头或者不带头

2.2.3 循环非循环

虽然有这么多的链表的结构,但是我们重点掌握两种:

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

2.3 链表的实现

1. IList接口

java 复制代码
public interface IList {
    
    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 clear();
        
    public void display();
}

再次回顾:接口中的方法默认都是 public abstract 修饰的,这里不写public也可以

正式使用LinkList 之前还是一样 自己实现一个链表 方便我们更好的理解链表

2. MySingleList 类中具体实现(不带头单向非循环链表)

1. 节点抽象成内部类
手搓一个链表

就是

java 复制代码
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. 头插法


java 复制代码
public void addFirst(int data) {
        ListNode node  = new ListNode(data);
        node.next=head;
        head=node;
    }
3. 尾插法


java 复制代码
public void addLast(int data) {
        ListNode node  = new ListNode(data);
    if(head==null){
        node=head;
        return ;
    }
        ListNode cur  =head;
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next=node;

    }
4. 指定位置插入

首先要判断插入的位置是否合法 , 写一个自定义异常

java 复制代码
public class IndexException extends RuntimeException{
	public IndexException(){

	}
	public IndexException(String message){
		super(message);
    }
}


java 复制代码
public void addIndex(int index, int data) {
       if(index==0){
           addFirst(data);
           return ;
       }
       int len=size();//获取链表长度
       if(index==len){
           addLast(data);
       }
       ListNode node=new ListNode(data);
        ListNode cur=head;
       try{
           while(index-1!=0){
            cur=cur.next;
            index--;
           }
           node.next=cur.next;
           cur.next=node;
       }catch (IndexException e){
           System.out.println("index位置不合法!!!!");
           e.printStackTrace();
        }

    }
5. 是否包含key值
java 复制代码
public boolean contains(int key) {
        //还是需要cur 不能让头结点移动,要不然找不到头了
        ListNode cur=head;
        while (cur!=null){
            if(cur.val==key){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }
6. 删除指定值的节点
java 复制代码
public void remove(int key) {
        ListNode node=new ListNode(key);
        if(head==null){
            //空链表  直接return  没啥可删的
            return;
        }
        if(head.val==key){
            //如果要删的是头  把头定位下一个节点就行了
            head=head.next;
            return ;
        }
        ListNode cur = findNodeOfKey(key);
        if(cur==null){
            return; //没找到要删除的值
        }
        ListNode del = cur.next;
        //要是找到了,就架空del  删除del
        cur.next=del.next;
    }
    public ListNode findNodeOfKey(int key){
        ListNode cur=head;
        while (cur.next!=null){
            if(cur.next.val==key){
                return cur;
            }
            cur=cur.next;
        }
        return null;
    }
7. 删除所有指定值所在节点
java 复制代码
public void removeAllKey(int key) {
        if(head == null) {  //头为空 直接返回
            return;
        }
        ListNode prev = head;
        ListNode cur = head.next;//cur 代表当前节点是否为你要删除的节点
        while (cur != null) {
            if(cur.val == key) { //判断一下是否为指定值
                prev.next = cur.next;
                cur = cur.next;//  是指定值就 架空cur 删除cur  并且cur向后走一位
            }else {
                prev = cur;
                cur = cur.next;  //不是指定值就  两个都往后走
            }
        }
        if(head.val == key) {
            head = head.next;   //头为指定值  删头
        }
    }
8. 获取链表长度
java 复制代码
 public int size() {
        int len = 0;
        ListNode cur = head;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        return len;
    }
9. 清空链表

两种方法: ①直接head置为空 ,有点暴力

②head cur 一个一个置为空, 更温和一点

java 复制代码
public void clear() {
        ListNode cur = head;
        while (cur != null) {
            ListNode curN = cur.next;
            cur.next = null;
            cur = curN;
        }
        head = null;
    }
10. 打印链表
java 复制代码
 public void display() {
            ListNode cur = head;
            while (cur != null) {
                System.out.print(cur.val+" ");
                cur = cur.next;
            }
            System.out.println();
    }
}

三. 链表面试题

关于链表的面试题 都在刷题专栏 test1,像练习链表题的同学可以移步刷题专栏的第一篇文章
以上我们讲的都是不带头单向非循环链表
集合类中都是双向链表,大家注意区分,内部类的实现有所区别,双向链表有三个域,前驱域,数据域,后驱域

四. MyLinkedList 的模拟实现(不带头双向非循环链表)

节点抽象成内部类

java 复制代码
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;

1.头插法

2.尾插法

java 复制代码
 public void addLast(int data) {
//尾插 遍历整个链表 最后一个节点的next 指向node
        ListNode node=new ListNode(data);
        if(head==null){
            head=node;
            return ;
        }
        ListNode cur=head;
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next=node;

    }

3.指定位置插入

java 复制代码
 public  void addIndex(int index, int data) {
       int len=size();
       if(index<0 || index>len){
           System.out.println("index位置不合法");
           return;
       }
       if(index==0){
           addFirst(data);
           return ;
       }
       if(index==len){
           addLast(data);
           return ;
       }
       ListNode cur=head;
       while(index-1!=0){
           cur=cur.next;
           index--;
       }
       ListNode node=new ListNode(data);
       node.next=cur.next;
       cur.next=node;
    }

4.是否包含Key值

java 复制代码
public boolean contains(int key) {
        ListNode cur=head;
        while(cur!=null){
            if(cur.val==key){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }

5. 删除指定值的节点

java 复制代码
public void remove(int key) {
        if(head==null){
            return ;
        }
        //头是需要删除的值 删除 头
        if(head.val==key){
            head=head.next;
            return ;
        }
        ListNode cur=findKeyNode(key);
        ListNode del=cur.next;
        //cur是需要删除的节点的前一个

        if(cur==null){
            return ;//没有需要删除的节点
        }
        cur.next=del.next;//架空删除 哪个需要删除的节点

    }
    private ListNode findKeyNode(int key){
        ListNode cur=head;
        while(cur.next!=null){
            if(cur.next.val==key){
                return cur; //获得需要删除的节点的前一个结点  单链表走过了 无法找到前一个结点
            }
            cur=cur.next;
        }
        return null;
    }

6.删除所有指定值的节点

java 复制代码
public void removeAllKey(int key) {
        if(head==null){
            return ;
        }
        if(head.val == key) {
            head = head.next;
        }
        ListNode prev=head;
        ListNode cur=head.next;
        while(cur!=null){
            if(cur.val==key){
                prev.next=cur.next;
                cur=cur.next;
            }else{
                prev=cur;
                cur=cur.next;
            }
        }
    }

7. 获取链表长度

java 复制代码
public int size() {
        int count=0;
        ListNode cur=head;
        while(cur!=null){
            count++;
            cur=cur.next;
        }
        return 0;
    }

8. 清空链表

java 复制代码
 public void clear() {
        //不是把头置为空  太暴力了 我们一个一个置空
        ListNode cur=head;
        while(cur!=null) {
            ListNode curN = cur.next;
            cur.next  = null;
            cur = curN;
        }
        head=null;
    }

9.打印链表

java 复制代码
public void display() {
        //打印
        ListNode cur=head;
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
        System.out.println();
    }

五. LinkedList 的使用

5.1 什么是LinkedList

LinkList官方文档

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

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

【说明】

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

5.2 LinkedList 的使用

5.2.1 LinkedList 的构造

java 复制代码
 public static void main(String[] args) {
 // 构造一个空的LinkedList
 List<Integer> list1 = new LinkedList<>();//无参构造
 List<String> list2 = new java.util.ArrayList<>();
 list2.add("JavaSE");
 list2.add("JavaWeb");
 list2.add("JavaEE");
 // 使用ArrayList构造LinkedList
 List<String> list3 = new LinkedList<>(list2);// 使用集合容器中的元素构造
 }

通配符 (讲解ArrayList的博客中有介绍)list2 由List创建 属于LinkedList 的子类所以可以作为参数

5.2.2 LinkedList的其他常用方法介绍

简单演示几个方法 大家也可以自己点击源码查看其他方法的逻辑

5.3 LinkedList的遍历

5.3.1 for遍历

就是MyLinkedList里的display()方法

5.3.2 foreach

java 复制代码
 LinkedList<Integer> list = new LinkedList<>();
    list.add(1);   // add(): 默认尾插
    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 (int e:list) {
        System.out.print(e + " ");
    }
    System.out.println();

5.3.3 迭代器遍历(正向反向)

java 复制代码
 // 使用迭代器遍历---正向遍历
    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();

六. ArrayList和LinkedList的区别

相关推荐
猎猎长风1 小时前
【数据结构和算法】3. 排序算法
数据结构·算法·排序算法
乌鸦9441 小时前
《数据结构之美--双向链表》
数据结构·链表
bookish_2010_prj2 小时前
链式栈和线性栈
数据结构·c++·算法
egoist20232 小时前
【C++指南】哈希驱动的封装:如何让unordered_map/set飞得更快更稳?【上】
数据结构·c++·算法·容器·哈希算法·散列表·c++11
刚入坑的新人编程2 小时前
数据结构——栈和队列
c语言·数据结构·c++·链表·数组
祁同伟.4 小时前
OJ - 设计循环队列
c语言·数据结构·算法
啊阿狸不会拉杆4 小时前
数据结构-数组与广义表
java·c语言·数据结构·c++·python·算法
jie188945758665 小时前
数据结构-----目录管理-----1
数据结构
不会计算机的捞地5 小时前
【数据结构入门训练DAY-14】 蓝桥杯2024年第十五届省赛真题-R 格式
数据结构·蓝桥杯