【leetcode 力扣刷题】链表基础知识 基础操作

链表基础知识 基础操作

  • 链表基础操作
    • 链表基础知识
    • 插入节点
    • 删除节点
    • 查找节点
  • 707. 设计链表
    • 实现:单向链表:
    • 实现:双向链表

链表基础操作

链表基础知识

在数据结构的学习过程中,我们知道线性表【一种数据组织、在内存中存储的形式】是线性结构的,其中线性表包括顺序表和链表。数组就是顺序表,其各个元素在内存中是连续存储的。
链表则是由数据域指针域组成的结构体构成的,数据域是一个节点的数据,指针域存储下一个节点的地址,下一个节点依靠上一个节点的指针域寻址。给出整个链表的头节点地址(指针)head后,就能依次访问链表的每个节点。
链表定义:

链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该节点还有一个元素和一个指向另一条链表的引用。

根据链表间节点的指向,链表可以分为以下三种:

  • 单向链表:指针域中只有指向下一个节点的地址;只能单向遍历链表;
    在这里插入图片描述
  • 双向链表:指针域中有两个指针,一个指向前一个节点,一个指向下一个节点;可以双向遍历链表;
    在这里插入图片描述
  • 循环链表:链表形成环,最后一个节点的指针域不赋值为null,而是指向头节点head;
    在这里插入图片描述

定义一个链表节点,是单向的链表:

	//定义一个结构体ListNode表示链表节点
   struct ListNode {
        int val;   //数据域,这里用的int,根据实际情况选择type,比如char、float等
        ListNode *next; //指针域,存下一个节点的地址,所以是指针类型,并且下一个节点也是该结构体,所以是ListNode*
        //自定义构造函数,给数据域和指针域赋值
        ListNode() : val(0), next(nullptr) {}
        ListNode(int x) : val(x), next(nullptr) {}
        ListNode(int x, ListNode *next) : val(x), next(next) {}
    };

链表中头节点直接用head指针访问,其他节点用currentNode->next的指针访问,可见链表的头节点是特殊的。在插入、删除操作中,针对头节点都需要做特殊的处理,会较麻烦,因此在头节点前增加一个虚拟头节点【也叫附加头节点、哨兵节点等】dummy_head,虚拟头节点的指针域存head,即dummy_head->next = head;数据域无效,因为后续不会用到。给定dummy_head后,其他节点都用当前节点的next指针访问,即currentNode->next:
在这里插入图片描述
接下来介绍对链表进行的一些操作【穿针引线法】:

插入节点

在链表中的一个位置插入节点,需要先断开插入位置前后两个节点的链接,再和这两个节点建立新的链接:先cur->next = pre->next;再pre->next = cur; 如果先pre->next = cur,就会丢失sur这个节点的地址。
在这里插入图片描述
上面是普通的情况,那么针对头节点,尾节点的特殊情况呢? 末尾节点其实也没有什么特殊的,只是suc是NULL,也就是pre->next为NULL,按照上述的两步操作也是ok的。问题是在头节点前插入:

  • 如果是普通链表,头节点前什么都没有,pre是NULL的。只进行①:cur->next = head,head = cur【头节点是新插入的节点】;
  • 如果前面有虚拟头节点,那么pre就是dummy_head,头节点和其他节点是一样的操作;

删除节点

从链表中删除节点,可以是删除指定位置的,比如删除第三个节点;也可以是根据节点值删除的,比如删除值等于target的节点。删除节点时,将被删除节点的前面节点和后面节点连接起来的同时,断开被删除节点和其前面一个、后面一个节点的连接,并且要释放掉被删除节点的空间:pre->next = cur->next;delete cur。
在这里插入图片描述
针对头节点和尾节点的特殊情况呢? 将尾节点看作是下一个节点是null的节点,处理和其他节点一样。问题同样是头节点,删除头节点的话:

  • 普通链表,直接tmp = head->next,delete head,head = tmp;
  • 添加了虚拟头节点的链表,删除头节点,其pre = dummy_head,cur = head;直接按照正常节点删除即可。
    从删除头节点和在头节点前插入节点的分析就可以知道,添加了虚拟头节点dummy_head后,对头节点的操作不需要单独讨论,所有节点操作一致。

查找节点

比如按位置查找某个节点,返回其节点值;比如按值查找链表中是否存在值等于target的节点。因为链表是通过节点的指针域而将各个节点连接起来的,它不能像数组一样直接按下标查找【数组各元素在内存空间是连续存储的,通过下标就能够定位到内存空间】。要查找链表中某个节点,需要遍历链表,需要O(n)的时间复杂度。

707. 设计链表

题目链接:707.设计链表
题目内容:
在这里插入图片描述
理解题意:实际上就是实现一个链表类,可以单向也可以双向,其中要涉及到插入节点:在头部插入、在尾部插入、根据下标index在指定位置插入;删除节点;根据下标index获取节点值。

实现:单向链表:

以下代码实现的是有虚拟头节点的单向链表,并且在类中定义一个_size变量存储链表中节点数量,以便根据下标index删除、添加节点时,判断下标是否合理。 另外在节点末尾处添加节点,可认为是index = _size时,在index处插入节点:

class MyLinkedList {
private:
	//定义类的私有变量
    int _size;   //链表中节点数量,不包括虚拟头节点
    //定义链表节点结构体
    struct ListNode {
        int val;   //数据域
        ListNode *next;  //指针域
        //构造函数
        ListNode() : val(0), next(nullptr) {}
        ListNode(int x) : val(x), next(nullptr) {}
        ListNode(int x, ListNode *next) : val(x), next(next) {}
    };
   //链表附加头节点,整个链表的开始
   ListNode* _dummyhead;
public:    
	//自定义类的构造函数
    MyLinkedList() {
        _dummyhead = new ListNode(0); //新建虚拟头节点
        _size = 0; //初始化链表中有效节点数量为0
    }
    //根据下标返回节点value
    int get(int index) {
        //下标无效
        if(index >= _size)
            return -1;
        ListNode* currNode = _dummyhead; //从虚拟头节点开始访问
        //遍历到下标为index的节点
        for(int i = 0; i <= index; i++){
            currNode = currNode->next;
        }        
        return currNode->val;  //返回value
    }
    //在头部添加节点
    void addAtHead(int val) {
        ListNode * newNode = new ListNode(val); //先构造一个新节点
        newNode->next = _dummyhead->next; //直接在虚拟头节点后插入
        _dummyhead->next = newNode;
        _size++;   //插入节点后,链表节点数量+1
    }
    //在尾部添加节点 
    //实际上就是在index为_size的地方添加节点
    void addAtTail(int val) {
        addAtIndex(_size,val);
    }
    //在指定下标index位置处插入节点
    void addAtIndex(int index, int val) {
        if(index > _size) return ;  //下标不合理
        ListNode* newNode = new ListNode(val); //构造一个新节点
        ListNode* prevNode = _dummyhead;
        //找到需要插入的地方
        while(index)  {
            prevNode = prevNode->next;
            index--;
        }   
        //插入节点   
        newNode->next = prevNode->next;
        prevNode->next = newNode;
        _size++;  //节点数量++
    }
    //删除指定位置的节点
    void deleteAtIndex(int index) {
        if(index >= _size) return;  //下标不合理
        ListNode* prevNode = _dummyhead;
        //找到要删除的节点的前一个节点
        while(index){
            prevNode = prevNode->next;
            index--;
        }
        //tmp为要删除的节点
        ListNode* tmp = prevNode->next;
        //要删除节点前后两个节点建立新连接
        prevNode->next = tmp->next;
        //删除tmp节点,释放空间
        delete tmp;
        _size--; //链表中节点数量-1;
    }
};

实现:双向链表

双向链表就是每个节点有两个指针,一个指向前驱节点(preNode),一个指向后驱节点(succNode),插入、删除节点时,需要对两个指针的指向都处理:

  • 插入一个节点时,preNode->next = newNode; newNode->next = succNode; succNode->prior = newNode; newNode->prior = preNode;
  • 删除一个节点时:preNode->next = succNode;succNode->prior = preNode;
    这里需要注意的是,succNode实际上是currNode->next,如果是删除最后一个节点或者在最后一个节点后追加一个节点,succNode=NULL,为了和其他节点统一操作,同样在末尾增加一个虚拟尾节点
    双向链表可以双向遍历,在index位置插入、删除、查询节点值时,可以先判断index是在链表前半段还是后半段,确定是从前往后遍历更快,还是从后往前遍历更快。 代码如下(C++):
class MyLinkedList {
private:
    int _size;   //链表中有效节点数量,不包括虚拟头节点、虚拟尾节点
    struct ListNode {  //定义链表节点结构体
        int val;
        ListNode *next, *prior;  //双向链表需要有两个指针
        ListNode() : val(0), next(nullptr), prior(nullptr) {}
        ListNode(int x) : val(x), next(nullptr), prior(nullptr) {}
        ListNode(int x, ListNode *next, ListNode *prior) : val(x), next(next), prior(prior) {}
    };
   ListNode *_dummyhead, *_dummytail; //虚拟头节点和虚拟尾节点
public:    
    MyLinkedList() {
        _dummyhead = new ListNode(0); //虚拟头节点
        _dummytail = new ListNode(0); //虚拟尾节点
        //建立虚拟头节点和虚拟尾节点之间的连接
        _dummyhead->next = _dummytail;  
        _dummytail->prior = _dummyhead;
        _size = 0; //链表中有效节点数量
    }
    
    int get(int index) {
        //下标无效
        if(index >= _size)
            return -1;
        ListNode *currNode;
        //判断index在前半段
        if(index < _size/2){
            currNode = _dummyhead;
            //从前往后遍历更快
            for(int i = 0; i <= index; i++){
                currNode = currNode->next;
            } 
        }
        else{ //index在后半段
            currNode = _dummytail;
            //从后往前遍历更快
            for(int i = _size-1; i >= index ;i--){
                currNode = currNode->prior;
            }
        }
        return currNode->val;
    }
    //在头部添加节点,即在虚拟头节点后插入
    void addAtHead(int val) {
        ListNode *newNode = new ListNode(val);
        //建立四条新的连接
        _dummyhead->next->prior = newNode;
        newNode->next = _dummyhead->next;
        _dummyhead->next = newNode;
        newNode->prior = _dummyhead;
        _size++;
    }
    //在尾部插入节点,等于在index = _size的位置插入节点
    void addAtTail(int val) {
        addAtIndex(_size,val);
    }
    //在指定index处插入
    void addAtIndex(int index, int val) {
        if(index > _size) return ;
        ListNode* newNode = new ListNode(val);
        ListNode *prevNode = _dummyhead, *succNode = _dummytail;
        if(index < _size/2){ //从前往后更快定位到index前的节点
            for(int i = 0; i < index; i++){
                prevNode = prevNode->next;
            }
            succNode = prevNode->next;
        }
        else{ //从后往前更快定位到index前的节点
            for(int i = _size - 1; i >= index; i--){
                succNode = succNode->prior;
            }
            prevNode = succNode->prior;
        }
        //插入一个节点后,新增四条连接
        succNode->prior = newNode;     
        newNode->next = succNode;
        prevNode->next = newNode;
        newNode->prior = prevNode;
        _size++;
    }
    //删除index处的节点
    void deleteAtIndex(int index) {
        if(index >= _size) return;
        ListNode *prevNode = _dummyhead, *succNode = _dummytail;
        //根据index和_size的关系,决定从前往后遍历还是从后往前遍历
        if(index < _size/2){
            for(int i = 0; i < index; i++){
                prevNode = prevNode->next;
            }
            succNode = prevNode->next->next;
        }
        else{
            for(int i = _size - 1; i > index; i--){
                succNode = succNode->prior;
            }
            prevNode = succNode->prior->prior;
        }
        ListNode* tmp = prevNode->next;
        //preNode和succNode之间建立双向连接
        prevNode->next = succNode;        
        succNode->prior = prevNode;
        delete tmp;
        _size--;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/91818.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

django的简易的图书管理系统jsp书店进销存源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 django的简易的图书管理系统 系统有1权限&#xff1a…

Redis的基本操作

文章目录 1.Redis简介2.Redis的常用数据类型3.Redis的常用命令1.字符串操作命令2.哈希操作命令3.列表操作命令4.集合操作命令5.有序集合操作命令6.通用操作命令 4.Springboot配置Redis1.导入SpringDataRedis的Maven坐标2.配置Redis的数据源3.编写配置类&#xff0c;创还能Redis…

如何在VSCode中将html文件打开到浏览器

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

springboot整合jdbctemplate教程

这篇文章介绍一下springboot项目整合jdbctemplate的步骤&#xff0c;以及通过jdbctemplate完成数据库的增删改查功能。 目录 第一步&#xff1a;准备数据库 第二步&#xff1a;创建springboot项目 1、创建一个springboot项目并命名为jdbctemplate 2、添加spring-jdbc和项目…

【30天熟悉Go语言】11 数组的全方位使用与解析

作者&#xff1a;秃秃爱健身&#xff0c;多平台博客专家&#xff0c;某大厂后端开发&#xff0c;个人IP起于源码分析文章 &#x1f60b;。 源码系列专栏&#xff1a;Spring MVC源码系列、Spring Boot源码系列、SpringCloud源码系列&#xff08;含&#xff1a;Ribbon、Feign&…

睿趣科技:抖音开网店要怎么找货源

在当今数字化的时代&#xff0c;电商平台的兴起为越来越多的人提供了开设网店的机会&#xff0c;而抖音作为一个充满活力的短视频平台&#xff0c;也为创业者提供了广阔的发展空间。然而&#xff0c;对于许多初次涉足电商领域的人来说&#xff0c;找到合适的货源却是一个重要的…

Pygame编程(9)font模块

Pygame编程&#xff08;9&#xff09;font模块 函数示例 函数 pygame.font.init 初始化字体模块init() -> None pygame.font.quit 反初始化字体模块quit() -> None pygame.font.get_init True,如果字体模块已初始化get_init() -> bool pygame.font.get_default_font …

Adapter Tuning Overview:在CV,NLP,多模态领域的代表性工作

文章目录 Delta TuningAdapter Tuning in CVAdapter Tuning in NLP Delta Tuning Adapter Tuning in CV 题目: Learning multiple visual domains with residual adapters 机构&#xff1a;牛津VGG组 论文: https://arxiv.org/pdf/1705.08045.pdf Adapter Tuning in NLP …

Gateway简述

前言 ​ 在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端调用多个微服务接口的地址。另外微服务架构的请求中&#xff0c;90%的都携带认证信息/用户登录信息&#xff0c;都需要做相关的限制管理&#xff0c;API网关由此应允而生。 这样的架构会存…

海康摄像头通过SDK接入到LiveNVR实现双向语音喊话对讲与网页无插件播放,并支持GB28181级联语音对讲...

目录 1、确认摄像头是否支持对讲2、摄像头视频类型复合流3、通道配置SDK接入4、视频广场点击播放5、相关问题 5.1、如何配置通道获取直播流&#xff1f;5.2、如何GB28181级联国标平台&#xff1f;6、RTSP/HLS/FLV/RTMP拉流Onvif流媒体服务 1、确认摄像头是否支持对讲 可以访问摄…

基于微信小程序的宠物领养平台的设计与实现(Java+spring boot+微信小程序+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于微信小程序的宠物领养平台的设计与实现&#xff08;Javaspring boot微信小程序MySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java…

NFT Insider #104:The Sandbox:全新土地销售活动 Turkishverse 来袭

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members、BeepCrypto联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周报将从NFT市场数据&#xff0c;艺术新闻类&#xff0c;游戏新闻类&#xff0c;虚拟世界类&#…

ReactNative 密码生成器实战

效果展示图 使用插件 Formik 负责表单校验、监听表单提交、数据校验错误信息展示 Yup 负责表单校验规则 分析页面 从上述的展示图我们可以看到的主要元素有&#xff1a;输入框、单选按钮和按钮。其中生成的密码长度不可能很大也不可能为负数和 0&#xff0c;所以我们可以限…

在Jupyter Notebook中添加Anaconda环境(内核)

在使用前我们先要搞清楚一些事&#xff1a; 我们在安装anaconda的时候它就内置了Jupyter Notebook&#xff0c;这个jupyter初始只有base一个内核&#xff08;显示为Python3&#xff09; 此后其实我们就不需要重复安装完整的jupyter notebook了&#xff0c;只要按需为其添加新的…

通讯录(C语言)

通讯录 一、基本思路及功能介绍二、功能实现1.基础菜单的实现2.添加联系人信息功能实现3.显示联系人信息功能实现4.删除联系人信息功能实现5.查找联系人信息功能实现6.修改联系人信息功能实现7.排序联系人信息功能实现8.加载和保存联系人信息功能实现 三、源文件展示1.test.c2.…

JavaFX 加载 fxml 文件

JavaFX 加载 fxml 文件主要有两种方式&#xff0c;第一种方式通过 FXMLLoader 类直接加载 fxml 文件&#xff0c;简单直接&#xff0c;但是有些控件目前还不知道该如何获取&#xff0c;所以只能显示&#xff0c;目前无法处理。第二种方式较为复杂&#xff0c;但是可以使用与 fx…

C#,《小白学程序》第四课:数学计算

1 文本格式 /// <summary> /// 《小白学程序》第四课&#xff1a;数学计算 /// 这节课超级简单&#xff0c;就是计算成绩的平均值&#xff08;平均分&#xff09; /// 这个是老师们经常做的一件事。 /// </summary> /// <param name"sender"></…

MyBatis plus 多数据源实现

1. 项目背景 最近写文章发布到【笑小枫】小程序和我的个人网站上&#xff0c;因为个人网站用的是halo框架搭建&#xff0c;两边数据结构不一致&#xff0c;导致我每次维护文章都需要两边维护&#xff0c;这就很烦~ 于是&#xff0c;本文就诞生了。通过项目连接这两个数据库&a…

uniapp 安卓平台签名证书(.keystore)生成

安装JRE环境 下载jre安装包&#xff1a;https://www.oracle.com/java/technologies/downloads/#java8安装jre安装包时&#xff0c;记录安装目录(例:C:\Program Files\Java\jdk-20)打开命令行&#xff08;cmd&#xff09;&#xff0c;将JRE安装路径添加到系统环境变量 d: se…

tableau基础学习1:数据源与绘图

文章目录 读取数据常用绘图方法1. 柱状图2. 饼图3. 散点图4. 热力图 第一部分是一些较容易上手的内容&#xff0c;以及比较常见的可视化内容&#xff0c;包括&#xff1a;柱状图、饼图、散点图与热力图 读取数据 打开界面后&#xff0c;选择数据源之后就可以导入数据&#xf…