首页 > 编程语言 >java链表详解 理论+代码+图示

java链表详解 理论+代码+图示

时间:2023-10-17 18:59:13浏览次数:55  
标签:content 图示 java cur head next 链表 type 节点

1、定义

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。(即链表是一个个节点组成的,这些节点物理上不连续,但逻辑上连续) 0 一个节点就是一个Node对象。

2、链表结构

单向、双向;
带头、不带头;
循环、非循环;
  以上情况组合起来就有8种链表结构 (双向) 单向、带头、循环 (双向) 单向、带头、不循环 (双向) 单向、不带头、循环 (双向) 单向、不带头、不循环  

2.1单向或者双向

0

2.2带头或者不带头

0

2.3循环或者非循环

0

2.4常用

虽然有这么多链表结构,但实际我们最常用的还是两种结构: 无头单向非循环链表带头双向循环链表

2.4.1无头单向非循环链表(单链表):

0 结构简单,一般不会单独用来存储数据。实际中更多的是作为其他数据结构的子结构,如哈希桶,图的邻接表等。另外这种结构在笔试面试中很多。   第一个节点称之为头节点,最后一个节点称之尾节点单向链表的尾节点的next域为null,因为尾节点不再指向任何一个节点。

2.4.2带头单向非循环链表

0 用一个专门的节点作为头节点,这个节点没有data数据域,只有next指针域,这个指针用来存储第一个数据节点的地址,且这个头节点是无法删除的,他就像一个哨兵,指向我们第一个数据节点。

2.4.2带头双循环链表:

0 结构最复杂,一般用在单独存储数据。双循环链表的所有后继指针形成了一个环,所有前趋指针也形成了一个环 (一共两个环)。这样的话,给定一个数据结点,无论访问链表的后继结点还是前趋结点,都非常灵活和方便。   

3、单链表代码实现

※注意事项: ①单链表中头节点很烦人,不论是插入还是删除均需考虑头节点,因为其没有前驱。 ②所有的点操作(".")均需注意空指针情况  

自定义链表对象

class ListNode {
        public int val;//存储数据的值
        public ListNode next;//存储下一个节点的地址
        //这里为什么使用Node引用,单链表的所有节点都是Node类的对象

        //构造函数,可以直接创建有值的节点
        public ListNode(int val) {
            //不知道下一个节点位置,只需要val
            this.val = val;
        }
        
        //方便输出查看结果,写一个toString
        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
}


 ListNode node1=new ListNode(); //创建一个新节点
 node1.val=88; //值为88,地址为空
 ListNode node2=new ListNode(7); //创建一个值为7的节点
 node1.next=node2;  //让第一个节点的next存储下一个节点的地址
 

头插法

public static void addFirst(int[] arr){
    ListNode head=null;
    for (int i = 0; i < arr.length; i++) {
        ListNode node=new ListNode();
        node.val=arr[i];
        node.next=head;
        head=node;
    }
    System.out.println(head);
}
 

尾插法

//尾插法
public static void addLast(int[] arr){
    //定义虚拟头指向链表的第一个节点
    ListNode head=null;
    for (int i = 0; i < arr.length; i++) {
        //添加新节点
        ListNode newnode = new ListNode(arr[i]);
        //如果链表为空,头节点直接指向新节点
        if (head == null) {
            head = newnode;
        } else {  //链表不为空
            //定义游标遍历链表,找到尾节点
            ListNode cur = head;
            while (cur.next != null) {
                cur = cur.next;
            }
            //将新节点地址赋给尾节点的next域
            cur.next = newnode;
        }
    }
    输出一下这个链表
    System.out.println(head);
}
 

任意位置插入

static int size=0;
public static void insert(int index,int val){
    if(index<0 || index>size){
        System.out.println("插入位置不合理");
        return;
    }
    //如果链表为空
    if (head==null){
        //头插法
        ListNode node=new ListNode(val);
        node.next=head;
        head=node;
    }else {
        ListNode newnode=new ListNode(val);
        //从头开始遍历,到index的前驱
        ListNode prev=head;
        for (int i = 0; i < index-1; i++) {
            prev=prev.next;
        }
        //先让新节点和后面的节点连接
        newnode.next=prev.next;
        //再让前驱与新节点连接
        prev.next=newnode;
        //添加节点,长度加一
        size++;
    }
    System.out.println(head);
}
 

单链表的头删

为了使用删除功能,我们统一把head写到外边。 0 头删逻辑很简单,直接让记录着头节点的head往后移动一个节点即可。
public static void removeFirst(){
    if(head==null){
        System.out.println("空链表");
    }
    head=head.next;
    System.out.println(head);
}
 

单链表的尾删

同样使用游标遍历整个链表,找到尾部节点,则让尾部节点的前一个节点的next域为空。 这里巧妙的运用了cur.next.next
public static void removeFirst(){
    if(head==null){
        System.out.println("空链表");
    }
    ListNode cur=head;
    while (cur.next.next!=null){
        cur=cur.next;
    }
    cur.next=null;
    System.out.println(head);
}
 

删除指定位置

public static void remove(int index){
    if(head==null){
        System.out.println("空链表");
        return;
    }
    //定义游标遍历到目标前驱位置
    ListNode pre=head;
    for (int i = 0; i < index-1; i++) {
        pre=pre.next;
    }
    //找到前驱位置后,将前驱位置的下一个的下一个赋值给前驱位置的next域(即目标位置的下一个节点地址赋值给前驱的next域)
    cur.next=cur.next.next;
    System.out.println(head);
}
 

删除指定元素(第一个值为val的元素)

public static void remove(int val){
    if(head==null){
        System.out.println("空链表");
        return;
    }
    //如果头节点的值就是所找的值,则使用头删法
    if(head.val==val){
        //头删
        head=head.next;
        System.out.println(head);
        return;
    }
    
    //如果不是头节点,则开始遍历链表
    ListNode pre=head;
    //使用pre.next!=null遍历
    //为什么不使用pre.next.val!=val作为条件遍历? --因为当val不存在在链表中时,会产生空指针的情况
    while(pre.next!=null){
        if(pre.next.val==val){
            //找到的要删除节点
            ListNode x=pre.next;
            pre.next=x.next;
        }
        pre=pre.next;
    }
    //特殊条件,如果将链表遍历到最后也没找到这个值,则return
    if (pre.next==null){
        System.out.println("没有");
        return;
    }
    System.out.println(head);
}
 

删除所有的val值

如果只遍历一遍链表,时间复杂度时On。如果使用上边的方法,想要删掉重复的val值,有几个val则就需要遍历几遍,时间复杂度会非常大。 那么这么实现? ---引入两个变量pre和cur,也就是双指针,但是这两个指针是跟屁虫。   如图,初始状态下,pre的起点是head,二cur的起点是head.next。pre是cur的前一个节点。 在遍历过程中,通过比较cur.val与val值来寻找关键节点。如果说cur.val != val,那么cur和pre指针均需要向前走。 当走到下图这个位置,即cur到了我们需要删除的节点位置时,pre.next 直接指向 cur.next,此时cur所指的节点便成功删除。完成后接着让cur向后走。 注意:当cur.val==val时,我们完成删除一个节点操作后,只需让cur向后走,不急着让pre跟上,因为可能出现两个连续的待删除元素。 而且,如果pre跟上cur的话,原链表没办法重新链接  
 public static void removeAll(int val){
     //特殊条件,链表为空
    if(head==null){
        return;
    }
    //定义两个跟屁虫游标
    ListNode pre=head;
    ListNode cur=head.next;
    //如果第一个节点待删除,则头节点直接后移
    if(head.val==val){
        //头删
        head=head.next;
    }
    //循环查找还有没有需要删除的节点
    //注意这里的判定条件,如果你写cur.next!=null则无法删除最后一个元素,因为到最后一个元素会停止循环
    //判定条件为cur!=null的话会将最后一个元素也进行判定
    while (cur!=null){
        //找到待删除节点,直接让pre.next指向cur.next,从而跳过待删节点
        //cur=cur.next仅让cur后移,跟屁虫pre不紧跟cur,为了判断是否有待删节点连续出现的情况
        if(cur.val==val){
            pre.next=cur.next;
            cur=cur.next;
        }else{  //如果没有找到则两指针都向后移动
            pre=cur;
            cur=cur.next;
        }
    }
    System.out.println(head);
}
  下一篇讲解双链表的代码实现~

标签:content,图示,java,cur,head,next,链表,type,节点
From: https://www.cnblogs.com/nliu/p/17770410.html

相关文章

  • JavaScript中'??'和'?.'
     ??空值合并运算符判断一直变量是否为'null'/'undefined',进行不同的返回值处理console.log(1??2)//1console.log(null??2)//2console.log(undefined??2)//2console.log(1??2??3)//1console.log(null??2??3)//2console.log(null??null??3)//3......
  • Java中::的用法
    “::”是Java8引入的新特性之一,常常被称作为方法引用,提供了一种不执行方法的方法。使用“::”可以进一步简化一些使用了lambda表达式的代码,让代码更加简洁。用法1:省略lamda表达式publicclassTest01{publicstaticvoidmain(String[]args){String[]array......
  • Java开发到部署:从代码到上线的完整指南
    在软件开发领域中,Java一直是最流行和广泛使用的编程语言之一。Java的跨平台性以及强大的生态系统使其成为许多开发人员的首选。本文将介绍Java开发到部署的完整过程,帮助您了解如何将Java应用程序从代码编写到成功部署和上线。1、环境设置在开始Java开发之前,您需要安装JavaDevelo......
  • java基础,java基本数据类型、引用数据类型
    java数据类型基本数据类型:1,整型:byte(1字节),short(2字节),int(4字节),long(8字节)2,浮点型:float(单精度4字节),double(双精度8字节)3,字符型:char(2字节)4,布尔型:boolean(true/false)引用数据类型:1,类class引用例如Object:Object是一个很重要的类,Object是类层次结构的根类,每个类都使用Object作为......
  • JavaScript的数字运算不准的问题
    JavaScript的运算问题存在两方面:第一个表示不准问题:打开浏览器按F12,在Console里,输入0.1+0.2=0.30000000000000004输入91.25*0.7=63.87499999999999 解决这个问题,要用第三方库math.js或decimal.js constmath=require('mathjs');console.log(math.add(0.1,0.2));......
  • iframe实现与父页面跨域隔离的JavaScript 代码沙箱
    这篇文章主要介绍了使用iframe实现与父页面跨域隔离的JavaScript代码沙箱,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪目录正文1.iframe2.dataURL3.将JavaScript代码变成dataURL4.如果需要获取执行结果的话,基于postMessage定制通信机制正文假......
  • Java语言简介
    Java是一种广泛使用的编程语言,由SunMicrosystems(现在是OracleCorporation)于1995年推出。它是一种面向对象的语言,被广泛应用于各种应用程序开发领域,包括桌面应用程序、移动应用程序和企业级应用程序。特点和优势Java语言具有许多特点和优势,使其成为开发人员的首选。1.跨平台性Jav......
  • JavaScript中高阶函数的巧妙运用
    JavaScript中的高阶函数是指可以接受其他函数作为参数或者返回一个函数作为结果的函数,本文介绍了JS中一些高阶函数的妙用,希望对大家有所帮助目录1.接受函数作为参数的高阶函数2.返回函数的高阶函数3.同时接受和返回函数的高阶函数JavaScript中的高阶函数是指可以接受其他函数作为参......
  • day07-java常见加密
    1.Java常见加密1.1隐藏字节TreeMapmap=newTreeMap();map.put("sign",x);#搜索关键字signStringa=newString(newbyte[]{-26,-83,-90,-26,-78,-101,-23,-67,-112});TreeMapmap=newTreeMap();map.put(a,x);#hook机制,找到TreeMap中的put方法,......
  • 享元模式--Java实现
    画类图在围棋中,黑棋和白棋对象均只有一个,但是它们可以在不同的位置进行共享;具体代码实现//Chess.javapackageorg.example.design010;publicabstractclassChess{publicabstractStringgetColor();publicvoidlocate(Coordinatesco){System.out.......