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

数据结构之“双向链表”

文章目录

  • 1. 链表的分类
  • 2.双向链表
    • 2.2 双向链表的打印
  • 3.双向链表的插入/删除
    • 3.1双向链表的尾插
    • 3.2双线链表的头插
    • 3.3双向链表的尾删
    • 3.4双线链表的头删
  • 4. 在pos位置之后插入结点
  • 5. 删除pos指定位置的结点
  • 6. 双向链表的销毁
  • 7.比较

1. 链表的分类

链表的种类有(2x2x2)种,带头/不带头;单向/双向;循环/不循环。

带头/不带头
在之前学习单链表(不带头单向不循环链表)时,它是不带头的,它的第一个结点是可以变的,比如: 我们还可以将第一个结点删除;头插一个数据,等等
但是带头的链表,它的第一个结点(头结点)存储的不是有效数据,其余结点存储的是有效数据。头结点俗称为“哨兵位”,它只是用来占位子的。

在这里插入图片描述

单向/双向
单向:只能通过前一个的next找到下一个结点,不能通过下一个结点找到前一个结点的地址。
双向:通过前一个可找到后一个,通过后一个可找到前一个

在这里插入图片描述

循环/不循环
在这里插入图片描述

2.双向链表

1.全称是:带头双向循环链表
2.双向链表的结点结构:数据+下一个结点的地址next+上一个结点的地址prev

typedef int DataType;//双像链表的定义
typedef struct ListNode
{DataType val;struct ListNode* next;struct ListNode* prev;
};

3.双向链表在使用之前就要有头结点(哨兵位)。(初始化创建一个头结点)
记得:双向链表是:带头双向循环链表,要循环!!!那必定next和prev不可能指向空,而是指向它自己

//LTNode.c里的内容LTNode* LTBuynode(DataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode; //上/下结点地址都是自己,达到循环的目的return newnode;
}void LTInit(LTNode** pphead)
{//创建一个头结点*pphead = LTBuynode(-1);
}

在这里插入图片描述

4.双向链表在插入新结点的时候,不需要判断链表有没有头结点。双向链表一定有头结点(哨兵位)。
5.双向链表为空:只有哨兵位。
6.记得写#include<malloc.h> #include<stdlib.h>

2.2 双向链表的打印

记得注意循环打印链表时,什么时候停止打印

void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next; //pcur等于谁呢?不要写成哨兵位了,要写成第一个有效结点while (pcur!=phead)   //双向链表打印到什么时候停止,当pcur==phead时{printf("%d(%p) ->", pcur->val, pcur);pcur = pcur->next;}printf("NULL\n");
}

3.双向链表的插入/删除

双向链表插入或者删除,传过去的都是一级指针。因为phead是头结点,也就是哨兵位。哨兵位是不能被改变的,所以传一级指针。

3.1双向链表的尾插

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2双线链表的头插

重点:头插是指将新数据成为第一个有效结点,而不是换掉“哨兵位”。

在这里插入图片描述
在这里插入图片描述

void LTPushFront(LTNode* phead, DataType x)
{assert(phead);LTNode* newnode = LTBuynode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}

3.3双向链表的尾删

如果双向链表里,只有一个哨兵位,也是不可以删除的。所以要对链表进行判断,是否为空(空就是:phead->next=phead; phead->prev=phead;)。

判断用bool,记得包含头文件#include<stdbool.h>

bool LTempty(LTNode* phead)
{//传进来的哨兵位不能为空assert(phead);return phead->next == phead;//如果相等,则是return true
}
void LTPopBack(LTNode* phead)
{assert(phead);assert(!LTempty(phead));  //若链表不为空,则LTempty(phead)返回的是false,!LTempty(phead)则是trueLTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del->val = 0;del->next = del->prev = NULL;
}

3.4双线链表的头删

bool LTempty(LTNode* phead)
{//传进来的哨兵位不能为空assert(phead);return phead->next == phead;//如果相等,则是return true
}
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTempty(phead));  //若链表不为空,则LTempty(phead)返回的是false,!LTempty(phead)则是trueLTNode* del = phead->next;LTNode* Next = del->next;phead->next = Next;Next->prev = phead;free(del);del->val = 0;del->next = del->prev = NULL;
}

4. 在pos位置之后插入结点

不需要头结点,只需要pos

LTNode* LTFind(LTNode* phead, DataType x)
{assert(phead);LTNode* pcur = phead->next; //要让pcur是第一个有效结点while (pcur != phead){if (pcur->val == x)return pcur;pcur = pcur->next;}
}
void LTInsert(LTNode* pos, DataType x)
{assert(pos);LTNode* newnode = LTBuynode(x); //插入的数据LTNode* Next = pos->next;newnode->prev = pos;newnode->next = Next;pos->next = newnode;Next->prev = newnode;
}

在这里插入图片描述

5. 删除pos指定位置的结点

LTNode* LTFind(LTNode* phead, DataType x)
{assert(phead);LTNode* pcur = phead->next; //要让pcur是第一个有效结点while (pcur != phead){if (pcur->val == x)return pcur;pcur = pcur->next;}
}
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos->val = 0;pos->prev = pos->next = NULL;
}

6. 双向链表的销毁

循环,将每一个结点都销毁(先保存pcur->next,防止pcur销毁之后,找不到下一个结点)。最后将哨兵位也销毁。

传二级指针

void LTDestroy(LTNode** pphead)
{assert(pphead && *pphead);LTNode* pcur = (*pphead)->next;while (pcur != (*pphead)){LTNode* Next = pcur->next;free(pcur);pcur = NULL;pcur = Next;}free(*pphead);*pphead = NULL;
}

7.比较

在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 海外合规|新加坡网络安全认证计划简介(一)
  • k8s集群的调度
  • 如何使用事件流相关操作
  • WHAT - React 函数与 useMemo vs useCallback
  • 打工人应了解的裁员大礼包法律知识
  • c++的面向过程与面向对象
  • HNU-2023电路与电子学-实验1
  • ruoyi-vue-pro快速修改的包名和选配功能板块
  • Python操作数据库的ORM框架SQLAlchemy快速入门教程
  • 运维领域的先进思想和趋势
  • timm从本地加载预训练模型
  • Docker 容器编排之 Docker Compose
  • OpenHarmony鸿蒙开发( Beta5.0)智能手表应用开发实践
  • Unity【Colliders碰撞器】和【Rigibody刚体】的应用——小球反弹效果
  • C#读写锁与并发控制
  • 【Leetcode】101. 对称二叉树
  • “大数据应用场景”之隔壁老王(连载四)
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • CentOS从零开始部署Nodejs项目
  • ES6 ...操作符
  • fetch 从初识到应用
  • gcc介绍及安装
  • HTTP请求重发
  • JAVA_NIO系列——Channel和Buffer详解
  • SSH 免密登录
  • 从零开始在ubuntu上搭建node开发环境
  • 搞机器学习要哪些技能
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 简单实现一个textarea自适应高度
  • 我这样减少了26.5M Java内存!
  • 2017年360最后一道编程题
  • 阿里云重庆大学大数据训练营落地分享
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • # include “ “ 和 # include < >两者的区别
  • ###C语言程序设计-----C语言学习(3)#
  • #if 1...#endif
  • #pragma once与条件编译
  • (2015)JS ES6 必知的十个 特性
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (Java入门)学生管理系统
  • (LeetCode) T14. Longest Common Prefix
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (笔试题)分解质因式
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (算法)Game
  • (一)基于IDEA的JAVA基础1
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)setTimeout 和 setInterval 的区别
  • (转载)利用webkit抓取动态网页和链接
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .NET CORE Aws S3 使用
  • .NET Core 中的路径问题
  • .NET Core中的去虚
  • .NET 事件模型教程(二)