数据结构-LinkedList和链表

news/2025/1/21 14:03:19/

1.链表

1.1为什么要引入链表

上一篇文章中我们介绍了ArrayList,ArrayList的底层是通过数组实现的,具有随机访问效率高等特点,那么我们为什么还需要引入链表呢?

这是因为ArrayList底层通过数组实现,当在ArrayList任意位置(除了最后一位)进行插入和删除操作时,就需要将后续的元素整体向前或者向后进行搬移,时间复杂度为O(n),效率比较低,由此可见ArrayList很不适合在一些插入或者删除操作较多的场景,而java集合中引入了链表,可以很好改善这个问题

1.2链表的概念和结构

链表是一种物理存储上非连续的存储结构,数据元素上的逻辑顺序是通过链表中的引用实现的。不同于数组,链表的物理存储单元可以分散在内存中的任意位置,通过节点(每个节点包含两部分:数据域和指针域。数据域用于存储数据元素,指针域则存储指向下一个节点的引用)之间的引用将它们连接起来形成一个线性序列

在Java中用对象引用代替指针

链表的结构多种多样,主要分为以下6种结构:

1.单向链表或双向链表

2.带头链表或者不带头链表

3.循环链表或者非循环链表

 

1.3实现一个单链表

public class SingleLinkedList{//头插法public void addFirst(int data){}//尾插法public void addLast(int data){}//任意位置插入public void addIndex(int index,int data){}//查询链表中是否含有关键字keypublic boolean contains(int key){}//删除第一次出现关键字key的节点public void remove(int key){}//删除所有值为key的节点public void removeAllKey(int data){}//得到单链表的长度public int size(){}//清空所有节点,清空单链表public void clear(){}//打印单链表public void display(){}
}

 实现思路:

1.addFirst(int data)在单链表头部进行插入操作,只需要创建一个新的节点将新节点的value属性赋值为data,并将新节点的next指向原来链表的头部节点即可

2.addLast(int data)在单链表尾部进行插入操作,只需要创建一个新的节点将新节点的value属性赋值为data,并将原链表尾部的节点的next指向新的节点即可

3.addIndex(int pos,int data)在链表指定位置插入元素,首先要判断插入的位置是否合法,如果合法则分别需要修改要将原来插入位置的元素的next指向新节点,并且新节点的next要指向插入位置后面的那个节点

4.contains(int key)判断链表是否包含元素key,只需要依次遍历链表,获取到每个节点的元素并进行判断

5.remove(int key)删除元素key,遍历链表,找到key所在的位置,将key前一个元素的next指向key后面的元素即可

6.removeAllKey(int key)删除元素值为key的所有元素,每找到一个值为key的元素要进行判断key是否有前后节点,再进行相应的删除操作

7.size()返回链表中的节点个数,可以通过遍历链表并设置计数器,通过计数器统计链表中结点的个数

8.display()打印链表,遍历并依次打印每个节点的元素

 

2.LinkedList

2.1什么是LinkedList

LinkedList是Java集合框架中的链表,LinkedList是基于双向链表实现的,由于链表中没有将元素存储在连续的空间内,元素存储在单独的节点中,然后通过引用将节点连接起来,因此在任意位置插入或者删除元素,不需要搬移元素,效率比较高。

LinkedList中的节点结构: 

 

说明:

1.LinkedList实现了List接口

2.LinkedList的底层使用了双向链表

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

4.LinkedList的任意位置插入和删除操作效率比较高,时间复杂度为O(1)

5.LinkedList比较适合插入删除比较多的场景

6.LinkedList的访问效率比较低,时间复杂度为O(n)

 

2.2LinkedList的构造方法

方法解释
LinkedList()无参构造
public LinkedList(Collection<? extends E> c)使用其他集合容器种的元素构造List

代码演示:

public class Test {public static void main(String[] args) {//构造一个空的LinkedListList<Integer> list1=new LinkedList<>();System.out.println(list1);List<String> list=new java.util.ArrayList<>();list.add("hajimi");list.add("hahaha");list.add("lalala");//使用ArrayList构造LinkedListList<String> list2=new LinkedList<>(list);System.out.println(list2);}
}

 

2.3LinkedList的常用方法

方法解释
boolean add(E,e)在尾部插入元素e
void add(int index,E e)将元素e插入到index的位置
boolean addAll(Collection<? extends E> c)在尾部插入c中的所有元素
E remove(int index)删除index位置的元素

boolean remove(Object o)

删除第一个o元素
E get(int index)获取下标为index位置元素
E set(int index,E e)将下标为index位置的元素设置为e
void clear()清空链表中的所有元素
boolean contains(Object o)判断o是否在线性表中
int indexOf(Object o)返回第一个o所在的下标
int lastIndexOf(Object o)返回最后一个o所在的下标
List<E> subList(int fromIndex,int toIndex)截取部分list

代码演示:

 

import java.util.*;public class Test {public static void main(String[] args) {List<String> list=new LinkedList<>();//在链表末尾插入元素list.add("My");list.add("name");list.add("hajimi");System.out.println(list);//在指定位置插入元素list.add(2,"is");System.out.println(list);list.add(4,"king");System.out.println(list);//删除指定位置的元素list.remove(4);System.out.println("进行删除操作后的链表"+list);//删除指定元素list.remove("My");System.out.println("进行删除操作后的链表"+list);//获取指定位置的元素System.out.println(list.get(2));//将指定位置下标的元素设置为eSystem.out.println(list.set(0,"Your name"));System.out.println(list);//判断线性表中是否包含某元素System.out.println(list.contains("hajimi"));list.add("hajimi");//返回指定元素第一个出现的下标System.out.println(list.indexOf("hajimi"));//返回指定元素最后一次出现的下标System.out.println(list.lastIndexOf("hajimi"));//截取部分链表内容List<String> partList;partList=list.subList(0,3);System.out.println("截取的部分:"+partList);//在链表尾部插入其他容器的所有元素list.addAll(partList);System.out.println(list);//清空链表中的所有元素list.clear();System.out.println(list);}
}

 

2.4LinkedList的遍历

LinkedList遍历主要有3中方式:

1.使用for循环搭配链表长度进行遍历

2.使用foreach循环

3.使用迭代器进行遍历

代码演示:

import java.util.*;public class Test {public static void main(String[] args) {List<String> list=new LinkedList<>();list.add("My");list.add("name");list.add("is");list.add("hajimi");list.add("king");list.add("!");//使用for循环搭配链表长度进行遍历链表for (int i=0;i<list.size();i++){System.out.print(list.get(i)+" ");}System.out.println();//使用foreach循环遍历链表for(String s:list){System.out.print(s+" ");}System.out.println();//使用迭代器遍历链表//正向遍历ListIterator<String> it= list.listIterator();while(it.hasNext()){System.out.print(it.next()+" ");}System.out.println();//反向遍历ListIterator<String> rit= list.listIterator(list.size());while (rit.hasPrevious()){System.out.print(rit.previous()+" ");}}
}

2.5实现一个LinkedList

 LinkedList原码中节点的结构如下:

  实现一个泛型为Integer的链表,代码演示:

public class MyLinkedList2 {static class ListNode{private ListNode prev;private int val;private ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head;public ListNode tail;public MyLinkedList2(){}//头插法public void addFirst(int data){ListNode newNode=new ListNode(data);if(head==null){head=tail=newNode;}else {newNode.next=head;head.prev=newNode;head=newNode;}}//尾插法public void addLast(int data){ListNode newNode=new ListNode(data);if(head==null){head=tail=newNode;}else {newNode.prev=tail;tail.next=newNode;tail=newNode;}}//任意位置插入,第一个数据节点为0号下标public boolean addIndex(int index,int data){if(index<0||index>size()){System.out.println("要插入的位置不合法");return false;}if(index==0){addFirst(data);return true;}if(index==size()){addLast(data);return true;}ListNode cur=head;while(index!=0){cur=cur.next;index--;}ListNode newNode=new ListNode(data);newNode.next=cur;newNode.prev=cur.prev;cur.prev.next=newNode;cur.prev=newNode;return true;}//查找是否包含关键字key是否在单链表当中public boolean contains(int key){if (head==null){System.out.println("链表为空");return false;}ListNode cur=head;while (cur!=null){if(cur.val==key){System.out.println("找到了!");return true;}cur=cur.next;}System.out.println("没找到");return false;}//删除第一次出现关键字为key的节点public void remove(int key){if (head==null){System.out.println("链表中没有元素,无法进行删除");return;}ListNode cur=head;while (cur!=null){if(cur.val==key){//判断是否是链表尾部删除if(cur.next==null){cur.prev.next=null;tail=tail.prev;}//删除中间节点操作cur.prev.next=cur.next;cur.next.prev=cur.prev;}cur=cur.next;}//判断首节点情况if(head.val==key){//如果链表只有一个节点,需要进行特殊处理if(head.next!=null){head=head.next;head.prev=null;}else {head=null;}}}//删除所有值为key的节点public void removeAllKey(int key){if(head==null){return;}ListNode cur=head.next;while(cur!=null){if(cur.val==key){if(cur.next==null){cur.prev.next=null;tail=tail.prev;}else{cur.prev.next=cur.next;cur.next.prev=cur.prev;}}cur=cur.next;}if(head.val==key){if(head.next==null){head=null;}else{head=head.next;head.prev=null;}}}//得到单链表的长度public int size(){int count=0;ListNode cur=head;while(cur!=null){cur=cur.next;count++;}return count;}public void display(){ListNode cur=head;if(head==null){return;}while(cur!=null){System.out.print(cur.val+" ");cur=cur.next;}System.out.println();}public void clear(){ListNode cur=head;if(head==null){return;}while(cur!=null){ListNode curNext=cur.next;cur.prev=null;cur.next=null;cur=curNext;}//处理还在引用的head和tailhead=null;tail=null;}
}

对上述代码进行运行测试:

public class Test {public static void main(String[] args) {MyLinkedList2 myLinkedList2=new MyLinkedList2();myLinkedList2.addLast(1);myLinkedList2.addLast(2);myLinkedList2.addFirst(3);myLinkedList2.addIndex(2,6);myLinkedList2.addIndex(0,5);myLinkedList2.addIndex(1,4);myLinkedList2.display();System.out.println(myLinkedList2.contains(6));myLinkedList2.remove(6);myLinkedList2.display();myLinkedList2.addFirst(1);myLinkedList2.display();myLinkedList2.removeAllKey(1);myLinkedList2.display();System.out.println(myLinkedList2.size());myLinkedList2.clear();myLinkedList2.display();}
}

3.LinkedList的特点

LinkedList的特点主要体现在以下几个方面:

1.数据存储的方式

LinkedList是通过一系列节点组成的,每个节点都包含三个部分,数据元素,指向前一个节点的引用和指向后一个节点的引用,链表的内存是动态分配的,每个节点的内存空间是独立分配的,使得链表的大小灵活可调

2.操作效率

链表头部或者尾部插入和删除元素操作的时间复杂度为O(1),如果已经获得目标节点的引用,进行插入和删除操作的时间复杂度为O(1),但如果没有则需要从头遍历找到目标位置,时间复杂度为O(n)

链表不支持随机访问,方位任意元素都需要从头开始进行遍历,时间复杂度为O(n)

3.内存使用

每个节点除了存取数据元素外,还需要存储引用,有额外的内存开销。链表的内存分配时动态的,不会像数组那样分配固定的大小

4.适用场景

适合插入和删除频繁的场景,不适合随机访问的场景,适合数据量不确定的场景(动态扩展,不过多浪费空间)

4.LinkedList和ArrayList的区别

不同点ArrayListLinkedList
存储空间上物理上连续的逻辑上连续,物理上不一定连续
随机访问支持,时间复杂度O(1)不支持,时间复杂度O(n)
插入/删除需要搬运元素,效率低,时间复杂度O(n)只需要修改引用的指向,时间复杂度O(1)
扩容机制当数组空间不足时,会创建一个更大的数组并复制原数据动态分配内存,每次插入时动态分配新节点
内存使用内存分配连续,可能有空间浪费(预留空间),但内存使用效率较高。每个节点需要额外存储指针,内存使用相对较高。
适用场景元素频繁访问的场景插入删除频繁的场景


http://www.ppmy.cn/news/1564969.html

相关文章

记录一下OpenCV Contrib 编译踩的坑

最近有需要采用OpenCV Contrib 里面的函数做一下处理&#xff0c;要重新编译&#xff0c;一路编译两三个小时了&#xff0c;记录一下备忘吧。 1、编译前先准备好如下环境 ①visual studio已安装&#xff0c;具体版本和型号根据需求经验来&#xff0c;我看常用的是VS2015、VS201…

用行动回应“实体清单”,智谱发布了一系列新模型

1月15日晚间&#xff0c;美国商务部工业和安全局&#xff08;BIS&#xff09;修订了《出口管制条例》&#xff08;EAR&#xff09;&#xff0c;以安全为由在实体清单中分两批增加了25个中国实体。 其中就包括智谱及其子公司&#xff0c;也是国内首家被美国列入实体清单的大模型…

Kubernetes (K8s) 权限管理指南

1. 引言 Kubernetes (K8s) 作为当今最流行的容器编排平台,其安全性至关重要。本指南旨在全面介绍 K8s 的权限管理机制,帮助具有一定基础的读者深入理解并掌握这一关键领域。 © ivwdcwso (ID: u012172506) 2. Kubernetes 安全模型概述 K8s 的安全模型主要包括三个阶段…

使用Python爬虫获取1688网站item_get_company API接口的公司档案信息

一、引言 在当今的商业环境中&#xff0c;获取供应商的详细信息对于采购决策、市场分析和供应链管理至关重要。1688作为中国领先的B2B电子商务平台&#xff0c;提供了丰富的供应商档案信息。通过使用1688的item_get_company API接口&#xff0c;我们可以方便地获取这些信息。本…

nginx实现TCP反向代理

当前实验环境&#xff1a; nginx已安装版本1.11.13 需要动态扩展安装模块nginx_tcp_proxy_module&#xff0c;实现tcp反向代理 实验步骤&#xff1a; 1、nginx当前版本1.11.13&#xff08;nginx已安装&#xff09; # /alidata/nginx/sbin/nginx -v nginx version: nginx/1.1…

OLED--软件I2C驱动__标准库和HAL库

一、标准库---版本一 OLED.c--标准库 #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x…

假设k8s集群规模上千,需要注意的问题有哪些?

在Kubernetes&#xff08;K8s&#xff09;集群规模达到上千个节点时&#xff0c;需要注意的问题相对较为复杂和全面。以下是一些关键的考虑因素和最佳实践&#xff1a; 1. 资源管理 资源配额&#xff1a;设置适当的资源配额&#xff08;Resource Quotas&#xff09;和限制&am…

网络Web存储之LocalStorage

文章目录 LocalStorage介绍定义特点兼容性常用方法存值取值删除指定键值对清空所有键值对通过索引获取键名获取所有值判断是否含有某个键&#xff08;key&#xff09;拓展遍历得到key存储和读取复杂类型的数据 应用场景 LocalStorage介绍 定义 LocalStorage 是HTML5提供的一种…