在上一篇所讲述的 单链表 中,存在一些缺陷:

  • 1、在进行尾插和尾删时,需要遍历链表找到尾结点
  • 2、在进行中间插入和删除时,也需要先遍历链表找到前一个结点

对于这些缺陷,可以采用一种结构更为复杂的链表 双向带头循环链表

双向带头循环链表结构虽然复杂,但在链表的操作上带来了很大的优势

文章目录

  • 一、双向带头循环链表的结构
  • 二、双向带头循环链表的函数接口
    • 1. 申请结点
    • 2. 初识化
    • 3. 打印
    • 4. 尾插尾删
    • 5. 头插头删
    • 6. 查找
    • 7. 中间插入和删除
    • 8. 判空及求链表长度
    • 9. 销毁单链表

一、双向带头循环链表的结构

//存储数据的类型,这里以 int 来举例
typedef int LTDataType;//结点的类型
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}LTNode;

二、双向带头循环链表的函数接口

1. 申请结点

在插入等操作时需要申请结点,为了避免麻烦重复的操作,这里将申请结点封装为一个函数

申请结点函数如下:

LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){//开辟空间失败,打印错误信息perror("malloc");//结束程序exit(-1);}newnode->data = x;newnode->prev = newnode->next = NULL;return newnode;
}

2. 初识化

在双向带头循环链表中,即使没有存储数据也 至少会包含一个哨兵位的头结点

初始化函数如下:

LTNode* InitLT()
{//申请头结点,头结点的数据存什么无关紧要LTNode* phead = BuyLTNode(-1);//改变指针指向,构成循环phead->prev = phead->next = phead;return phead;
}

3. 打印

为了验证插入、删除等得到的结果是否正确,提供打印函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改函数即可

打印函数如下:

void LTPrint(LTNode* phead)
{//链表不能为空assert(phead);LTNode* cur = phead->next;printf("head->");while (cur != phead){printf("%d->", cur->data);cur = cur->next;}printf("head\n");
}

4. 尾插尾删

尾插:在链表的最后一个结点之后插入结点

尾插函数如下:

void LTPushBack(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* newnode = BuyLTNode(x);//找到尾结点LTNode* tail = phead->prev;//改变指针指向tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

尾删:删除链表最后一个结点

尾删函数如下:

void LTPopBack(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead);   //空链表不能删//找尾结点及尾结点的前一个结点LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;//改变指针指向tailPrev->next = phead;phead->prev = tailPrev;free(tail);
}

5. 头插头删

头插: 在第一个结点之前插入新结点

头插函数如下:

void LTPushFront(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* newnode = BuyLTNode(x);//找到头结点后的第一个结点LTNode* first = phead->next;//改变指针指向phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}

头删:删除链表的第一个结点

头删函数如下:

void LTPopFront(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead);   //空链表不能删//找到头结点后的第一个和第二个结点LTNode* first = phead->next;LTNode* second = first->next;//改变指针指向phead->next = second;second->prev = phead;free(first);
}

6. 查找

查找:如果数据存在,返回该数据结点的指针,不存在返回 NULL

查找函数如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x) return cur;cur = cur->next;}return NULL;
}

7. 中间插入和删除

中间插入:通过查找函数 LTFind 获得指向结点的指针 pos,在 pos 指向的 结点之前 插入结点

在 pos 之前插入结点函数如下:

void LTInsert(LTNode* pos, LTDataType x)
{//pos 不能为空assert(pos);LTNode* newnode = BuyLTNode(x);//找到 pos 的前一个结点LTNode* posPrev = pos->prev;//改变指针指向posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode;
}

在调用中间插入函数 LTInsert 时

  • 如果在链表头结点之前插入数据,便和尾插函数的功能一样
  • 如果在链表头结点之后插入数据,便和头插函数的功能一样

因此在尾插和头插函数的实现中可以直接调用中间插入函数 LTInsert

尾插和头插函数更改如下:

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTInsert(phead, x);
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTInsert(phead->next, x);
}

中间删除:通过查找函数 LTFind 获得指向结点的指针 pos,删除 pos 指向的结点

删除 pos 指向的结点函数如下:

void LTErase(LTNode* pos)
{//pos 不能为空assert(pos);//找到 pos 的前一个和后一个结点LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;//改变指针指向posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}

在调用中间删除函数 LTErase 时

  • 如果删除链表头结点的前一个结点,便和尾删函数的功能一样
  • 如果删除链表头结点的后一个结点,便和头删函数的功能一样

因此在尾删和头删函数的实现中可以直接调用中间删除函数 LTErase

尾删和头删函数更改如下:

//尾删
void LTPopBack(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead);   //空链表不能删LTErase(phead->prev);
}//头删
void LTPopFront(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead);   //空链表不能删LTErase(phead->next);
}

8. 判空及求链表长度

判空:判断链表是否为空

判空函数如下:

bool LTEmpty(LTNode* phead)
{//链表不能为空assert(phead);return phead->next == phead;
}

链表长度:链表有效数据个数

链表长度函数如下:

size_t LTSize(LTNode* phead)
{//链表不能为空assert(phead);size_t size = 0;LTNode* cur = phead->next;while (cur != phead){size++;cur = cur->next;}return size;
}

9. 销毁单链表

在链表中,存储数据的结点是由自己开辟的,当不使用链表时,应将其销毁

销毁链表函数如下:

void LTDestroy(LTNode* phead)
{//链表不能为空assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* curNext = cur->next;free(cur);cur = curNext;}free(phead);
}

双向带头循环链表详解相关推荐

  1. GPIO输入输出各种模式(推挽、开漏、准双向端口)详解---重点双向开漏端口

    GPIO输入输出各种模式(推挽.开漏.准双向端口)详解 原创techexchangeischeap 发布于2017-05-20 10:31:31 阅读数 62961  收藏 展开 技术交流是便宜的博客 ...

  2. 【数据结构初阶】双向带头循环链表原来是纸老虎,结构复杂,操作简单

    目录 0.结构体定义 1.初始化 2.尾插 3.打印 4.头插 5.任意位置插入前面位置 6.尾删 7.头删 8.链表长度 9.任意位置删除当前位置 10. 销毁 双向带头循环链表:结构复杂,操作简单 ...

  3. 链表界的“扛把子”—双向带头循环链表

    目录 一:链表的分类 二:链表的实现 2-1:创建链表 2-2:创建新的结点 2-3:初始化链表 2-4:打印链表 2-5:销毁链表 三:链表的核心功能 3-1:尾插 3-2:尾删 3-3:链表查找 ...

  4. 双向带头循环链表的(增删查改)的实现

    文章目录 一.双向带头循环链表 构成 二.双向带头循环链表的实现 1.函数的定义和结构体的创建--list.h 2.函数的调用--list.c 3. 双向带头循环链表与单链表的传递参数区别 4.双向带 ...

  5. 数据结构------双向带头循环链表

    目录 1.什么是双向带头循环链表?模型. 2.1 创建结点函数 2.2初始化 3.尾插实现 3.1图示 3.2尾插 4.打印函数实现 4.1打印函数 5.思考为啥是一级指针 6.头插实现 6.2实现代 ...

  6. 双向带头循环链表的实现

    双向带头循环链表 双向带头循环链表 结构讲解 期望实现功能 创建链表和头节点作用 头插和头删 头插 头删 尾插与尾删 尾插 尾删 pos 删除和插入 插入 删除 打印和查找 整体代码 这个数据结构可以 ...

  7. 外强中干——双向带头循环链表

    前言:众所周知,链表有八种结构,由单向或双向,有头或无头,循环或不循环构成.在本篇,将介绍8种链表结构中最复杂的--双向带头循环链表.听着名字或许挺唬人的,但实际上双向带头循环链表实现起来比结构最简单 ...

  8. 【双向带头循环链表】

    双向带头循环链表 0.链表中每个节点的基本结构 1.初始化链表(创建哨兵位头节点) 2.申请新节点 3.尾插节点 4.尾删节点 5.头插节点 6.头删节点 7.寻找pos位置的节点 8.在pos位置之 ...

  9. 双向带头循环链表-实现思路+图解

    目录 一.何为双向循环链表? 1.何为'双向'? 2.何为'带头'? 3.何为'循环'? 二.如何实现双向带头循环链表? 1.基本结构-结点的创建 2.创建哨兵位结点 3.链表的增删查改 一.何为双向 ...

最新文章

  1. java中Integer装箱的注意
  2. cmake 2.8.12在redhat 4.4下安装
  3. Swift之深入解析基于闭包的类型擦除
  4. android通讯录管理(获取联系人,通话记录,短信消息),Android通讯录管理(获取联系人、通话记录、短信消息)(二)...
  5. 鸿蒙首饰用什么合成,天下HD鸿蒙炼炉是什么?鸿蒙炼炉讲解
  6. Find 3-friendly Integers
  7. (王道408考研操作系统)第四章文件管理-第一节4:文件物理结构(文件分配方式)
  8. 容器安全 - 通过SECCOMP过滤在容器中的风险操作
  9. 使用Iocomp工控图表工具绘制实时曲线
  10. java实现代码在线编译器-从零开发(一)简单本地编译+运行测试
  11. Ubuntu命令方式安装中文语言包
  12. 编译原理实验一:单词的词法分析程序设计
  13. Unity与Android权限设置
  14. 从网站细节入手提高易用性
  15. 《SQL进阶教程》学习
  16. 国网大数据中心是如何进行数据治理的?
  17. 分享32套精美的免费 PSD 网页界面设计素材
  18. scheduling agreement 计划协议详解
  19. 3 个月前被裁员了,心情跌落谷底,直到我看到了这本神书…
  20. skype8.34 新版本不能启动/打开

热门文章

  1. CefSharp 自定义缓存目录
  2. Android 加载gif图,Glide
  3. 1088: 整数序列求和-2
  4. 【1252. 奇数值单元格的数目】
  5. php生成二维码工具(带LOGO)
  6. 联通、德勤联合发布《5G赋能智慧城市白皮书》| 附下载
  7. 3389 远程桌面 Mstsc RDP协议详细解析 (下)
  8. python如何查看函数的参数_python 如何查看自带函数的默认参数?
  9. 《计算机网络》五层各层结构——简单认识。五层协议结构,OSI结构
  10. namenode无法启动