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

news2024/9/22 3:52:47

链表基础知识 基础操作

  • 链表基础操作
    • 链表基础知识
    • 插入节点
    • 删除节点
    • 查找节点
  • 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--;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于原生Servlet使用模板引擎Thymeleaf访问界面

我们常在Spring Boot项目中使用Thymeleaf模板引擎,今天突发奇想&#xff0c;尝试原生Servlet访问&#xff01; 说做就做 搭建完整的WEB项目 其中的大部分依赖都是后续报错 追加进来的 导入依赖 thymeleaf-3.0.11.RELEASE.jar 第一次访问 访问地址: http://localhost:8080…

利用屏幕水印学习英语单词,无打扰英语单词学习

1、利用屏幕水印学习英语单词&#xff0c;不影响任何鼠标键盘操作&#xff0c;不影响工作 2、利用系统热键快速隐藏&#xff08;ALT1键 隐藏与显示&#xff09; 3、日积月累单词会有进步 4、软件下载地址: 免安装&#xff0c;代码未加密&#xff0c;安全的屏幕水印学习英语…

Linux学习之ftp安装、vsftpd安装和使用

ftp需要两个端口&#xff1a; 数据端口 命令端口 ftp有两种模式&#xff1a; 被动模式&#xff1a;建立命令连接之后&#xff0c;服务器等待客户端发起请求。 主动模式&#xff1a;建立命令连接之后&#xff0c;服务器主动向客户端发起数据连接&#xff0c;因为客户端可能有防火…

6-2 使用函数求素数和

分数 20 全屏浏览题目 切换布局 作者 张高燕 单位 浙大城市学院 本题要求实现一个判断素数的简单函数、以及利用该函数计算给定区间内素数和的函数。 素数就是只能被1和自身整除的正整数。注意&#xff1a;1不是素数&#xff0c;2是素数。 函数接口定义&#xff1a; int p…

相关变化率的例子

如图&#xff0c;不解释。 很多物理学上的物理量&#xff0c;直接使用微分和导数来定义&#xff0c;因此可以不加证明的直接使用这些物理量。 解&#xff1a; d l 2 , d w 3 dl 2, dw 3 dl2,dw3 v l 2 w 2 , d v − 2 l d l 2 w d w 2 l 2 w 2 − 2 12 2 2 5…

内网渗透神器CobaltStrike之内网信息收集(九)

收集域内信息 Windows命令 查看网关的ip地址, DNS的ip地址、域名等等&#xff1a;shell ipconfig /all 查看当前主机所在的域: shell net view /domain 查看当前域的主机列表: shell net view 查看指定域的主机列表: shell net view /domain:[domain] 若beacon用户是域控, 则…

数据同步工具比较:选择适合您业务需求的解决方案

在当今数字化时代&#xff0c;数据已经成为企业的核心资产。然而&#xff0c;随着业务的扩展和设备的增多&#xff0c;如何实现数据的高效管理和同步成为了一个亟待解决的问题。本文将介绍几种常见的数据同步工具&#xff0c;并对比它们的功能、性能和适用场景&#xff0c;帮助…

二、9.硬盘驱动程序

文件系统是运行在操作系统中的软件模块&#xff0c;是操作系统提供的一套管理磁盘文件读写的方法和数据组织、存储形式&#xff0c;因此&#xff0c;文件系统&#xff1d;数据结构&#xff0b;算法&#xff0c;哈哈&#xff0c;所以它是程序。它的管理对象是文件&#xff0c;管…

互斥锁的概念,与部分接口

何为互斥 一种对共享数据的保护&#xff0c;防止多线程同时访问共享资源的时&#xff0c;数据混乱的问题。在互斥期间&#xff0c;保证执行流由并行改为串行。任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&#xff0c;访问临界资源&#xff0c;通常对临界资源起…

苍穹外卖 day2 反向代理和负载均衡配置的代码

为什么要整这些玩意 为了并发&#xff0c;为了容错&#xff0c;为了高可用 一 反向代理的代码 server{listen 80;server_name localhost;location /api/{proxy_pass http://localhost:8080/admin/; #反向代理} }**proxy_pass&#xff1a;**该指令是用来设置代理服务器的地址&…

1小时学会Python

1.Hello world 安装完Python之后,打开IDLE(Python GUI) ,该程序是Python语言解释器,你写的语句能够立即运行。 我们写下一句著名的程序语句: 并按回车,你就能看到这句被K&R引入到程序世界的名言。 在解释器中选择"File"--"New Window" 或快捷键 …

如何通过振动判断设备健康度?以PreMaint设备数字化平台为例

在工业生产过程中&#xff0c;设备的健康状况直接关系到生产效率和安全。而振动分析作为一种重要的设备健康监测手段&#xff0c;可以通过监测设备的振动情况来判断其健康状况。本文将以PreMaint设备数字化平台为例&#xff0c;探讨如何通过振动分析来判断设备的健康度&#xf…

【javaweb】学习日记Day3 - Ajax 前后端分离开发 入门

目录 一、Ajax 1、简介 2、Axios &#xff08;没懂 暂留&#xff09; &#xff08;1&#xff09;请求方式别名 &#xff08;2&#xff09;发送get请求 &#xff08;3&#xff09;发送post请求 &#xff08;4&#xff09;案例 二、前端工程化 1、Vue项目-目录结构 2、…

IDEA中使用Docker插件构建镜像并推送至私服Harbor

一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的&#xff0c;需要加点配置&#xff0c;开启Docker的远程访问 # 首先查看docker配置文件所在位置 systemctl status docker# 会输出如下内容&#xff1a; ● docker.service - Docker Ap…

如何使用PHP实现多语言网站功能

如何使用PHP实现多语言网站功能 在全球化的今天&#xff0c;开发多语言网站成为了一个必要的需求。PHP作为一种常用的编程语言&#xff0c;可以很方便地实现多语言网站功能。本文将介绍如何使用PHP实现多语言网站功能&#xff0c;并提供相应的代码示例。 一、创建语言文件 首先…

系统集成项目管理工程师【中级】考证学习资料整理分享——第一章《信息化基础知识》,持续更新中........

系统集成项目管理工程师(中级)考证学习资料整理分享,持续更新中........ 第一章 《信息化基础知识》 一、信息与信息化 在充满前所未有的创新活力的同时,信息化正以更快地速度推进生产力的发展,围绕智能制造、云计算、网络空间、移动互联、工业互联、大数据、信息安全等领…

网络安全—黑客—自学笔记

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

多线程与高并发编程一

文章目录 一、故事背景二、知识点主要构成1、线程的概念2、启动方式2.1、继承Thread类 重写run方法2.2、实现Runnable接口 重写run方法2.3、实现Callable 重写call方法 配合FuterTask获取线程结果 3、常用方法start()方法&#xff1a;run()方法&#xff1a;sleep(long millis)方…

【HCIP】企业网三层架构实验

题目&#xff1a; 拓扑图 配置 LSW1 //链路聚合 [lsw3]interface Eth-Trunk 1 [lsw3-Eth-Trunk1]trunkport GigabitEthernet 0/0/3 0/0/4 [lsw3-Eth-Trunk1]q [lsw3]vlan batch 1 2 [lsw3]interface Eth-Trunk 1 [lsw3-Eth-Trunk1]port link-type trunk [lsw3-Eth-Trunk1]port…

docker中bridge、host、container、none四种网络模式简介

一.bridge模式 1.简介 2.演示 &#xff08;1&#xff09;运行两个容器&#xff0c;不指定网络模式情况下默认是bridge模式 &#xff08;2&#xff09;在主机中自动生成了两个veth设备 &#xff08;3&#xff09;查看两个容器的IP地址 &#xff08;4&#xff09;可以自定义…