当前位置: 首页 > news >正文

【数据结构】单链表的使用

单链表的使用

    • 1、基本概念
    • 2、链表的分类
    • 3、链表的基本操作
      • a、单链表节点设计
      • b、单链表初始化
      • c、单链表增删节点
        • **节点头插:**
        • **节点尾插:**
        • **新节点插入指定节点后:**
        • 节点删除:
      • d、单链表修改节点
      • e、单链表遍历,并打印数据
    • 4、链表优缺点
    • 5、完整示例代码

1、基本概念

  • 顺序表:顺序存储的线性表。
  • 链式表:链式存储的线性表,简称链表。
    既然顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后在用来指针将它们串起来。这种朴素的思路所形成的链式线性表,就是所谓的链表。

顺序表和链表在内存在的基本样态如下图所示:

  • 顺序表:

在这里插入图片描述
链表:
在这里插入图片描述

2、链表的分类

\quad 根据链表中各个节点之间使用指针的个数,以及首尾节点是否相连,可以将链表细分为如下种类:
1.单向链表
2.单向循环链表
3.双向循环链表
\quad 这些不同链表的操作都是差不多的,只是指针数目的异同。以最简单的单向链表为例,其基本示意图如下所示:
在这里插入图片描述上图中,所有的节点均保存一个指针,指向其逻辑上相邻的下一个节点(末尾节点指向空)。
另外注意到,整条链表用一个所谓的头指针 head 来指向,由 head 开始可以找到链表中的任意一个节点。head 通常被称为头指针

3、链表的基本操作

  1. 节点设计
  2. 初始化空链表
  3. 增删节点
  4. 链表遍历(改查)
  5. 销毁链表

下面着重针对这几项常见操作,讲解单向链表的处理。

a、单链表节点设计

单向链表的节点非常简单,节点中除了要保存用户数据之外(这里以整型数据为例),只需要增加一个指向本类节点的指针即可,如下所示:

typedef struct single_list{// 数据域--》真实的数据int data;    				// 以整型数据为例// 指针域struct single_list *next; 	// //用来指向下一个元素在内存中的首地址
}single_list_t;

b、单链表初始化

\quad 首先,空链表有两种常见的形式。一种是带头结点的,一种是不带头结点的。所谓的头结点是不存放有效数据的节点,仅仅用来方便操作,如下:
在这里插入图片描述
而不带头结点的空链表如下所示:
在这里插入图片描述
\quad 由于头结点是不存放有效数据的,因此如果空链表中带有头结点,那么头指针 head 将永远不变,这会给以后的链表操作带来些许便捷。
下面以带头结点的链表为例,展示单向链表的初始化的示例代码:

single_list_t *single_list_init()
{single_list_t *head_node = malloc(sizeof(single_list_t));if (head_node != NULL){head_node->next = NULL;}return head_node;
}

c、单链表增删节点

\quad 相对于顺序表需要整片移动数据,链表增删节点只需要修改几个相关指针的指向,动作非常快速。
\quad 与顺序表类似,可以对一条链表中的任意节点进行增删操作,示例代码:

节点头插:
int insert_list_head(int newdata, single_list_t *list)
{// 申请一个节点 -堆空间single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// 插入节点new_node->next = list->next;list->next = new_node;return 0;
}
节点尾插:
int insert_list(int newdata, single_list_t *list)
{// 申请一个节点 -堆空间single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// 定义指针p去找到链表的尾部single_list_t *p = list;while (p->next != NULL){p = p->next;}// 此时p已经到最后一个节点的位置p->next = new_node;return 0;
}
新节点插入指定节点后:
int insert_list_mid(int olddata, int newdata, single_list_t *list)
{// 找到要插入的节点single_list_t *p = list;while (p->next != NULL){p = p->next;if (p->data == olddata) // 待插入的节点在链表中间{break;}}// 申请一个新的节点 single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// p指向最后一个节点if (p->next == NULL){// 最后一个节点是要插入的节点if (p->data == olddata){new_node->next = p->next; // 先让新增加的节点指向找到节点的下一个节点p->next = new_node;   // 再将该节点指向新增加的节点}else{printf("要插入的数据不存在\n");return -1;}}else // 遍历到中间找到需要插入的节点{new_node->next = p->next; // 先让新增加的节点指向找到节点的下一个节点p->next = new_node;   // 再将该节点指向新增加的节点}return 0;
}
节点删除:
int list_delnode(int deldata, single_list_t *list)
{single_list_t *q = list;single_list_t *p = list->next;while (p->next != NULL){// 找到要删除的节点并进行删除if (p->data == deldata){q->next = p->next;  // 将q指向的节点指向被删除节点的下一个节点p->next = NULL;     // p节点的next指针指向NULLfree(p);    // 释放p后此时p是野指针p = q->next;// 将p指向被删除节点的下一个节点}else{p = p->next;q = q->next;}  }// 遍历到最后一个节点if (p->next == NULL){// 若最后一个节点是要删除的节点,则删除if (p->data == deldata){q->next = p->next;p->next = NULL;free(p);}else{printf("最后一个节点不是要删除的节点\n");return 0;}}
}

d、单链表修改节点

int list_update_node(int old_data, int new_data, single_list_t *list)
{single_list_t *p = list;while (p->next != NULL){p = p->next;  // p指向下一个节点if (p->data == old_data){p->data = new_data;}}return 0;
}

e、单链表遍历,并打印数据

int list_show(single_list_t *list)
{single_list_t *p = list;while (p->next != NULL){p = p->next;printf("当前p指向的节点数据:%d\n", p->data);}
}

4、链表优缺点

\quad 链式存储中,所有节点的存储位置是随机的,他们之间的逻辑关系用指针来确定,跟物理存储位置无关,因此从上述示例代码可以很清楚看到,增删数据都非常迅速,不需要移动任何数据。另外,又由于位置与逻辑关系无关,因此也无法直接访问某一个指定的节点,只能从头到尾按遍历的方式一个个找到想要的节点。简单讲,链式存储的优缺点跟顺序存储几乎是相对的。
总结其特点如下:

  • 优点
    a.插入、删除时只需要调整几个指针,无需移动任何数据
    b.当数据节点数量较多时,无需一整片较大的连续内存空间,可以灵活利用离散的内存
    c.当数据节点数量变化剧烈时,内存的释放和分配灵活,速度快
  • 缺点
    a.在节点中,需要多余的指针来记录节点之间的关联
    b.所有数据都是随机存储的,不支持立即访问任意一个随机数据。

5、完整示例代码

#include <stdio.h>
#include <stdlib.h>// 1.封装一个结构体来表示单链表
typedef struct single_list{int data;struct single_list *next;
}single_list_t;// 2.初始化单链表-->定义结构体变量 创建头节点
single_list_t *single_list_init()
{single_list_t *head_node = malloc(sizeof(single_list_t));if (head_node != NULL){head_node->next = NULL;}return head_node;
}// 头插
int insert_list_head(int newdata, single_list_t *list)
{// 申请一个节点 -堆空间single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// 插入节点new_node->next = list->next;list->next = new_node;return 0;
}
/*@brief:3.插入数据-->尾插(在最后一个有效成员的后面插入数据)*@param(in):  newdata :待插入的数据  list:待插入的链表*@param(out):  *@retval:    
*/
int insert_list(int newdata, single_list_t *list)
{// 申请一个节点 -堆空间single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// 定义指针p去找到链表的尾部single_list_t *p = list;while (p->next != NULL){p = p->next;}// 此时p已经到最后一个节点的位置p->next = new_node;return 0;
}// 中间插入
int insert_list_mid(int olddata, int newdata, single_list_t *list)
{// 找到要插入的节点single_list_t *p = list;while (p->next != NULL){p = p->next;if (p->data == olddata){break;}}// 申请一个节点 -堆空间single_list_t *new_node = malloc(sizeof(single_list_t));// 初始化数据域new_node->data = newdata;new_node->next = NULL;// p指向最后一个节点if (p->next == NULL){if (p->data == olddata){new_node->next = p->next; // 先让新增加的节点指向找到节点的下一个节点p->next = new_node;   // 再将该节点指向新增加的节点}else{printf("要插入的数据不存在\n");return -1;}}else // 遍历到中间找到需要插入的节点{new_node->next = p->next; // 先让新增加的节点指向找到节点的下一个节点p->next = new_node;   // 再将该节点指向新增加的节点}return 0;
}
// 删除节点
int list_delnode(int deldata, single_list_t *list)
{single_list_t *q = list;single_list_t *p = list->next;while (p->next != NULL){// 找到要删除的节点并进行删除if (p->data == deldata){q->next = p->next;  // 将q指向的节点指向被删除节点的下一个节点p->next = NULL;     // p节点的next指针指向NULLfree(p);    // 释放p后此时p是野指针p = q->next;// 将p指向被删除节点的下一个节点}else{p = p->next;q = q->next;}  }// 遍历到最后一个节点if (p->next == NULL){// 若最后一个节点是要删除的节点,则删除if (p->data == deldata){q->next = p->next;p->next = NULL;free(p);}else{printf("最后一个节点不是要删除的节点\n");return 0;}}
}
// 修改节点
int list_update_node(int old_data, int new_data, single_list_t *list)
{single_list_t *p = list;while (p->next != NULL){p = p->next;  // p往后移动if (p->data == old_data){p->data = new_data;}}return 0;
}// 4.遍历链表,打印节点数据
int list_show(single_list_t *list)
{single_list_t *p = list;while (p->next != NULL){p = p->next;printf("当前p指向的节点数据:%d\n", p->data);}
}int main(int argc, char const *argv[])
{// 初始化单链表single_list_t *my_list = single_list_init();// 往链表插入数据insert_list(15, my_list);insert_list(18, my_list);insert_list(15, my_list);insert_list(25, my_list);insert_list(666, my_list);insert_list(123, my_list);insert_list(11111111, my_list);insert_list_mid(666, 777, my_list);insert_list_mid(1, 222, my_list);insert_list_head(15, my_list);printf("============插入的节点============\n");list_show(my_list);printf("============插入的节点============\n");// 删除节点list_delnode(25,my_list);printf("============删除后的节点============\n");list_show(my_list); // 打印数据printf("============删除后的节点============\n");// 修改数据list_update_node(123, 234, my_list);printf("============修改后的节点============\n");list_show(my_list); // 打印数据printf("============修改后的节点============\n");return 0;
}
/*
执行结果:
要插入的数据不存在
============插入的节点============
当前p指向的节点数据:15
当前p指向的节点数据:15
当前p指向的节点数据:18
当前p指向的节点数据:15
当前p指向的节点数据:25
当前p指向的节点数据:666
当前p指向的节点数据:777
当前p指向的节点数据:123
当前p指向的节点数据:11111111
============插入的节点============
最后一个节点不是要删除的节点
============删除后的节点============
当前p指向的节点数据:15
当前p指向的节点数据:15
当前p指向的节点数据:18
当前p指向的节点数据:15
当前p指向的节点数据:666
当前p指向的节点数据:777
当前p指向的节点数据:123
当前p指向的节点数据:11111111
============删除后的节点============
============修改后的节点============
当前p指向的节点数据:15
当前p指向的节点数据:15
当前p指向的节点数据:18
当前p指向的节点数据:15
当前p指向的节点数据:666
当前p指向的节点数据:777
当前p指向的节点数据:234
当前p指向的节点数据:11111111
============修改后的节点============
*/

相关文章:

【数据结构】单链表的使用

单链表的使用 1、基本概念2、链表的分类3、链表的基本操作a、单链表节点设计b、单链表初始化c、单链表增删节点**节点头插&#xff1a;****节点尾插&#xff1a;****新节点插入指定节点后&#xff1a;**节点删除&#xff1a; d、单链表修改节点e、单链表遍历&#xff0c;并打印…...

外键约束的应用层维护

1.前言 一般来说 对于不同表格之间的属性约束 我们通常直接使用数据库已经实现好的外键来完成 但是数据库底层实现的外键他的性能很差 这是因为在执行数据库修改操作时 他需要遍历其他所有的表来找出其中可能相关联的属性 一并进行数据库修改(应用层的维护则只需要遍历所有关联…...

springboot整合log4j2日志框架1

目录 一 log4j基本知识 1.1 log4j的日志级别 1.2 log4j的日志文件结构* 1.2.1 概述 1.2.2 详解 1.3 log4j的日志格式化api 1.3.1 api详解 1.3.2 演示案例 1.3.3 演示案例 1.4 log4j中onmatch和onmismatch的区别* 1.4.1 案例 1.4.2 onmatch的api 1.5 logback&#x…...

06 - Django 视图view

HttpRequest 和 HttpResponse Django中的视图主要用来接受Web请求&#xff0c;并做出响应。 视图的本质就是一个Python中的函数 视图的响应分为两大类 以Json数据形式返回(JsonResponse)以网页的形式返回 重定向到另一个网页 (HttpResponseRedirect)错误视图(4XX,5XX) (Htt…...

基于云计算的资源管理系统

基于云计算的资源管理系统是一种将云计算技术与资源管理技术相结合&#xff0c;以实现资源高效利用和管理的系统。以下是对该系统的详细分析&#xff1a; 一、系统概述 云计算是一种基于网络的计算模式&#xff0c;通过将计算资源和数据存储在云端服务器上&#xff0c;使用户…...

从0入门自主空中机器人-3-【环境与常用软件安装】

关于本课程&#xff1a; 本次课程是一套面向对自主空中机器人感兴趣的学生、爱好者、相关从业人员的免费课程&#xff0c;包含了从硬件组装、机载电脑环境设置、代码部署、实机实验等全套详细流程&#xff0c;带你从0开始&#xff0c;组装属于自己的自主无人机&#xff0c;并让…...

electron node-api addon开发

解决方案入口 拷贝日志以及json等第三方源码 增加包含目录 编写接口 默认模板已经有一个回调函数了 照葫芦画瓢就行 其中几个重要的点要注意 1.参数传入 比如如下的例子&#xff1a; 头文件定义&#xff1a; public:下增加 Napi::Value StartAnswer (const Napi::Callb…...

如何在嵌入式系统或计算机系统中验证boot程序

在嵌入式系统或计算机系统中&#xff0c;验证boot程序&#xff08;引导程序&#xff09;的正确性至关重要&#xff0c;因为它负责初始化系统硬件、加载操作系统内核&#xff0c;并设置系统环境。以下是一些常用的验证boot程序的方法&#xff1a; 一、硬件验证 示波器与逻辑分…...

scala基础学习_运算符

文章目录 scala运算符算术运算符关系运算符逻辑运算符位运算符其他运算符赋值运算符 scala运算符 在 Scala 中&#xff0c;运算符通常被定义为方法。这意味着你可以将运算符视为对象上的方法调用。以下是一些常用的运算符及其对应的操作&#xff1a; 算术运算符 &#xff1a…...

【ANGULAR网站开发】初始环境搭建

1. 初始化angular项目 1.1 创建angular项目 需要安装npm和nodejs&#xff0c;这边不在重新安装 直接安装最新版本的angular npm install -g angular/cli安装指定大版本的angular npm install -g angular/cli181.2 启动angular 使用idea启动 控制台启动 ng serve启动成功…...

【Java】面试题 并发安全 (2)

文章目录 可重入锁&#xff08;ReentrantLock&#xff09;知识总结1. 可重入锁概念与特点2. 基本语法与使用注意事项3. 底层实现原理4. 面试回答要点 synchronized与lock的区别死锁相关面试题讲解死锁产生的四个条件ConcurrentHashMap2. JDK1.7的ConcurrentHashMap结构添加数据…...

springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失

这个包丢失了 启动不了 起因是pom中加入了 <tomcat.version></tomcat.version>版本指定&#xff0c;然后idea自动编译后&#xff0c;包丢了&#xff0c;删除这个配置后再也找不回来&#xff0c; 这个包正常在 <dependency><groupId>org.springframe…...

React 组件的通信方式

在 React 应用开发中&#xff0c;组件之间的通信是构建复杂用户界面和交互逻辑的关键。正确地实现组件通信能够让我们的应用更加灵活和易于维护。以下是几种常见的 React组件通信方式。 一、父子组件通信 1. 通过 props 传递数据&#xff08;父组件向子组件传递数据&#xff0…...

WAV文件双轨PCM格式详细说明及C语言解析示例

WAV文件双轨PCM格式详细说明及C语言解析示例 一、WAV文件双轨PCM格式详细说明1. WAV文件基本结构2. PCM编码方式3. 双轨PCM格式详细说明二、C语言解析WAV文件的代码示例代码说明一、WAV文件双轨PCM格式详细说明 WAV文件是一种用于存储未压缩音频数据的文件格式,广泛应用于音频…...

【ES6复习笔记】数值扩展(16)

介绍 在 JavaScript 中&#xff0c;数值扩展提供了一些额外的功能&#xff0c;使得处理数值变得更加方便。本教程将介绍一些常用的数值扩展方法和属性。 1. Number.EPSILON Number.EPSILON 是 JavaScript 表示的最小精度。它的值接近于 2.2204460492503130808472633361816E-…...

百度热力图数据日期如何选择

目录 1、看日历2、看天气 根据研究内容定&#xff0c;一般如果研究城市活力的话&#xff0c;通常会写“非重大节假日&#xff0c;非重大活动&#xff0c;非极端天气等”。南方晴天不多&#xff0c;有小雨或者中雨都可认为没有影响&#xff0c;要不然在南方很难找到完全一周没有…...

Vue.js 高级组件开发:设计模式与实践

Vue.js 高级组件开发&#xff1a;设计模式与实践 引言一、组合式 API 与动态依赖注入1. 基于 provide/inject 的动态依赖2. 动态依赖注入与懒加载 二、动态渲染与自定义渲染函数1. 使用 Render 函数动态生成内容2. 自定义 vnode 操作 三、复杂场景下的动态表单生成与验证四、高…...

《一文读懂卷积网络CNN:原理、模型与应用全解析》

《一文读懂卷积网络CNN&#xff1a;原理、模型与应用全解析》 一、CNN 基本原理大揭秘&#xff08;一&#xff09;从人类视觉到 CNN 灵感&#xff08;二&#xff09;核心组件详解 二、经典 CNN 模型巡礼&#xff08;一&#xff09;LeNet-5&#xff1a;开山鼻祖&#xff08;二&a…...

MONI后台管理系统-数据敏感字段存储加密

前言&#xff1a;     在我们数据库中&#xff0c;存在很多的敏感数据&#xff0c;如用户表中&#xff0c;存在用户电话、身份证号、邮箱等属于用户的敏感信息&#xff0c;我们通常在存入数据库后&#xff0c;将其进行加密存储&#xff0c;以此来保证数据安全性。     …...

熟悉各类游戏设计模式的用途与限制,如 factory、strategy、mvc、object pool 等

良好的系统分析与设计能力要求开发者熟悉并正确运用各种设计模式来解决特定问题。设计模式是一种针对特定问题的通用解决方案&#xff0c;可提高代码的可复用性、可维护性和可扩展性。以下是对一些常见游戏设计模式的详细分析&#xff0c;包括其用途、限制和代码示例。 一、工厂…...

Vue3(watch,watchEffect,标签中ref的使用,TS,props,生命周期)

Vue3&#xff08;watch&#xff0c;watchEffect&#xff0c;标签中ref的使用,TS,props,生命周期&#xff09; watch监视 情况三&#xff1a;监视reactive定义的对象类型的数据 监视reactive定义的对象类型的数据&#xff0c;默认开启深度监视。地址没变&#xff0c;新值和旧…...

rtpmixsound:实现音频混音攻击!全参数详细教程!Kali Linux教程!

简介 一种将预先录制的音频与指定目标音频流中的音频&#xff08;即 RTP&#xff09;实时混合的工具。 一款用于将预先录制的音频与指定目标音频流中的音频&#xff08;即 RTP&#xff09;实时混合的工具。该工具创建于 2006 年 8 月至 9 月之间。该工具名为 rtpmixsound。它…...

【Dv3Admin】工具权限配置文件解析

接口级权限控制是后台系统安全防护的核心手段。基于用户角色、请求路径与方法进行细粒度授权&#xff0c;可以有效隔离不同用户的数据访问范围&#xff0c;防止越权操作&#xff0c;保障系统整体稳定性。 本文解析 dvadmin/utils/permission.py 模块&#xff0c;重点关注其在匿…...

设计模式——桥接设计模式(结构型)

摘要 桥接设计模式是一种结构型设计模式&#xff0c;用于将抽象与实现解耦&#xff0c;使二者可以独立变化。它通过将一个类拆分为“抽象”和“实现”两部分&#xff0c;并通过桥接关系组合&#xff0c;避免了类继承层次结构过于庞大。桥接模式包含抽象类、扩充抽象类、实现类…...

Visual Studio 2022 插件推荐

Visual Studio 2022 插件推荐 Visual Studio 2022 (简称 VS2022) 是一款强大的 IDE&#xff0c;适合各类系统组件、框架和应用的开发。插件是接入 VS2022 最重要的扩展方式之一&#xff0c;它们可以大幅提升开发效率、优化代码质量&#xff0c;并提供强大的调试和分析功能。 …...

PHP下实现RSA的加密,解密,加签和验签

前言&#xff1a; RSA下加密&#xff0c;解密&#xff0c;加签和验签是四种不同的操作&#xff0c;有时候会搞错&#xff0c;记录一下。 1.公钥加密&#xff0c;私钥解密 发送方通过公钥将原数据加密成一个sign参数&#xff0c;相当于就是信息的载体&#xff0c;接收方能通过si…...

InfluxQL 数据分析实战:聚合、过滤与关联查询全解析

InfluxQL 作为时序数据库的专用查询语言&#xff0c;在处理时间序列数据时展现出独特优势。本文深入探讨 聚合计算、数据过滤和跨测量关联 三大核心操作&#xff0c;通过真实代码示例展示如何从海量时序数据中提取关键洞察。文中涵盖从基础平均值计算到复杂多维度分析的完整流程…...

十三: 神经网络的学习

这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程。为了使神经网络能进行学习&#xff0c;将导入损失函数这一指标。而学习的目的就是以该损失函数为基准&#xff0c;找出能使它的值达到最小的权重参数。为了找出尽可能小的损失函数的值&#xff0c;我们将介绍利…...

工厂模式 vs 策略模式:设计模式中的 “创建者” 与 “决策者”

在日常工作里&#xff0c;需求变动或者新增功能是再常见不过的事情了。而面对这种情况时&#xff0c;那些耦合度较高的代码就会给我们带来不少麻烦&#xff0c;因为在这样的代码基础上添加新需求往往困难重重。为了保证系统的稳定性&#xff0c;我们在添加新需求时&#xff0c;…...

数据共享交换平台之数据资源目录

依据信息资源体系规范&#xff0c;构建多维度、多层级的资源目录体系&#xff0c;完整的展示和管理资源目录。资源目录提供以下功能&#xff1a; 多层级资源目录展示&#xff0c;能够将资源目录按照技术维度和管理维度进行分类管理&#xff0c;并能够将资源目录按照数据湖、基础…...