一篇文章带你玩转JAVA单链表

目录
  • 一、链表
    • 1. 概念
    • 2. 结构
  • 二、单向不带头非循环链表
    • 1. 概念及结构
    • 2. 链表的实现
  • 三、链表面试题
  • 四、总结

一、链表

1. 概念

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

上章介绍到顺序表适合用作查询和修改,而不适合用作插入和删除。并且它增容时容易造成空间浪费。而链表则具有以下的特点

适合用作插入和删除随用随取,避免了空间的浪费不适合用作查询和修改

2. 结构

链表其实可以想象成一条被打了一些结的绳子

而实际上,链表就是由一个个节点构成的,只不过每个节点一般有两个域

数值域 data: 里面存储数据

next 域: 里面存储的是下一个节点的地址(是引用变量)

其中

尾节点: 当这个节点的 next 域为 null 时,该节点就是尾节点

头节点: 整个链表的第一个节点就是头节点

实际中的链表结构非常多,通过以下的情况的组合可以有8种链表的结构(比如带头单向循环链表就是一种)

单向、双向

带头节点、不带头

节点循环、非循环

而上图所示,就是一个单向不带头非循环链表。

可能有人会疑问,上述图片中不是存在头节点吗?那为啥它又是一个不带头节点的链表呢?我将上述图片实例稍稍改一下就是带头节点的了

我在原有的头节点前面又多了一个节点,并且这个节点的数值域里面的数据可有可无,因为对其没有影响。而这里面的头节点只是一个标志,标识这个节点是该链表的头

那带头节点的和不带头结点的链表有啥差别呢?

不带头: 这个链表的头节点可能发生改

变带头: 这个链表的头节点不会发生改变

那循环链表又是啥样的呢?我们可以将上述单向不带头非循环链表稍微修改一下

即将尾节点的 next 域存储了头节点的地址

我们知道一个节点一般就两个域,但如果是双向链表,则就有三个域了

数值域 data: 里面存储数据

next 域: 里面存储的是下一个节点的地址(是引用变量)

prev 域: 里面存储的是上一个节点的地址(是引用变量)

我们画一个不带头双向非循环链表就是这样的

接下来我将主要介绍单向不带头非循环链表和双向不带头非循环链表,这两种链表也是经常出现在面试题中的

二、单向不带头非循环链表

1. 概念及结构

上述通过图解,大家应该对单向不带头非循环链表应该有了了解。那么代码中该怎么实现呢?

我们知道,链表应该可以看作一个类,而节点本身其实也可以抽象的看作成一个类

首先对于节点类,代码可以写出这样

class Node{
    public int val;
    public Node next;
    public Node(int val){
        this.val=val;
    }
}

先定义了数值域,再定义了 next 域(由于 next 存储的是下一个节点的地址,故写出上述那样)。而我们初始时可以知道 val,所以可以写个构造函数对 val 进行初始化,而该节点的 next 域初始时是不可能知道的,所以不对 next 做初始化

如果创造一个 Node 对象就可以写出

Node node = new Node(10);

即头节点的数值域为10。

再定义链表类,代码可以写成这样

public class MyLinkedList {
    public Node head;
}

我们定义了一个头节点,这是我们很容易发现的,就比如我要对该链表进行头插,则该链表的头节点一直都在改变。所以写上述的代码就是为了标识单链表的头节点。

2. 链表的实现

接下来我们来实现单向不带头非循环链表的一些操作

打印链表

public void display(){
    Node cur =this.head;
    while(cur!=null){
        System.out.print(cur.val + " ");
        cur=cur.next;
    }
    System.out.println();
}

求链表的长度

public int size(){
    Node cur=this.head;
    int count=0;
    while(cur!=null){
        count++;
        cur=cur.next;
    }
    return count;
}

查找值 key 是否在单链表中

public boolean contains(int key){
    Node cur=this.head;
    while(cur!=null){
        if(key==cur.val){
            return true;
        }
        cur=cur.next;
    }
    return false;
}

清除单链表

public void clear(){
    this.head.val=0;
    this.head.next=null;
}

头插法

public void addFirst(int data){
    Node node=new Node(data);
    if(head==null){
        this.head=node;
    }else {
        node.next = this.head;
        this.head = node;
    }
}

尾插法

public void addLast(int data){
    Node node = new Node(data);
    if(this.head==null){
        this.head=node;
    }else {
        Node cur = this.head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }
}

通过下标找到前驱节点(第一个节点下标为0)

public Node searchPrev(int index){
    Node cur = this.head;
    int count=0;
    while(count!=index-1){
        cur=cur.next;
        count++;
    }
    return cur;
}

任意位置插入

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;
    }
    Node node =new Node(data);
    Node cur =searchPrev(index);
    node.next=cur.next;
    cur.next=node;
}

通过值 key 找到前驱节点(第一个节点下标为0)

public Node searchPrevNode(int key){
    Node cur = this.head;
    while(cur.next!=null){
        if(cur.next.val==key) {
            return cur;
        }
        cur=cur.next;
    }
    return null;
}

删除第一次出现的值为 key 的节点

public void remove(int key){
    if(this.head==null){
        return;
    }
    if(key==this.head.val){
        this.head=this.head.next;
        return;
    }
    Node node = searchPrevNode(key);
    if(node==null){
        System.out.println("链表中无要删除的元素");
    }else {
        node.next = node.next.next;
    }
}

删除所有出现的值为 key 的节点

public void removeAllKey(int key){
    if(this.head==null){
        return;
    }
    Node prev=this.head;
    Node cur=this.head.next;
    while(cur!=null){
        if(cur.val==key){
            cur=cur.next;
            prev.next=cur;
        }else{
            prev=cur;
            cur=cur.next;
        }
    }
    if(this.head.val==key){
        this.head=this.head.next;
    }
}

呼,写的我人傻了

你以为结束了吗?NO!下面还有一大波链表面试题等着我和你,冲吧,牛牛!

三、链表面试题

删除链表中等于给定值 val 的所有节点 OJ 链接

反转一个单链表 OJ 链接

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点 OJ 链接

输入一个链表,输出该链表中倒数第k个结点 OJ 链接

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的 OJ 链接

编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 OJ 链接

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针 OJ 链接

链表的回文结构 OJ 链接

输入两个链表,找出它们的第一个公共结点 OJ 链接

给定一个链表,判断链表中是否有环 OJ 链接

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null OJ 链接

如果你已经可以轻松应对上述题目了,可以继续去下面的链接中做题 Leetcode OJ 链接 和 牛客 OJ 链接

四、总结

今天简单了解了链表,以及对单向不带头非循环链表做了一些基操的实现。下章将包含个人的上述前11道 OJ 题的题解,以及了解双向不带头非循环链表

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • java数据结构基础:单链表与双向链表

    目录 单链表: 实现思路: 代码实现: 双向链表: 实现思路: 代码实现: 总结 单链表: 每个数据是以节点的形式存在的 每个节点分为数据域和指针域 数据域中保存该节点的数据 指针域中保存指向下一个节点的指针 实现思路: 节点类SingleNode中保存数据和指向下一个节点的指针 单链表类SingleLinkedList中保存链表的头节点,实现相关链表方法 对于链表方法,涉及到位置查找,如在指定位置增加.删除节点,需要使用一个临时变量temp从头节点开始遍历,直至找到对应的位置. 对于节点的增加

  • Java之单链表问题解决案例讲解

    单链表 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. 链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据. 问题 问题1:给定一个单链表,判断链表中是否有环 问题2:给定一个链表,返回链表开始入环的第一个节点,如果无环,则返回null class Node{ public int data; Node next; public Node(int da

  • Java如何实现单链表的增删改查

    一.新建学生节点类 Stu_Node节点包含: 学号:int num; 姓名:String name; 性别:String gender; 下一个节点:Stu_Node next; 为了便于打印节点内容需要重写toString方法 class Stu_Node{ int num; String name; String gender; Stu_Node next; @Override public String toString() { return "Stu_Node{" + &qu

  • Java实现单链表反转的多种方法总结

    对于单链表不熟悉的可以看一下基于Java实现单链表的增删改查 一.原地反转 1.新建一个哨兵节点下一结点指向头结点 2.把待反转链表的下一节点插入到哨兵节点的下一节点 反转之前的链表:1–>2–>3–>4>–>5 加入哨兵节点:dummp–>1–>2–>3–>4>–>5 原地反转: 定义:prev=dummp.next; pcur=prev.next; prev.next=pcur.next; pcur.next=dummp.next; d

  • Java数据结构之单链表详解

    一.图示 二.链表的概念及结构 链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 . 实际中链表的结构非常多样,以下情况组合起来就有8种链表结构: 单向.双向 带头.不带头 循环.非循环 今天,我们实现的是一个 单向 无头 非循环的链表. 下面是此链表的结构组成. 三.单链表的实现 (1)定义一个节点类型 我们创建一个 ListNode 的类作为节点类型,那么我们如何定义成员属性呢? 通过上面的结构分析,我们需要定义两个成员变量 val --作为该节点的

  • 一篇文章带你玩转JAVA单链表

    目录 一.链表 1. 概念 2. 结构 二.单向不带头非循环链表 1. 概念及结构 2. 链表的实现 三.链表面试题 四.总结 一.链表 1. 概念 链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 上章介绍到顺序表适合用作查询和修改,而不适合用作插入和删除.并且它增容时容易造成空间浪费.而链表则具有以下的特点 适合用作插入和删除随用随取,避免了空间的浪费不适合用作查询和修改 2. 结构 链表其实可以想象成一条被打了一些结的绳子 而实际上,链表就是由一

  • 一篇文章带你搞懂Java线程池实现原理

    目录 1. 为什么要使用线程池 2. 线程池的使用 3. 线程池核心参数 4. 线程池工作原理 5. 线程池源码剖析 5.1 线程池的属性 5.2 线程池状态 5.3 execute源码 5.4 worker源码 5.5 runWorker源码 1. 为什么要使用线程池 使用线程池通常由以下两个原因: 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程. 使用线程池可以更容易管理线程,线程池可以动态管理线程个数.具有阻塞队列.定时周期执行任务.环境隔离等. 2. 线程池的使用 /** *

  • 一篇文章带你搞定JAVA反射

    目录 1.反射的概念 1.概念 2.获取字节码文件对象的方式 2.1 元数据的概念 2.2 获取class对象的方式 1.访问权限 2.获取方法 2.1 访问静态方法 2.2 访问类方法 3.获取字段,读取字段的值 4.获取实现的接口 5.获取构造函数,创建实例 6.获取继承的父类 7.获取注解 4.反射实例 5.总结 1.反射的概念 1.概念 反射,指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法

  • 一篇文章带你深入了解Java对象与Java类

    目录 1.面向对象是什么? 2.Java类 1.什么是类 2.Java类 类的结构 Java类的格式 3.java对象 4.类和对象 5.类中的变量,方法 1.变量分类 成员变量: 局部变量: 2.方法分类 6.方法重载 7.对象与引用 基本类型和引用类型的区别: 值传递与引用传递 8.static关键字 概念 static属性 static方法 代码块 9.类的加载执行 10.包 包的概念: 包的作用: 包(package)的命名规范: 访问权限修饰符 11.面向对象语言的三大特征 1.封装

  • 一篇文章带你深入了解Java基础(2)

    目录 1.Java主要特点 2.计算机的高级汇编语言类型: 3.JVM(Java Visual Machine) 4.编写第一个Java程序并运行 5.CLASSPATH指的是类加载路径 6.程序注释,对以后的所有代码都要进行注释,主页可以方便进行开发需求 7.标识符和关键字 8.Java数据类型的划分以及数据类型的操作 9.运算符 自增.自减操作 总结 1.Java主要特点 简单性.跨平台性.分布性.安全性.健壮性.平台独立与可移植性.多线程.动态性.面向对象的编程语言.支持垃圾自动收集处理等

  • 一篇文章带你搞定JAVA泛型

    目录 1.泛型的概念 2.泛型的使用 3.泛型原理,泛型擦除 3.1 IDEA 查看字节码 3.2 泛型擦除原理 4.?和 T 的区别 5.super extends 6.注意点 1.静态方法无法访问类的泛型 2.创建之后无法修改类型 3.类型判断问题 4.创建类型实例 7.总结 1.泛型的概念 泛型的作用就是把类型参数化,也就是我们常说的类型参数 平时我们接触的普通方法的参数,比如public void fun(String s):参数的类型是String,是固定的 现在泛型的作用就是再将St

  • 一篇文章带你搞定JAVA注解

    目录 1.注解是什么 2.jdk支持的注解有哪些 2.1 三种常用的注解: 2.2 元注解 3.注解实例 1.自定义注解 2.在对应的方法上增加注解 3.在项目启动的时候检查注解的枚举 4.总结 1.注解是什么 Java 注解用于为 Java 代码提供元数据,看完这句话也许你还是一脸懵逼,用人话说就是注解不直接影响你的代码执行,仅提供信息.接下我将从注解的定义.元注解.注解属性.自定义注解.注解解析JDK 提供的注解这几个方面再次了解注解(Annotation) 2.jdk支持的注解有哪些 2.

  • 一篇文章带你深入了解Java类加载

    目录 1.类加载 <1>.父子类执行的顺序 <2>类加载的时机 <3>类的生命周期 <4>类加载的过程 <5>类加载器 1.启动类加载器(BootstrapClassLoader) 2.扩展类加载器(ExtClassLoader) 3.应用程序类加载器(AppClassLoader) 4.2 自定义加载器 <6>类加载机制--双亲委派模型 总结 1.类加载 <1>.父子类执行的顺序 1.父类的静态变量和静态代码块(书写顺序

  • 一篇文章带你深入了解Java基础

    目录 1.String类 1.1两种对象实例化方式 1.2字符串比较 1.3字符串常量是String的匿名对象 1.4String两种实例化方式区别 1.分析直接赋值方式 2.构造方法赋值 1.5字符串常量不可改变 1.6开发中String必用 1.7字符串和字符数组 1.9字符串比较 1.11字符串的替换 1.12字符串的拆分 1.12字符串的截取 1.13其他操作方法 2.1. 给定一个email地址,要求验证其是否正确,提示:可以简单的验证一下,重点验证"@"和".&q

  • 一篇文章带你深入了解Java基础(3)

    目录 1.方法的基本定义 2.方法重载 3.方法的递归调用 4.面向对象的前身是面向过程 5.类与对象 总结 1.方法的基本定义 限制条件:本次所讲解的方法指的是在主类中定义,并且由主方法由主方法直接调用. 方法是指就是一段可以被重复调用的代码块. 在java里面如果想要进行方法的定义,则可以使用如下的方法进行完成. public static 方法返回值 方法名称([参数类型 变量,....]){ 方法体代码 ; return [返回值]; } 在定义方法的时候对于方法的返回值由以下两类:vo

随机推荐