数据结构学习之双向链表(各种操作合集)

双向链表(各种操作合集)

双向链表的两种创建方式:

方法1:根据函数的返回值创建

  • 通过返回值返回所申请的头结点所在的内存空间首地址,即创建双向链表的头结点,代码如下:

  • 示例代码:

	node_t *create_dplink_node_1(){node_t *phead = (node_t *)malloc(sizeof(node_t));if(NULL == phead){printf("内存分配失败\n");exit(-1);}phead->data = -1;phead->front = NULL;phead->next = NULL;return phead;}
  • 注意事项:
  • 1.分配完头结点的内存地址空间后,一定要检查内存分配是否成功
  • 2.若头结点的内存分配失败,需要使用shell命令exit(-1)退出
  • 3.双向链表的每个结点都有三个部分,即前驱、元素、后继front、data、next),而头结点的数据域可以不储存任何数据,头结点的数据域在此处,我将其被赋值 -1
  • 4.头结点的指针域(含前驱、后继)被赋值 NULL,表示此时的双向链表只有一个头结点

方法2:根据地址传参创建

  • 使用地址传参的方法创建双向链表的头结点,代码如下:
  • 示例代码:
	int create_dplink_node_2(node_t **phead,int data){if(NULL == phead){printf("入参为NULL\n");return -1;}*phead = (node_t *)malloc(sizeof(node_t));if(NULL == *phead){printf("内存分配失败\n");return -1;}(*phead)->data = data;(*phead)->front = NULL;(*phead)->next = NULL;return 0;}
  • 注意事项:
  • 1.所传入的形参必须是二级指针变量,因为二级指针用来存储一级指针变量的地址,即所申请的双向链表头结点在内存空间的地址
  • 2.形参传入到创建双向链表头结点的功能函数后,一定要做入参合理性检查
  • 3.同方法1一样,分配完头结点的内存地址空间后,一定要检查内存分配是否成功
  • 4.头结点的数据域(即链表元素)被赋值 -1
  • 5.头结点的指针域(含前驱、后继)被赋值 NULL

双向链表的三种插入方式:

头插法:

  • 在双向链表的头结点和第0个结点之间插入新结点,即头插法,代码如下:
  • 示例代码:
	int insert_dplink_list_1(node_t *phead,int data){if(NULL == phead){printf("入参为NULL\n");return -1;}//创建新结点node_t *pnew = NULL;create_dplink_node_2(&pnew,data);//头插到链表pnew->next = phead->next;pnew->front = phead;if(NULL != phead->next){phead->next->front = pnew;}phead->next = pnew;return 0;}
  • 操作步骤:
  • 1.创建新结点pnew
  • 2.将新结点的后继指针(即pnew->next)指向头结点的后继指针phead->next)此处存储的是第0个结点的地址,可能是空指针(NULL)即pnew->next = phead->next
  • 3.再将新结点的前驱地址(即pnew->front)指向头结点的地址,即pnew->front = phead
  • 4.判断是否有第0个结点;
  • 5.如果存在,则第0个结点的前驱指针指向新结点的地址,即phead->next->front = pnew
  • 6.最后,头结点的后继指针(phead->next)指向新结点的地址(pnew),即phead->next = pnew

尾插法:

  • 在双向链表的最后一个结点后面插入新结点,即尾插法,代码如下:
  • 示例代码:
	int insert_dplink_list_2(node_t *phead,int data){if(NULL == phead){printf("入参为NULL\n");return -1;}//创建新结点node_t *pnew = NULL;create_dplink_node_2(&pnew,data);//遍历链表,找到最后一个结点node_t *ptemp = phead;while(NULL != ptemp->next){ptemp = ptemp->next;}ptemp->next = pnew;pnew->front = ptemp;return 0;}
  • 操作步骤:
  • 1.创建新结点pnew
  • 2.遍历双向链表,找到链表的最后一个结点ptemp
  • 3.尾结点的后继指针指向新结点的地址,即ptemp->next = pnew
  • 4.新结点的前驱指针指向尾结点的地址,即pnew->front = ptemp

任意位置插入新节点:

  • 在双向链表的任意一个位置,插入新结点,代码如下:
  • 示例代码:
	int insert_dplink_list_3(node_t *phead,int pos,int data){if(NULL == phead){printf("入参为NULL\n");return -1;}if(pos < 0){printf("插入位置不合理,插入失败\n");return -1;}node_t *ptemp = phead;int i = 0;for(i = 0; i < pos; i++){if(NULL == ptemp->next){break;}ptemp = ptemp->next;}if(i < pos){printf("插入位置不合理,插入失败\n");return -1;}//创建新结点node_t *pnew = NULL;create_dplink_node_2(&pnew,data);pnew->next = ptemp->next;pnew->front = ptemp;if(NULL != ptemp->next){ptemp->next->front = pnew;}ptemp->next = pnew;return 0;}
  • 操作步骤:
  • 1.遍历链表,找到待插入位置的前一个结点,即ptemp;
  • 2.创建新结点pnew
  • 3.此处使用头插法插入即可;

双向链表的三种删除方式 :

头删法:

  • 删除双向链表头结点后的结点,即头删法,代码如下:
  • 示例代码:
	int delete_dplink_list_1(node_t *phead){if(NULL == phead){printf("入参为NULL\n");return -1;}if(NULL == phead->next){printf("链表只有一个头结点,无其他的结点\n");return -1;}node_t *pdel = phead->next;if(NULL != pdel->next){pdel->next->front = phead;}phead->next = pdel->next;free(pdel);pdel = NULL;return 0;}
  • 操作步骤:
  • 1.定义待删结点pdel,并将待删结点的指针指向头结点的后继结点地址,即node_t *pdel = phead->next
  • 2.判断双向链表是否有第0个结点;
  • 3.若有,则让待删除结点的下一个结点的前驱指针指向头结点地址,即pdel->next->front = phead
  • 4.最后,头结点的后继指针指向待删除结点的下一个结点的地址,即phead->next = pdel->next
  • 5.用free函数释放待删结点所占用的空间,即 free(pdel)
  • 6.防止野指针产生,给待删结点的地址赋值NULL,即pdel = NULL

尾删法:

  • 删除双向链表的最后一个结点,即尾删法,代码如下:
  • 示例代码:
	int delete_dplink_list_2(node_t *phead){if(NULL == phead){printf("入参为NULL\n");return -1;}if(NULL == phead->next){printf("链表只有一个头结点,无其他的结点\n");return -1;}//遍历链表,找到倒数第二个结点node_t *ptemp = phead;while(NULL != ptemp->next->next){ptemp = ptemp->next;}free(ptemp->next);ptemp->next = NULL;return 0;}
  • 操作步骤:
  • 1.利用while循环,遍历双向链表,找到倒数第二个结点,即ptemp
  • 2.释放ptemp后继指针,并赋值NULL,这样就删除了双向链表的尾结点;
  • 友情提示:
  • 单向链表和双向链表的尾删法基本一致

任意位置删除旧节点:

  • 选择结点在链表中的位置,然后根据链表元素的位置,删除待删结点,代码如下:

  • 示例代码:

	int delete_dplink_list_3(node_t *phead,int pos){if(NULL == phead){printf("入参为NULL\n");return -1;}if(NULL == phead->next){printf("链表只有一个头结点,无其他的结点\n");return -1;}if(pos < 0){printf("删除位置不合理,删除失败\n");return -1;}node_t *ptemp = phead;int i = 0;for(i = 0; i < pos; i++){ptemp = ptemp->next;if(NULL == ptemp->next){break;}}if(i < pos){printf("删除位置不合理,删除失败\n");return -1;}node_t *pdel = ptemp->next;if(NULL != pdel->next){pdel->next->front = ptemp;}ptemp->next = pdel->next;free(pdel);pdel = NULL;return 0;}
  • 操作步骤:
  • 1.找到双向链表待删结点的前一个结点ptemp;
  • 2.此处类比使用上述头删法即可;

双向链表的翻转

  • 单向链表翻转思路一致,都是 将第0个数据结点后面的所有数据结点,依次头插头结点和第0个数据结点之间即可,代码如下:
  • 示例代码:
//翻转
int filp_dplink_list(node_t *phead){if(NULL == phead){printf("入参为NULL,请检查..\n");return -1;}if(NULL == phead->next){printf("只有一个头结点\n");return -1;}if(NULL == phead->next->next){printf("只有一个数据结点\n");return -1;}node_t *p = phead->next;node_t *q = p->next;node_t *ptemp = NULL;p->next = NULL;while(NULL != q){ptemp = q->next;q->next = phead->next;q->front = phead;phead->next->front = q;phead->next = q;q = ptemp;}return 0;
}
  • 注意事项:
  • 1.定义一个指针,用来保存指针q的指针域,即ptemp = q->next,以便于循环遍历双向链表的所有数据结点,即每次循环结束前,令q = ptemp,就可以继续向后遍历其他的数据结点
  • 2.采用头插法的形式,并利用while循环,将第1个数据结点其以后的所有数据结点依次头插到头结点和第0个数据结点之间,直到指针q为NULL,即原双向链表第0个结点的指针域为NULL,就完成了双向链表的所有数据结点的翻转

相关提示:

  • 其他的操作,诸如双向链表的节点查找、节点修改、节点排序、节点去重、节点的清空、节点的销毁等相关操作均与单向链表的操作保持一致,本文不再赘述
  • 可以参考以下单向链表各种操作合集链接:
	https://blog.csdn.net/qq_41878292/article/details/135679744

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

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

相关文章

【设计模式】什么是外观模式并给出例子!

什么是外观模式&#xff1f; 外观模式是一种结构型设计模式&#xff0c;主要用于为复杂系统、库或框架提供一种简化的接口。这种模式通过定义一个包含单个方法的高级接口&#xff0c;来隐藏系统的复杂性&#xff0c;使得对外的API变得简洁并易于使用。 为什么要使用外观模式&a…

Ubuntu安装mysql8详细步骤

1、拉取镜像 docker pull mysql:8.0.20 2、启动镜像 docker run -p 3307:3306 --name mysql8 -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0.20 检查是否启动成功 docker ps 3、配置挂载 创建挂载目录&#xff08;请检查保证创建成功&#xff09; mkdir -p /docker/mysql8.0.20/…

统计学-R语言-7.2

文章目录 前言总体均值的检验总体均值的检验(一个总体均值的检验)两个总体均值之差的检验 总体比例的检验一个总体比例的检验 练习 前言 本篇将继续上篇文章进行介绍。 总体均值的检验 总体均值的检验(一个总体均值的检验) 小样本的检验 假定条件 小样本(n<30) 总体服从正…

如何利用 APM 追踪完整的类函数调用

通常&#xff0c;应用接入 APM 后&#xff0c;可以追踪到应用相关组件、服务间的调用链路情况&#xff0c;如 Tomcat、Redis、MySQL 等&#xff0c;这是因为 APM 对于标准性组件做了插桩处理&#xff0c;从而更好的观测到在实际使用过程中组件调用对应用的影响。 而在实际生产…

揭秘AI换脸技术:从原理到应用

随着人工智能技术的不断发展&#xff0c;AI换脸技术逐渐成为人们关注的焦点。这项神奇的技术能够将一张图像或视频中的人脸替换成另一张人脸&#xff0c;让人不禁惊叹科技的神奇。那么&#xff0c;AI换脸技术究竟是如何实现的呢&#xff1f;本文将带您深入了解AI换脸技术的原理…

55 C++ 多线程 返回值问题。引出的 async,future,packaged_task,promise.

一 前提&#xff0c;thread返回值的写法 在之前的代码中&#xff0c;我们并没有讨论 子线程的返回值问题。 这一章就考虑这个问题怎么处理。 下面我们先按照之前的写法&#xff0c;我们需要返回值时的可能的fix方案。 //如果线程有返回值&#xff0c;并且主线程要得到这个返…

《WebKit 技术内幕》之五(3): HTML解释器和DOM 模型

3 DOM的事件机制 基于 WebKit 的浏览器事件处理过程&#xff1a;首先检测事件发生处的元素有无监听者&#xff0c;如果网页的相关节点注册了事件的监听者则浏览器会将事件派发给 WebKit 内核来处理。另外浏览器可能也需要处理这样的事件&#xff08;浏览器对于有些事件必须响应…

logstack 日志技术栈-04-opensource 开源工具 OpenObserve+Grafana Loki

日志技术栈 日志管理包含日志数据存储、处理、分析和可视化&#xff0c;通过利用日志管理工具&#xff0c;可以监控性能趋势、解决问题、检测异常并优化整体系统性能。 近年来&#xff0c;开源日志管理解决方案在大家寻求灵活且经济有效的方式来管理现代系统典型的大量日志数…

JVM问题分析处理手册

一.前言 各位开发和运维同学&#xff0c;在项目实施落地的过程中&#xff0c;尤其是使用EDAS、DRDS、MQ这些java中间件时&#xff0c;肯定会遇到不少JAVA程序运行和JVM的问题。我结合过去遇到的各种各样的问题和实际处理经验&#xff0c;总结了JAVA问题的处理方式&#xff0c;…

soso移动营业大厅(纯后端+MySQL数据库+JDBC)

一、项目需求 中国移动,中国联通,中国电信是国内3大通信运营商,每个运营商都提供了不同的品牌套餐来应对不同的用户群,比如北京移动主要有全球通,神州行,动感地带等3大品牌套餐,每种套餐的内容和费用不同,嗖嗖移动是一个假定的通信运营商,提供了话痨套餐,网虫套餐,超人套餐,各…

等离子环制作

免责声明 在您参考该博客制作等离子环前&#xff0c;请仔细阅读以下重要安全警告和免责说明。使用本文档即表示您已充分了解并同意以下条款&#xff1a; 等离子环的危险性&#xff1a;等离子环在运行时玻璃瓶身会产生高温&#xff0c;存在低温烧伤风险。任何时候都不建议用手…

C for Graphic:Sliced Circle Image

不做UI不知道&#xff0c;没想到时至今日&#xff0c;ugui居然没有sliced filled image模式&#xff0c;用circle做filled&#xff0c;不能用sliced九宫格图&#xff0c;导致每次使用这个效果必须一张新图&#xff0c;何其浪费资源。 原始功能如下&#xff1a; 我…

contextlib.contextmanager函数装饰器介绍

contextlib 是 Python 标准库中的一个模块&#xff0c;提供了一些用于创建上下文管理器&#xff08;context manager&#xff09;的实用工具。contextlib.contextmanager 是 contextlib 模块中的一个装饰器&#xff0c;用于将一个生成器函数转换为上下文管理器。通过使用这个装…

LC 2788. 按分隔符拆分字符串

2788. 按分隔符拆分字符串 难度 简单 题目大意&#xff1a; 给你一个字符串数组 words 和一个字符 separator &#xff0c;请你按 separator 拆分 words 中的每个字符串。 返回一个由拆分后的新字符串组成的字符串数组&#xff0c;不包括空字符串 。 注意 separator 用于决…

Go语言协程使用

主协程执行打印&#xff0c;子协程不打印 package main import ("fmt" )func do(i int) {fmt.Println("执行中") } func main() {fmt.Println("main协程")go do(1)fmt.Println("执行完了") }//main协程 //执行完了子协程没有打印输出…

椋鸟C语言笔记#36:从源代码到运行

萌新的学习笔记&#xff0c;写错了恳请斧正。 目录 从源代码到运行 翻译环境 编译 预处理 编译 汇编 链接 运行环境 从源代码到运行 在ANSI C的标准中&#xff0c;源代码先经过翻译环境生成可执行程序&#xff0c;再于运行环境中执行 翻译环境 翻译环境由编译与链接…

【C++】unordered_map,unordered_set模拟实现

unordered_map&#xff0c;unordered_set模拟实现 插入普通迭代器const迭代器unordered_map的[ ]接口实现查找修改哈希桶完整代码unordered_map完整代码unordered_set完整代码 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 上一篇文章我们把unordered_map和u…

爬虫之Cookie获取:利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法

爬虫之Cookie获取&#xff1a;利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法 在爬虫或模拟请求时&#xff0c;特别是获取验证码的时候&#xff0c;反爬虫的网站的cookie或定期失效&#xff0c;复制出来使用是不行的为了应对这种方式&#xff0c;我们可能…

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Windows下载安装vcpkg并使用它来安装第三方库(visualstudio)

1.使用Git下载vcpkg仓库&#xff08;下载比较慢&#xff0c;个人比较喜欢打开下面网址然后用迅雷下载&#xff0c;速度飞快&#xff09; git clone "https://github.com/Microsoft/vcpkg.git"2.下载好之后解压打开文件夹&#xff0c;双击bootstrap-vcpkg.bat文件&…