【数据结构】双向链表详解

在这里插入图片描述
当我们学习完单链表后,双向链表就简单的多了,双向链表中的头插,尾插,头删,尾删,以及任意位置插,任意位置删除比单链表简单,今天就跟着小张一起学习吧!!

双向链表的分类

双向不带头链表

在这里插入图片描述

双向带头循环链表

在这里插入图片描述

还有双向带头不循环链表,双向不带头循环链表,着重使用双向带头循环链表,带头也就是有哨兵位。

双向带头循环链表

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

双向循环链表的接口实现

ListNode* ListCreate(int x)//创建新结点
ListNode* createhead()//创建哨兵位
void ListPushBack(ListNode* phead, int x)//尾插
void SListPrint(ListNode* phead)//打印链表
void SListPushFront(ListNode* phead, int x)//头插
void SListPopBack(ListNode* phead)//尾删
void SListPopFront(ListNode* phead)//头删
void SListInsert(ListNode* pos, int x)//pos前插
void SListErase(ListNode* pos)//删除pos;
ListNode* SListFind(ListNode* phead,int x)//查找链表中第一个x

0.结点结构体创建

typedef struct ListNode
{int data;//数据struct ListNode* next;//下个结点地址struct ListNode* prev;//上个结点地址
}ListNode;

1.创建一个新结点

ListNode* ListCreate(int x)//创建新结点
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//给新结点申请空间if (newnode == NULL){perror("malloc error");//没申请到,malloc返回NULL给newnode}newnode->data = x;//新结点的数据给xnewnode->next = NULL;//新结点的下一个结点地址为空newnode->prev = NULL;//新结点的上一个结点地址为空return newnode;//新结点的地址返回回去
}

2.创建哨兵位

该哨兵位作为链表的头结点,不存数据

ListNode* createhead()
{ListNode* head = ListCreate(-1);//哨兵位结点的数据随便给个-1head->next = head;head->prev = head;return head;//返回哨兵位的头结点地址
}

在这里插入图片描述当没有带数据的新结点时,先让他上一个结点地址,和下一关结点地址都存入head自己的地址

3.尾插

void ListPushBack(ListNode* phead, int x)//尾插
{ListNode* newnode = ListCreate(int x);ListNode* tail = phead->prev;//记录尾结点的地址newnode->prev =tail;//新结点的prev存放尾结点的地址-1newnode->next = phead;//新结点的next存放头结点哨兵位的地址->2phead->prev = newnode;//头结点的prev存放新结点的地址->4tail->next = newnode;//尾结点的next存放新节点的地址->3}

分析:在这里插入图片描述
由于使用tail记录了尾结点的地址,所以1,2,3,4可以任意切换顺序,如果没有记录,记得先将新结点的prev,next,先保存,然后在修改head->prev, head->prev->next;防止修改过程中,尾结点的地址丢失。
在这里插入图片描述
同样适用于在哨兵位后插新结点

4.打印双链表

void SListPrint(ListNode* phead)//打印链表
{ListNode* cur = phead->next;while (cur != phead){printf("%d->", cur->data);cur = cur->next;}printf("\n");}

分析:定义一个指针cur遍历整个链表,由于是循环链表,肯定会指向哨兵位,哨兵位结点的数据不使用,所以cur指针从头节点哨兵位的下一个结点开始遍历,直到cur==phead,循环结束,每次打印cur->data,然后移动cur到下一个结点。

5.头插

void SListPushFront(ListNode* phead, int x)//头插
{ListNode* newnode = ListCreate(x);newnode->next = phead->next;phead->next->prev = newnode;newnode->prev = phead;phead->next = newnode;}

分析:在这里插入图片描述
注意:先保存newnode->next;和newnode->prev;
此时如果先进行4的话,phead->next的地址就变成了newnode,之前的phead->next地址丢失掉。
还有一种方法是先保存* next=phead->next;然后1,2,3,4,顺序可以调换。

6.尾删

void SListPopBack(ListNode* phead)//尾删
{ListNode* last = phead->prev->prev;//记录尾结点的上一个结点的地址phead->prev = last;//头节点哨兵位的prev存入last的地址last->next = phead;//last的next存入phead的地址}

分析:在这里插入图片描述当要删除尾结点,我们可以先记录尾结点的上一个结点的地址,因为要删除尾结点,改变就是将尾结点的上一个结点last的next存phead的地址,phead的prev存入的是last的地址
在这里插入图片描述除哨兵位还有一个结点的尾删剩下一个哨兵位头结点,同样适用上面的代码

7.头删

void SListPopFront(ListNode* phead)//头删
{ListNode* next = phead->next;phead->next = next->next;next->next->prev = phead;
}

分析:在这里插入图片描述先保存哨兵位的下一个结点地址到next,然后将phead的next中存入next->next;将next->next->prev存入phead的首地址

8.pos指针指向的结点前插

void SListInsert(ListNode* pos, int x)//pos前插
{ListNode* newnode = ListCreate(x);//申请新结点将地址存放在newnode变量中newnode->next = pos;//新结点的下一个结点保存pos指针指向节点的地址newnode->prev = pos->prev;//新结点的prev保存pos指针指向的节点的上一个节点的地址pos->prev->next=newnode;//pos指针指向的节点的前一个结点的next保存newnode指针指向的结点pos->prev = newnode;//pos指针指向的结点的prev保存newnode指针指向结点的地址
}

分析:在这里插入图片描述

9.删除pos指针指向的结点

void SListErase(ListNode* pos)//删除pos;
{ListNode* last = pos->prev;//记录pos指针指向结点的前一个结点地址ListNode* next = pos->next;//记录pos指针指向结点的后一个结点地址last->next = next;//操作1next->prev = last;//操作2}

分析:在这里插入图片描述

10.查找链表第一个出现的x

ListNode* SListFind(ListNode* phead,int x)
{ListNode* cur = phead->next;//cur指针先指向phead的下一个结点的位置while (cur != phead)//循环遍历{if (cur->data == x)//第一次找到{return cur;返回结点数据等于x的地址}cur = cur->next;//cur指针指向下一个结点}return NULL;
}

分析:类似于打印打印链表,遍历循环,从phead->next开始遍历,当cur不等于phead继续循环,如果cur->datax,就return cur的内容,也就是datax的结点地址,循环结束没有找到的话,就是该链表没有等于x的结点返回NULL;

11.双向链表的销毁

void Destroy(ListNode* phead)
{ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);
}

分析,定义一个指针cur先指向phead的下一个结点,开始遍历,先保存cur指向结点的下一个结点的地址到next,然后释放掉cur指针指向的空间,然后让cur指向已经保存在next中下一个结点的地址,依次循环释放掉所有的结点,循环结束,释放掉哨兵位。

12.完整源码

#include <stdio.h>
#include <stdlib.h>
typedef struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}ListNode;
ListNode* ListCreate(int x)//创建新结点
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc error");//return;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
ListNode* createhead()
{ListNode* head = ListCreate(-1);head->next = head;head->prev = head;return head;
}
void ListPushBack(ListNode* phead, int x)//尾插
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->data = x;ListNode* tail = phead->prev;newnode->prev =tail;phead->prev = newnode;tail->next = newnode;newnode->next = phead;}
void SListPrint(ListNode* phead)//打印链表
{ListNode* cur = phead->next;while (cur != phead){printf("%d->",cur->data);cur = cur->next;}printf("\n");}
void SListPushFront(ListNode* phead, int x)//头插
{ListNode* newnode = ListCreate(x);newnode->next = phead->next;phead->next->prev = newnode;newnode->prev = phead;phead->next = newnode;}
void SListPopBack(ListNode* phead)//尾删
{ListNode* last = phead->prev->prev;phead->prev = last;last->next = phead;}
void SListPopFront(ListNode* phead)//头删
{ListNode* next = phead->next;phead->next = next->next;next->next->prev = phead;
}
void SListInsert(ListNode* pos, int x)//pos前插
{ListNode* newnode = ListCreate(x);newnode->next = pos;newnode->prev = pos->prev;pos->prev->next=newnode;pos->prev = newnode;
}
void SListErase(ListNode* pos)//删除pos;
{ListNode* last = pos->prev;ListNode* next = pos->next;last->next = next;next->prev = last;}
ListNode* SListFind(ListNode* phead,int x)
{ListNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
//双链表的销毁
void Destroy(ListNode* phead)
{ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);
}
int main()
{ListNode* list;list = createhead();printf("尾插:");ListPushBack(list,1);ListPushBack(list,2);ListPushBack(list,3);ListPushBack(list,4);SListPrint(list);printf("头插:");SListPushFront(list, 8);SListPushFront(list, 7);SListPushFront(list, 6);SListPushFront(list, 5);SListPrint(list);printf("尾删:");SListPopBack(list);SListPopBack(list);SListPrint(list);printf("头删:");SListPopFront(list);SListPopFront(list);SListPopFront(list);SListPrint(list);printf("插入pos指向结点前面:");SListInsert(list->next->next, 1000);SListPrint(list);printf("删除pos指向的结点:");SListErase(list->next->next);SListPrint(list);printf("查找第一个x的位置并打印出来:");ListNode* p=SListFind(list, 8);printf("%d", p->data);printf("修改查找到的结点:\n");p->data = 10000;SListPrint(list);printf("销毁打印销毁后哨兵位的下一个结点的数据,如果为随机值说明已经被销毁:");Destroy(list);printf("%d",list->data);}

13.编译运行

在这里插入图片描述

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

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

相关文章

12个微服务架构模式最佳实践

微服务架构是一种软件开发技术&#xff0c;它将大型应用程序分解为更小的、可管理的、独立的服务。每个服务负责特定的功能&#xff0c;并通过明确定义的 API 与其他服务进行通信。微服务架构有助于实现软件系统更好的可扩展性、可维护性和灵活性。 接下来&#xff0c;我们将介…

vue中预览xml并高亮显示

项目中有需要将接口返回的数据流显示出来&#xff0c;并高亮显示&#xff1b; 1.后端接口返回blob,类型为xml,如图 2.页面中使用pre code标签&#xff1a; <pre v-if"showXML"><code class"language-xml">{{xml}}</code></pre> …

RJ45水晶头网线顺序出错排查

线序 网线水晶头RJ45常用的线序标准ANSI / TIA-568定义了T568A与T568B两种线序&#xff0c;一般使用T568B&#xff0c;水晶头8个孔对应的8条线颜色如下图&#xff1a; 那1至8的编号&#xff0c;是从水晶头哪一面为参考呢&#xff0c;如下图&#xff0c;是水晶头金手指一面&am…

Docker从认识到实践再到底层原理(四-2)|Docker镜像仓库实战案例

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

SVN 索引版本与打包版本号不匹配

今天突然遇到了一个问题&#xff0c;SVN上传不了&#xff0c;错误提示如下&#xff1a; 解决方法&#xff1a; 1.其实&#xff0c;这是SVN库不小心搞坏了&#xff0c;只能重新再创建一个SVN仓库了。

基于Hugo 搭建个人博客网站

目录 1.环境搭建 2.生成博客 3.设置主题 4.将博客部署到github上 1.环境搭建 1&#xff09;安装Homebrew brew是一个在 macOS 操作系统上用于管理软件包的包管理器。类似于centos下的yum或者ubuntu下的apt&#xff0c;它允许用户通过命令行安装、更新和管理各种软件工具、…

自动化测试:Selenium中的时间等待

在 Selenium 中&#xff0c;时间等待指在测试用例中等待某个操作完成或某个事件发生的时间。Selenium 中提供了多种方式来进行时间等待&#xff0c;包括使用 ExpectedConditions 中的 presence_of_element_located 和 visibility_of_element_located 方法等待元素可见或不可见&…

受检异常和非受检异常

异常 非受检异常和受检异常&#xff0c;都是继承自 Throwable 这个类中&#xff0c;分别是 Error 和 Exception&#xff0c; Error 是程序报错&#xff0c;系统收到无法处理的错误消息&#xff0c;它和程序本身无关。 Excetpion 是指程序运行时抛出需要处理的异常信息如果不主…

博客系统项目

文章目录 数据库的增删改查草稿箱草稿箱自动保存分页查询后端前端 评论区后端前端 md5加盐加密 md5加盐对用户密码进行加密; 全服用户博客列表页,实现分页查询; 用户博客列表页; 写博客,发博客,改博客; 博客草稿箱,自动保存,定时发布; 博客访问量,博客评论区,博客点赞; 数据库…

d3.js 的使用

这篇文章相当于之前 svg 的补充。 因为 svg 代码肯定不是人为去专门写的。 在这里推荐制作 svg 的第三方库 - D3.js 用于定制数据可视化的JavaScript库 - D3 官网地址&#xff1a; D3 by Observable | The JavaScript library for bespoke data visualization 简单使用 画…

openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库

文章目录 openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库65.1 前提条件65.2 背景信息65.3 注意事项65.4 操作步骤65.4.1 创建数据库65.4.2 查看数据库65.4.3 修改数据库65.4.4 删除数据库 openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库 65.1 前提…

PHP8的多维数组-PHP8知识详解

今天分享的是php8的数组中的多维数组&#xff0c;主要内容有&#xff1a;多维数组的概念、创建和输出二维数组、创建和输出三维数组。 1、多维数组的概念 多维数组是包含一个或多个数组的数组。在多维数组中&#xff0c;主数组中的每一个元素也可以是一个数组&#xff0c;子数…