【数据结构】线性表(四)双向链表的各种操作(插入、删除、查找、修改、遍历打印)
目录
线性表的定义及其基本操作(顺序表插入、删除、查找、修改)
四、线性表的链接存储结构
1. 单链表
2. 循环链表
3. 双向链表
a. 双向链表节点结构
b. 创建一个新的节点
c. 在链表末尾插入节点
d. 在指定位置插入节点
e. 删除指定位置的节点
f. 遍历并打印链表
g. 主函数
h. 代码整合
五、复杂性分析
1. 空间效率比较
2. 时间效率比较
线性表的定义及其基本操作(顺序表插入、删除、查找、修改)
一个线性表是由零个或多个具有相同类型的结点组成的有序集合。
按照线性表结点间的逻辑顺序依次将它们存储于一组地址连续的存储单元中的存储方式被称为线性表的顺序存储方式。按顺序存储方式存储的线性表具有顺序存储结构,一般称之为顺序表。换言之,在程序中采用定长的一维数组,按照顺序存储方式存储的线性表,被称为顺序表。
【数据结构】线性表(一)线性表的定义及其基本操作(顺序表插入、删除、查找、修改)-CSDN博客https://blog.csdn.net/m0_63834988/article/details/132089038?spm=1001.2014.3001.5501
四、线性表的链接存储结构
1. 单链表
顺序表的优点是存取速度快。但是,无论是插入一个结点,还是删除一个结点,都需要调整一批结点的地址。要克服该缺点,就必须给出一种不同于顺序存储的存储方式。用链接存储方式存储的线性表被称为链表,可以克服上述缺点。
链表中的结点用存储单元(若干个连续字节)来存放,存储单元之间既可以是(存储空间上)连续的,也可以是不连续的,甚至可以零散地分布在存储空间中的任何位置。换言之,链表中结点的逻辑次序和物理次序之间并无必然联系。最重要的是,链表可以在不移动结点位置的前提下根据需要随时添加删除结点,动态调整。
【数据结构】线性表(二)单链表及其基本操作(创建、插入、删除、修改、遍历打印)-CSDN博客https://blog.csdn.net/m0_63834988/article/details/133914875?spm=1001.2014.3001.5501
2. 循环链表
从单链表的一个结点出发,只能访问到链接在它后面的结点,而无法访问位于它前面的结点,这对一些实际应用很不方便。解决的办法是把链接结构“循环化”,即把表尾结点的next域存放指向哨位结点的指针,而不是存放空指针NULL,这样的单链表被称为循环链表。循环链表使用户可以从链表的任何位置开始,访问链表中的任意结点。
【数据结构】线性表(三)循环链表的各种操作(创建、插入、查找、删除、修改、遍历打印、释放内存空间)-CSDN博客https://blog.csdn.net/m0_63834988/article/details/133914085?spm=1001.2014.3001.5501
3. 双向链表
在循环链表中,从一个结点出发,必须遍历整个链表,方可找到其前驱结点,时间复杂度为O(n) . 双向链表将很好地解决该问题。所谓双向链表,系指链表中任一结点P都是由data域、左指针域left(pre)和右指针域right(next)构成的,左指针域和右指针域分别存放P的左右两边相邻结点的地址信息。
双向链表的优点是可以在常量时间内删除或插入一个节点,因为只需要修改节点的前后指针,而不需要像单向链表那样遍历到指定位置。而在单向链表中,删除或插入一个节点需要先找到前一个节点,然后修改指针。然而,双向链表相对于单向链表需要更多的内存空间来存储额外的指针。另外,由于多了一个指针,插入和删除节点时需要更多的操作。
a. 双向链表节点结构
typedef struct Node {int data;struct Node* prev;struct Node* next;
} Node;
包含一个整数data
以及两个指针prev
和next
,分别指向前一个节点和后一个节点。
b. 创建一个新的节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->prev = NULL;newNode->next = NULL;return newNode;
}
创建一个新的节点,它接受一个整数作为参数,并分配内存来存储节点。然后,节点的data
被设置为传入的整数,prev
和next
指针被初始化为NULL
,最后返回新创建的节点指针。
c. 在链表末尾插入节点
void append(Node** head, int data) {Node* newNode = createNode(data);if (*head == NULL) {*head = newNode;} else {Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode;newNode->prev = current;}
}
- 创建一个新的节点,并将传入的整数作为节点的数据。
- 检查链表是否为空
- 如果为空,将链表头指针指向新节点;
- 否则,遍历链表找到最后一个节点,将最后一个节点的
next
指针指向新节点,新节点的prev
指针指向最后一个节点。
d. 在指定位置插入节点
void insert(Node** head, int data, int position) {Node* newNode = createNode(data);if (position == 0) {newNode->next = *head;if (*head != NULL) {(*head)->prev = newNode;}*head = newNode;} else {Node* current = *head;int i;for (i = 0; i < position - 1 && current != NULL; i++) {current = current->next;}if (current == NULL) {printf("Invalid position\n");return;}newNode->next = current->next;newNode->prev = current;if (current->next != NULL) {current->next->prev = newNode;}current->next = newNode;}
}
- 创建一个新的节点,并将传入的整数作为节点的数据。
- 如果插入位置为0,表示在链表头插入新节点
- 将新节点的
next
指针指向链表的头节点 - 如果链表不为空,将链表的头节点的
prev
指针指向新节点,最后将链表的头指针指向新节点。
- 将新节点的
- 如果插入位置不为0
- 首先遍历链表找到插入位置的前一个节点
- 如果找到了位置或者遍历到链表末尾都没有找到指定位置,则输出"Invalid position"并返回。
- 否则,将新节点的
next
指针指向当前节点的next
指针所指向的节点,新节点的prev
指针指向当前节点 - 如果当前节点的
next
指针不为空,将当前节点的next
指针所指向的节点的prev
指针指向新节点,最后将当前节点的next
指针指向新节点。
e. 删除指定位置的节点
void delete(Node** head, int position) {if (*head == NULL) {printf("List is empty\n");return;}Node* temp = *head;if (position == 0) {*head = (*head)->next;if (*head != NULL) {(*head)->prev = NULL;}free(temp);} else {int i;for (i = 0; i < position && temp != NULL; i++) {temp = temp->next;}if (temp == NULL) {printf("Invalid position\n");return;}temp->prev->next = temp->next;if (temp->next != NULL) {temp->next->prev = temp->prev;}free(temp);}
}
- 如果链表为空,输出"List is empty"并返回。
- 如果要删除的节点是链表的头节点
- 将链表的头指针指向头节点的下一个节点,如果链表不为空,将新的头节点的
prev
指针指向NULL
,然后释放被删除的节点的内存。
- 将链表的头指针指向头节点的下一个节点,如果链表不为空,将新的头节点的
- 如果要删除的节点不是头节点
- 首先遍历链表找到要删除的节点
- 如果找到了指定位置的节点或者遍历到链表末尾都没有找到,则输出"Invalid position"并返回。
- 否则,将要删除节点的前一个节点的
next
指针指向要删除节点的下一个节点,如果要删除节点的下一个节点不为空,将要删除节点的下一个节点的prev
指针指向要删除节点的前一个节点。 - 最后释放要删除节点的内存。
f. 遍历并打印链表
void printList(Node* head) {Node* current = head;while (current != NULL) {printf("%d ", current->data);current = current->next;}printf("\n");
}
从链表的头节点开始,通过不断访问next
指针,打印每个节点的数据,并移动到下一个节点,直到遍历完整个链表。
g. 主函数
int main() {Node* head = NULL;printList(head);// 在链表末尾插入节点append(&head, 1);append(&head, 2);append(&head, 3);printList(head);// 在指定位置插入节点insert(&head, 4, 1);printList(head);// 删除指定位置的节点delete(&head, 2);printList(head);return 0;
}
h. 代码整合
#include <stdio.h>
#include <stdlib.h>// 双向链表节点结构
typedef struct Node {int data;struct Node* prev;struct Node* next;
} Node;// 创建一个新的节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->prev = NULL;newNode->next = NULL;return newNode;
}// 在链表末尾插入节点
void append(Node** head, int data) {Node* newNode = createNode(data);if (*head == NULL) {*head = newNode;} else {Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode;newNode->prev = current;}
}// 在指定位置插入节点
void insert(Node** head, int data, int position) {Node* newNode = createNode(data);if (position == 0) {newNode->next = *head;if (*head != NULL) {(*head)->prev = newNode;}*head = newNode;} else {Node* current = *head;int i;for (i = 0; i < position - 1 && current != NULL; i++) {current = current->next;}if (current == NULL) {printf("Invalid position\n");return;}newNode->next = current->next;newNode->prev = current;if (current->next != NULL) {current->next->prev = newNode;}current->next = newNode;}
}// 删除指定位置的节点
void delete(Node** head, int position) {if (*head == NULL) {printf("List is empty\n");return;}Node* temp = *head;if (position == 0) {*head = (*head)->next;if (*head != NULL) {(*head)->prev = NULL;}free(temp);} else {int i;for (i = 0; i < position && temp != NULL; i++) {temp = temp->next;}if (temp == NULL) {printf("Invalid position\n");return;}temp->prev->next = temp->next;if (temp->next != NULL) {temp->next->prev = temp->prev;}free(temp);}
}// 遍历并打印链表
void printList(Node* head) {Node* current = head;while (current != NULL) {printf("%d ", current->data);current = current->next;}printf("\n");
}// 主函数
int main() {Node* head = NULL;printList(head);// 在链表末尾插入节点append(&head, 1);append(&head, 2);append(&head, 3);printList(head);// 在指定位置插入节点insert(&head, 4, 1);printList(head);// 删除指定位置的节点delete(&head, 2);printList(head);return 0;
}
五、复杂性分析
到目前为止,本系列已详细介绍了线性表的两种存储方式——顺序存储和链接存储,下面从空间和时间复杂性两方面对二者进行比较分析。
1. 空间效率比较
顺序表所占用的空间来自于申请的数组空间,数组大小是事先确定的,很明显当表中的元素较少时,顺序表中的很多空间处于闲置状态,造成了空间的浪费;
链表所占用的空间是根据需要动态申请的,不存在浪费空间的问题,但是链表需要在每个结点上附加一个指针域,从而增加一定的空间开销。
2. 时间效率比较
线性表的基本操作是存取、插入和删除。对于顺序表,随机存取是非常容易的,但是每插入或删除一个元素,都需要移动若干元素。
对于链表,无法实现随机存取,必须要从表头开始遍历链表,直到发现要存取的元素,但是链表的插入和删除操作却非常简便,只需要修改几个指针。
- 当经常需要对线性表进行插入、删除操作时,链表的时间效率较高;
- 双向链表在某些场景下更加灵活和高效,特别是需要频繁的插入和删除操作时。然而,在内存有限的情况下,单向链表可能更为合适。
- 当经常需要对线性表进行存取且存取操作比插入、删除操作更为频繁时,顺序表的时间效率较高
相关文章:

【数据结构】线性表(四)双向链表的各种操作(插入、删除、查找、修改、遍历打印)
目录 线性表的定义及其基本操作(顺序表插入、删除、查找、修改) 四、线性表的链接存储结构 1. 单链表 2. 循环链表 3. 双向链表 a. 双向链表节点结构 b. 创建一个新的节点 c. 在链表末尾插入节点 d. 在指定位置插入节点 e. 删除指定位置的节点…...

数据结构和算法——图
图 有向图 带权图 邻接矩阵 邻接表相较于邻接矩阵,减少了存储空间; 邻接表 参考视频:【尚硅谷】数据结构与算法(Java数据结构与算法)_哔哩哔哩_bilibili...
大数据学习(16)-mapreduce详解
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...
Android---OkHttp详解
OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App,OkHttp 现在几乎已经占据了所有的网络请求操作。RetroFit OkHttp 实现网络请求似乎成了一种标配。 因此&…...

向某文件中逐秒追加带序号输入当前时间 fgets fputs fprintf sprintf
//向某文件中逐秒追加带序号输入当前时间 #include<stdio.h> #include<stdlib.h> #include<time.h> #include<string.h> #include <unistd.h> int main(int argc, char const *argv[]) { time_t tv; // time(&tv);//法1:获取秒数 …...

同为科技(TOWE)机架PDU产品在IDC数据中心机房建设中的应用
当今社会互联网发展迅速, 随着带宽需求的提升, 网络的保密性、安全性的要求就越来越迫切。PDU(Power Distribution Unit) 是 PDU具备电源分配和管理功能的电源分配管理器。PDU电源插座是多有设备运行的第一道也是最为密切的部件, PDU的好坏直…...

Elasticsearch学习笔记
1.核心概念 bucket: 一个数据分组(类似于sql group by以后的数据)metric:对bucket执行的某种聚合分析的操作,比如说求平均值,最大值,最小值。一些系列的统计方法(类似 select count(1) MAX MIN AVG) 请…...
Java框架随笔
Maven面试题 Myabtis面试题 文章目录 Maven面试题Myabtis面试题 1、简述Spring Boot的启动流程2、如何理解Bean的生命周期3、MyBatis的主要功能4、MyBatis的组成部分5、MyBatis的动态SQL 1、简述Spring Boot的启动流程 Spring Boot的启动流程可以分为以下几个步骤:…...

自然语言处理基础——词表示
词表示 把自然语言中最基本的语言单元——词转换为机器能够理解的 词表示能完成以下两个能力 词相似度计算 词与词之间语义的关系 近义词&上位词 使用近义词或上位词表示的问题 遗漏差异 遗漏新的释义 带有主观性 数据吸收 需要大量人工构建 One-Hot Representation …...
2023年9月青少年软件编程(C 语言) 等级考试试卷(七级)
青少年软件编程(C/C)7级等级考试真题试卷(2023年9月) 编程题第 1 题 红与黑(2023.9) 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,…...

鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统项目背景
鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工程管…...

apache httpd 换行解析漏洞
原理 Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞,在解析PHP时,1.php\x0A将被按照PHP后缀进行解析,导致绕过一些服务器的安全策略。 漏洞编号 cve-2017-15715 环境…...

【设计模式】工厂模式
工厂模式 1.什么是工厂模式 它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象…...

C++二分算法的应用:寻找峰值原理、源码及测试用例
说明 此文是课程https://edu.csdn.net/course/detail/38771 的讲义。 源码下载:https://download.csdn.net/download/he_zhidan/88458478 题目 长度为n的数组nums,请返回任意一峰值的索引。符合以下条件之一i便是峰值的索引。 n等于1 i等于0 n>…...

外汇天眼:本周无牌裸奔平台名单出炉,你踩“坑”了么?!!
监管信息早知道!外汇天眼将每周定期公布监管牌照状态发生变化的交易商,以供投资者参考,规避投资风险。如果平台天眼评分过高,建议投资者谨慎选择,因为在外汇天眼评分高不代表平台没问题! 以下是监管牌照发生…...
10 读写锁ReentrantReadWriteLock
1 介绍 为什么要使用读写锁? 需要高并发读取和较低并发写入的应用程序,降低锁的粒度,提高系统性能 使用场景: 读多写少的共享资源 缓存管理:读 >> 写,控制多个线程同时读缓存,需要刷新o…...

laravel队列
laravel redis队列 1、创建job队列任务 php artisan make:job StoreUser执行上述命令后,会生成app/Jobs/StoreUser.php文件,编辑文件内容如下: <?phpnamespace App\Jobs;use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queu…...

【计算机网络】TCP 协议的相关特性
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的协议。以下是TCP协议的相关特性: 可靠性:TCP通过确认和重传机制保证数据的可靠传输。 面向连接:TCP在传输数据前需要先建立连接。连接的建立过程包括三次握手…...
[软件安装] tmux安装及相关事项
tmux安装及相关事项 tmux是一个终端复用工具,可以在单个终端窗口中同时运行多个终端会话。安装tmux可以提高工作效率,使命令行操作更加方便。 1. 安装tmux: 在Linux系统下,可以使用包管理器来安装tmux,比如在Ubuntu…...
leetcode 887 ——扔鸡蛋
题目大意: 你有k个鸡蛋,对n层楼的建筑,请确认在f层扔鸡蛋鸡蛋恰好不会破碎的最少次数(f满足 0 < f < n)。 方法一: 状态:即会发生变化的量,很明显有两个,当前拥有…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...