本次为大家分享的是双向循环链表的增删查改等系列操作。

目录

一、图解双向循环链表结构

二、分步实现

(1)创建并初始化

(2)链表元素打印

(3)头插和尾插

(4)判断链表为空

(5)头删和尾删

(6)查找特定元素

(7)删除特定元素

(8)特定元素前插入

(9)链表销毁

三、优化及整体代码


一、图解双向循环链表结构

对于单向链表来说,每一个结点由数据块和一个指针域构成,只需要指针域记录下一个结点的位置即可。而双向链表则需要两个指针,对应“双向”,其结点具体结构和结点之间的连接方式如下。

二、分步实现

下面直接逐个上代码,给大家分块演示如何实现一个双向带头循环的链表。

(1)创建并初始化

首先我们需要声明结构体的结构,对于链表的初始化,我们只需要动态申请一个结点作为头结点,让这个哨兵结点头尾相连即可。

​​​​​​​

// 双链表 // Double Link List  // DLLtypedef int DLLData
typedef struct DLLNode
{DLLData data;struct ListNode* next;struct ListNode* prev;
}ListNode;// 链表哨兵结点的创建
ListNode* ListCreate()
{ListNode* guard = (ListNode*)malloc(sizeof(ListNode));guard->next = guard->prev = guard;return guard;
}

(2)链表元素打印

链表的打印相对简单,我们只需要打印出其中的数据即可,为了表示是双向带头循环链表,不同结点之间的数据我们用“<=>”连接表示双向,用phead放在两端表示带头循环。

// 双向链表打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* tmp = phead->next;printf("phead<=>");while (tmp != phead){printf("%d<=>", tmp->data);tmp = tmp->next;}printf("phead\n");
}

(3)头插和尾插

相比与之前学的单向链表,双向循环链表的头插和尾插则简单了许多,对照下面的图。我们传入函数的是哨兵结点phead, phead->next便是头结点,即在phead之后插入结点就是头插;同理,由于链表是循环的,因此在phead之前插入就是尾插。

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));new_node->data = x;new_node->next = phead->next;phead->next->prev = new_node;phead->next = new_node;new_node->prev = phead;
}// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));new_node->data = x;phead->prev->next = new_node;new_node->prev = phead->prev;phead->prev = new_node;new_node->next = phead;
}

(4)判断链表为空

完成了头插尾插,接下来就是头删和尾删了。但是在删除之前我们应该先检查一下我们的链表之中是否还存在结点,当没有结点的时候我们就无法继续删除了。所以我们需要检查一下链表是否为空,为空就是除哨兵节点外没有有效节点了,只有这哨兵结点循环,因此具体实现如下。

// 检查链表为空
bool CheckVoid(ListNode* rhs)
{assert(rhs);return rhs->next == rhs;
}

(5)头删和尾删

类比之前的头插和尾插,我们很轻松就可以找到头结点和尾结点,那么头删和尾删也就解决了。

// 双向链表头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(!CheckVoid(phead));ListNode* new_front = phead->next->next;free(phead->next);phead->next = new_front;new_front->prev = phead;
}// 双向链表尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(!CheckVoid(phead));ListNode* new_tail = phead->prev->prev;free(phead->prev);phead->prev = new_tail;new_tail->next = phead;
}

(6)查找特定元素

和单链表一样我们只需要遍历整个链表即可,但是需要注意的是我们的链表有哨兵结点,而且是循环链表,因此我们需要从phead->next开始遍历,同时当我们遍历遇到phead时,说明已经找完了整个链表都没有对应元素,要在此设置退出值。

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* tmp = phead->next;while (tmp != phead){if (tmp->data == x)return tmp;tmp = tmp->next;}return NULL;
}

(7)删除特定元素

我们删除元素是基于查找函数完成的,首先通过查找函数找到对应结点,向删除函数传入该结点,由于我们是双向链表,因此只需要将找到的结点pos的前后结点连接并释放pos结点即可。

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);
}

(8)特定元素前插入

在此选择的是在特定元素pos之前插入,想改成之后插入也十分简单,同样是和查找函数共同使用,找到之后传入pos结点和数据值即可。

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{ListNode* front = pos->prev;ListNode* new_node = BuyNewnode(x);front->next = new_node;new_node->next = pos;pos->prev = new_node;new_node->prev = front;
}

(9)链表销毁

链表销毁只需要遍历链表逐个释放即可,可以先释放其他节点然后跳出循环释放哨兵结点,也可以将phead->next设置为NULL,成为一个伪单链表,然后循环释放所有结点。此处使用后者实现。

// 双向链表销毁
void ListDestroy(ListNode* phead)
{assert(phead);ListNode* tmp = phead->next, *node = phead->next;phead->next = NULL;while (tmp){node = tmp->next;free(tmp);tmp = node;}
}

三、优化及整体代码

当我们完成了插入函数的在之后,对于头插和尾插,直接调用函数即可,头插是在front前插入,尾插是在phead前插入。(函数声明和结构体声明等放在头文件中即可)

#include"Double ListNode.h"// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* guard = (ListNode*)malloc(sizeof(ListNode));guard->next = guard->prev = guard;return guard;
}// 创建一个新的结点
ListNode* BuyNewnode(LTDataType x)
{ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));new_node->data = x;return new_node;
}// 双向链表销毁
void ListDestroy(ListNode* phead)
{assert(phead);ListNode* tmp = phead->next, *node = phead->next;phead->next = NULL;while (tmp){node = tmp->next;free(tmp);tmp = node;}
}// 双向链表打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* tmp = phead->next;printf("phead<=>");while (tmp != phead){printf("%d<=>", tmp->data);tmp = tmp->next;}printf("phead\n");
}// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListInsert(phead, x);
}// 双向链表尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(!CheckVoid(phead));ListErase(phead->prev);
}// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListInsert(phead->next, x);
}// 双向链表头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(!CheckVoid(phead));ListErase(phead->next);
}// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* tmp = phead->next;while (tmp != phead){if (tmp->data == x)return tmp;tmp = tmp->next;}return NULL;
}// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{ListNode* front = pos->prev;ListNode* new_node = BuyNewnode(x);front->next = new_node;new_node->next = pos;pos->prev = new_node;new_node->prev = front;
}// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);
}// 检查链表为空
bool CheckVoid(ListNode* rhs)
{assert(rhs);return rhs->next == rhs;
}

双向循环链表的讲解及实现(图解+代码/C语言)相关推荐

  1. 试编写一个将双向循环链表逆置的算法_图解:链表的快慢指针,解决 80% 的链表面试题!...

    一.前言 链表是基本的数据结构之一,它与数组不同,数组在内存中存储,需要一块连续的内容空间来存储,对内存的要求比较高.例如我们需要 100MB 大小的数组,内存中就必须有一段连续的 100MB 的内存 ...

  2. 超级详细树讲解三 —— B树、B+树图解+代码

    首先很高兴你看到了这篇文章,这篇文章可能会花费你很长很长的时间去看,但是这篇文章包括的内容绝对足够你对树的一个系统性的学习.为什么要写这篇文字呢?因为自己在学习树的时候,有些博客只有图解,有些博客只有 ...

  3. 【数据结构】双向链表(带头双向循环链表)——超详细代码

    文章目录 1. 双链表 1.1 前言 1.2 带头双向循环链表 2. 带头双向循环链表的实现 2.1 双向链表的定义声明 2.2 双向链表的初始化 2.3 释放双向链表 2.4 打印双向链表 2.5 ...

  4. 双向循环链表:鸿蒙轻内核中数据的“驿站”

    本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二 数据结构-双向循环链表>,原文作者:zhushy . 在学习OpenHarmony鸿蒙轻内核源代码的时候,常常会遇到一些数据结构的使用. ...

  5. 带头双向循环链表的模拟实现

    目录 1.简介 2.源码分享 3.链表模拟实现 3.1 动态申请一个节点 3.2初始化链表,创建头节点 3.3实现尾插 3.4 实现头插 3.5 实现头删 3.6 尾删 3.7打印链表 3.8实现查找 ...

  6. 比特数据结构与算法(第二章收尾)带头双向循环链表的实现

    1.链表的分类 链表的分类 ① 单向或者双向 ② 带头或者不带头 ③ 循环或者非循环 常用的链表: 根据上面的分类我们可以细分出8种不同类型的链表,这么多链表我们一个个讲解这并没有意义.我们实际中最常 ...

  7. 玩转数据结构之双向循环链表

    文章目录 一.前言 二.双向链表的实现 ①.定义节点 ②.创建新节点(BuyLTNode) ③.初始化链表(ListInit) ④.双向链表销毁(ListDestory) ⑤.双向链表打印(ListP ...

  8. 初阶数据结构之带头+双向+循环链表增删查实现(三)

    文章目录 @[TOC](文章目录) 前言 一.带头双向循环链表的初始化 1.1带头双向循环链表的结构体定义 1.2初始化代码的实现 二.带头+双向+循环链表的增功能实现 2.1头插代码的实现 2.2尾 ...

  9. 【数据结构】-关于带头双向循环链表的增删查改

    作者:低调 作者宣言:写好每一篇博客 文章目录 前言 一.带头双向循环链表的实现 1.1创建返回链表的头结点 1.2开辟一个新的结点 1.3双向链表的销毁 1.4双向链表的打印 1.5双向链表尾插 1 ...

最新文章

  1. 浏览器插件 火狐插件
  2. 可用子网数要不要减2_CCNA最实用的复习知识点(2)
  3. 正则表达式是匹配模式,要么匹配字符,要么匹配位置
  4. 熊孩子倾家荡产玩游戏、打赏主播有救了!最高法:无效,可退还
  5. 【递归算法01】递归的调用机制
  6. selenium+webdriver+java(基本小例子及初始化三种浏览器)---------------
  7. CentOS下apache绑定域名
  8. PHIL2650 是个好课程
  9. 红米note10 pro刷机
  10. java程序员电脑内存配置_学习JAVA对电脑配置有要求吗
  11. bootloader系列二——arm920t--bootloader架构设计
  12. 阿里云Oss云存储的使用步骤
  13. 【短视频运营】短视频制作流程 ( 视频存稿 | 写脚本 | 拍摄收音 | 提词器 | 后期剪辑 | 前测工具 | 检查违禁词 )
  14. 计算机毕业设计ssm文档资料管理系统
  15. 服务器装win10系统很卡,笔记本电脑安装win10系统后变很卡很慢的解决方法
  16. canvas中的橡皮檫
  17. Python「剪藏」网页为 PDF
  18. ams1117-3.3v三端稳压芯片低压差线性稳压器
  19. HTML网页设计基础——用户注册界面
  20. 华为HarmonyOS系统搭载了POKERTIME129263和AOMAHA的汉印智能打印新升级

热门文章

  1. 弘辽科技:淘宝违规店铺会降权吗?扣12分降权多久?
  2. Java中@Deprecated作用、使用以及引用
  3. 关于python csv文件操作,用wps打开乱码的问题
  4. 美团取消支付宝支付,你怎么看?
  5. 使用SketchUp制作椭圆形球体的方法(图文教程)
  6. vue 手动选择切换设置语言(详细三步)
  7. Ubuntu 19.04用wine完美运行微信
  8. i9 12900K配什么主板 显卡
  9. CPU处理器高清切片,看看基板和散热!
  10. OpenCV 读取视频,设置起始帧、结束帧及如何获取帧率