【数据结构与算法】模拟实现LinkedList类

简介: Java LinkedList(链表)类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。

LinkedList简介


Java LinkedList(链表)类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。

LinkedList底层是一个双向链表,如下:

10.png

一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接

双向链表有头节点和尾结点。

因此要实现双向链表,我们就要用类将双向链表的各个部分给抽取出来

  static class ListNode{

       public int val;//数据域

       public ListNode prev;//保存前一个节点的地址

       public ListNode next;//保存后一个结点的地址

       public ListNode(int val) {

           this.val = val;

       }

   }

   public ListNode head;//头节点

   public ListNode tail;//尾结点


头插法创建链表


要用头插法建立双向链表,与单链表不同,双向链表的每一个结点都有三个域,因此我们要想清楚改哪个地方,看下图:

11.png

由图我我们可知,我们要改变插入结点的next域,要改变头节点的prev域,然后将插入结点的prev置为null,最后让头节点指向插入的结点,这样在头结点插入一个结点就完成了。

但这个只是一般情况下插入,还有一种特殊情况,就是遇到空链表。

如果是空链表的情况,那么头节点就是要插入的这个结点,尾节点也是要插入的这个结点。

  public void addFirst(int data){

       ListNode node = new ListNode(data);

       //空链表

       if(this.head == null){

           this.head = node;

           this.tail = node;

       }else {

           node.next = this.head;

           this.head.prev = node;

           this.head = node;

       }

   }


尾插法创建链表


13.png

看上图,我们可知用尾插法插入一个结点,要改变尾结点的next域,将要插入的next域置为空,将插入结点的prev域指向尾结点,再将尾节点指向要插入的结点就可以了。

同时也不要忘记考虑空链表的情况,与头插法相同,如果为空,那么要插入的这个结点就既是头结点也是尾结点。

  public void addLast(int data){

       ListNode node = new ListNode(data);

       //空链表

       if(this.head == null){

           this.head = node;

           this.tail = node;

       }else {

           this.tail.next = node;

           node.prev = this.tail;

           this.tail = node;

       }

   }


任意位置插入,第一个数据节点为0号下标


在插入数据时,我们首先要对要插入的位置index进行判断,判断index是否合法,index 位置合法才能插入数据。其次我们要考虑index在特殊位置,例如:在头节点和尾结点进行插入数据。

  /**

    * 任意位置插入,第一个数据节点为0号下标

    * @param index

    * 要对index位置判断是否合法

    * index在特殊位置 -头插和尾插

    * @param data

    * @return

    */

   public boolean addIndex(int index,int data){

       if(index<0 || size()<index){

           System.out.println("index位置不合法");

           return false;

       }

       if(index == 0){

           addFirst(data);

           return true;

       }

       if(index == size()){

           addLast(data);

           return true;

       }

       ListNode node = new ListNode(data);

       ListNode cur = findIndexListNode(index);

       node.next = cur;

       cur.prev.next = node;

       node.prev = cur.prev;

       cur.prev = node;

       return false;

   }

   public ListNode findIndexListNode(int index){

       ListNode cur = this.head;

       while(index != 0){

           cur = cur.next;

           index--;

       }

       return cur;

   }


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


查找是否包含关键字key,也就是遍历链表,与各个节点的数据域进行比较,如果相等就返回true,如果不相等,就返回false。

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

   public boolean contains(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               return true;

           }

           cur= cur.next;

       }

       return false;

   }


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


删除双向链表的节点,首先要明确我们要改那些值,看下图:

14.png

假设删除的是值为3的结点,那么此时要改变两个地方的值,就是删除结点前一个结点的next,以及后一个结点的prev.改完之后,就可以让值为2的结点直接找到值为4的结点,值为4的节点也可以直接找到值为2的结点.

然后我们还要考虑到删除的结点在特殊的位置,有两种情况:1.删除的结点在头节点或者是尾结点 2.只有一个结点

   /**

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

    * 可能会遇到的特殊情况

    *    1.删除的节点在头或者尾

    *    2.只有一个结点

    * @param key

    */

   public void remove(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               if(cur==head){//头节点

                   if(cur.next==null){//只有一个结点

                       this.head = null;

                       this.tail = null;

                       return;

                   }

                   this.head = head.next;

                   this.head.prev = null;

               }else{

                   cur.prev.next = cur.next;

                   if(cur.next != null){

                       cur.next.prev = cur.prev;

                   }else{//删除的是尾结点的情况

                       this.tail = cur.prev;

                   }

               }

               return;

           }else{

               cur = cur.next;

           }

       }

   }


删除所有值为key的节点


删除所有为key的结点,只需要在前面删除一个为key的结点代码上,把return删了就可以了.

  //删除所有值为key的节点

   public void removeAllKey(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               if(cur==head){

                   if(cur.next==null){

                       this.head = null;

                       this.tail = null;

                       return;

                   }

                   this.head = head.next;

                   this.head.prev = null;

               }else{

                   cur.prev.next = cur.next;

                   if(cur.next != null){

                       cur.next.prev = cur.prev;

                   }else{

                       this.tail = cur.prev;

                   }

               }

           }

           cur = cur.next;

       }

   }


得到链表的长度


得到链表的长度,实际上就相当于遍历了一边链表,如果当前节点不为空count就+1,最后return count就可以了。

  //得到单链表的长度

   public int size(){

       ListNode cur = head;

       int count = 0;

       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;

       }

   }


清空链表


清空双向链表,不能直接将头节点和尾节点置为null

因为这是双向链表,直接置为null也可能会有别的结点引用,所以双向链表只能一个一个置空

   public void clear(){

       //error

       /*this.head = null;

       this.tail = null;*/

       ListNode cur= this.head;

       while(cur != null){

           ListNode curNext = cur.next;

           cur.prev = null;

           cur.next = null;

           cur = curNext;

       }

       this.head = null;

       this.tail = null;

   }


完整代码:


import java.util.List;

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

   /**

    * 无头双向链表实现

    * @param data

    */

   //头插法

   public void addFirst(int data){

       ListNode node = new ListNode(data);

       //空链表

       if(this.head == null){

           this.head = node;

           this.tail = node;

       }else {

           node.next = this.head;

           this.head.prev = node;

           this.head = node;

       }

   }

   //尾插法

   public void addLast(int data){

       ListNode node = new ListNode(data);

       //空链表

       if(this.head == null){

           this.head = node;

           this.tail = node;

       }else {

           this.tail.next = node;

           node.prev = this.tail;

           this.tail = node;

       }

   }

   /**

    * 任意位置插入,第一个数据节点为0号下标

    * @param index

    * 要对index位置判断是否合法

    * index在特殊位置 -头插和尾插

    * @param data

    * @return

    */

   public boolean addIndex(int index,int data){

       if(index<0 || size()<index){

           System.out.println("index位置不合法");

           return false;

       }

       if(index == 0){

           addFirst(data);

           return true;

       }

       if(index == size()){

           addLast(data);

           return true;

       }

       ListNode node = new ListNode(data);

       ListNode cur = findIndexListNode(index);

       node.next = cur;

       cur.prev.next = node;

       node.prev = cur.prev;

       cur.prev = node;

       return false;

   }

   public ListNode findIndexListNode(int index){

       ListNode cur = this.head;

       while(index != 0){

           cur = cur.next;

           index--;

       }

       return cur;

   }

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

   public boolean contains(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               return true;

           }

           cur= cur.next;

       }

       return false;

   }

   /**

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

    * 可能会遇到的特殊情况

    *    1.删除的节点在头或者尾

    *    2.只有一个结点

    * @param key

    */

   public void remove(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               if(cur==head){

                   if(cur.next==null){

                       this.head = null;

                       this.tail = null;

                       return;

                   }

                   this.head = head.next;

                   this.head.prev = null;

               }else{

                   cur.prev.next = cur.next;

                   if(cur.next != null){

                       cur.next.prev = cur.prev;

                   }else{

                       this.tail = cur.prev;

                   }

               }

               return;

           }else{

               cur = cur.next;

           }

       }

   }

   //删除所有值为key的节点

   public void removeAllKey(int key){

       ListNode cur = this.head;

       while(cur != null){

           if(cur.val == key){

               if(cur==head){

                   if(cur.next==null){

                       this.head = null;

                       this.tail = null;

                       return;

                   }

                   this.head = head.next;

                   this.head.prev = null;

               }else{

                   cur.prev.next = cur.next;

                   if(cur.next != null){

                       cur.next.prev = cur.prev;

                   }else{

                       this.tail = cur.prev;

                   }

               }

           }

           cur = cur.next;

       }

   }

   //得到单链表的长度

   public int size(){

       ListNode cur = head;

       int count = 0;

       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;

       }

   }

   public void clear(){

       //error

       this.head = null;

       this.tail = null;

      /* ListNode cur= this.head;

       while(cur != null){

           ListNode curNext = cur.next;

           cur.prev = null;

           cur.next = null;

           cur = curNext;

       }

       this.head = null;

       this.tail = null;*/

   }

}


总结:


LinkedList作为Java中的一个重要集合,底层是个双向链表,我只是模拟实现了一些简单的功能,大家如果要研究LinkedList底层到底是怎么实现的,大家开始去看源码比较好.

相关文章
|
16天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
1月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
32 4
|
1月前
|
Java C++ 索引
让星星⭐月亮告诉你,LinkedList和ArrayList底层数据结构及方法源码说明
`LinkedList` 和 `ArrayList` 是 Java 中两种常见的列表实现。`LinkedList` 基于双向链表,适合频繁的插入和删除操作,但按索引访问元素效率较低。`ArrayList` 基于动态数组,支持快速随机访问,但在中间位置插入或删除元素时性能较差。两者均实现了 `List` 接口,`LinkedList` 还额外实现了 `Deque` 接口,提供了更多队列操作。
23 3
|
1月前
|
存储 缓存 索引
从底层数据结构和CPU缓存两方面剖析LinkedList的查询效率为什么比ArrayList低
本文详细对比了ArrayList和LinkedList的查询效率,从底层数据结构和CPU缓存两个方面进行分析。ArrayList基于动态数组,支持随机访问,查询时间复杂度为O(1),且CPU缓存对其友好;而LinkedList基于双向链表,需要逐个节点遍历,查询时间复杂度为O(n),且CPU缓存对其帮助不大。文章还探讨了CPU缓存对数组增删操作的影响,指出缓存主要作用于读取而非修改。通过这些分析,加深了对这两种数据结构的理解。
36 2
|
3月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
40 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
3月前
|
数据采集 算法 数据可视化
基于K-Means聚类算法对球员数据的聚类分析,可以自主寻找最优聚类数进行聚类
本文介绍了一个基于K-Means聚类算法的NBA球员数据分析项目,该项目通过采集和分析球员的得分、篮板、助攻等统计数据,使用轮廓系数法和拐点法确定最优聚类数,将球员分为不同群组,并提供了一个可视化界面以便直观比较不同群组的球员表现。
基于K-Means聚类算法对球员数据的聚类分析,可以自主寻找最优聚类数进行聚类
|
4月前
创建KNN类
【7月更文挑战第22天】创建KNN类。
33 8
|
4月前
|
算法 数据库
|
5月前
|
数据采集 算法 安全
CVPR 2024:给NeRF开透视眼!稀疏视角下用X光进行三维重建,9类算法工具包全开源
【6月更文挑战第28天】CVPR 2024亮点:SAX-NeRF框架开源!融合X光与NeRF,提升3D重建效果。X3D数据集验证,Lineformer+MLG策略揭示物体内部结构,增强几何理解。虽有计算成本及泛化挑战,但为计算机视觉和医学影像开辟新路径。[论文链接](https://arxiv.org/abs/2311.10959)**
171 5
|
5月前
|
存储 Java 索引
【Java】LinkedList vs. ArrayList:Java中的数据结构选择
【Java】LinkedList vs. ArrayList:Java中的数据结构选择
29 3