目录

前言

一、双向循环链表

循环结构

1.双向循环链表头文件及函数声明

2.初始化

1.结点构造

2.初始化函数

3.结点申请

4.数据插入

1.按位置插入

2.尾插

3.头插

5.查找

6.数据删除

1.按位置删除

2.按值删除

3.尾删

4.头删

7.清空与销毁

1.清空

2.销毁

8.双向循环链表源文件及整体函数实现

总结


前言

这次我们将学习双向循环链表,首先了解双向链表和循环链表的定义和讲解。

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,是单链表的进阶,比单链表的结点结构多出一个指向前驱的指针域,如下图所示:

循环链表是一种链式储存结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。方法是将尾结点中的指针域进行使用,指向首结点,如下图所示:

双向循环链表即是将上述二者联合起来使用,以达到完成各类工作的效用,在以后大多数链表结构的使用中,我们都会选择双向循环链表。


一、双向循环链表

循环结构

双向循环链表的循环方式是其尾结点的后继指针指向头结点(表头),而头结点的前置指针指向尾结点,达到双向循环的目的,这样不仅使得对链表尾部的操作更为简单,也减少了对NULL指针的引用。

1.双向循环链表头文件及函数声明

新建头文件"dclist.h"(double circle list)对链表函数声明进行保存,方便我们后期查看及使用

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<ctype.h>
#include<stdlib.h>typedef int Element;typedef struct DNode
{Element data;DNode* next;DNode* prev;
}DNode, * PDNode;//初始化链表
void InitList(PDNode plist);//购买结点
PDNode BuyNode();//头插
bool Push_Front(PDNode plist, Element val);//尾插
bool Push_Back(PDNode plist, Element val);//按位置插
bool Insert_Pos(PDNode plist, int pos, Element val);//头删
bool Pop_Front(PDNode plist);//尾删
bool Pop_Back(PDNode plist);//按位置删
bool Erease_Pos(PDNode plist, int pos);//按值删
bool Erease_Val(PDNode plist, Element val);//查找  返回结点地址
PDNode Find(PDNode plist, Element val);//判空
bool IsEmpty(PDNode plist);//获取元素个数
int GetSize(PDNode plist);//打印链表
void Print(PDNode plist);//清空
void Clear(PDNode plist);//销毁
void Destroy(PDNode plist);

2.初始化

在对使用链表进行数据的储存之前,我们需要进行链表的初始化

1.结点构造

如图所示,带双向循环链表的结点由三部分构成:

1.数据域

2.指针域1:指向后继结点的指针

3.指针域2:指向前置结点的指针

typedef int Element;//如此对数据类型进行改名操作,若要进行修改数据类型操作会更加方便typedef struct DNode
{Element data;DNode* next;//后继指针DNode* prev;//前置指针
}DNode, * PDNode;

2.初始化函数

与单链表不同的是,在双向循环链表不存在数据时,头结点的指针域不置为NULL,其后继指针与前置指针都指向头结点本身,以形成最基础的循环形态:

//初始化链表
void InitList(PDNode plist)
{assert(plist != NULL);plist->next = plist->prev = plist;
}

3.结点申请

因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数

//购买结点
PDNode BuyNode()
{PDNode node = (PDNode)malloc(sizeof(DNode));if (node == NULL){exit(1);}memset(node, 0, sizeof(*node));//将申请的结点内存重置,防止数据残留return node;
}

4.数据插入

双向循环链表存在多个指针指向的断开和重新指向,我们在此先进行分析和讲解,如下图所示:

按照正常逻辑,我们再重新指定指针指向时,首先需要打破原有的指针指向,即上图中红叉部分,但实际编写程序中,我们会直接修改指针的指向,从而在代码中只会体现重新指向的过程。

在图中,已经标注出4个指针指向的修改,首先,我们应该对新申请的结点中指针(即图中34箭头的指向)进行指定,因为如果我们先对旧有指针进行修改,那么我们可能会丢失结点的地址信息,从而失去我们已经保存的数据。

其中指针具体的指向修改,我们不用太在意顺序,只需要记住先考虑新节点的指向,后修改旧结点的指向即可,下面的代码中,我们将采用4321的顺序:

1.按位置插入

我们先进行通用插入的实现:

//按位置插
bool Insert_Pos(PDNode plist, int pos, Element val)
{assert(plist != NULL);//判断插入位置是否合法if (pos < 0 || pos > GetSize(plist)){return false;}PDNode node = plist;//将结点指针定位至需要插入数据位置之前的结点for (int i = 0; i < pos; i++){node = node->next;}PDNode newnode = BuyNode();//申请新节点 newnode->data = val;//以下为插入步骤newnode->next = node->next;//4newnode->prev = node;//3node->next->prev = newnode;//2node->next = newnode;//1return true;
}

2.尾插

因为表头的前置指针指向尾结点,所以我们不用像单链表一样进行遍历后才能尾插。

//尾插
bool Push_Back(PDNode plist, Element val)
{assert(plist != NULL);PDNode newnode = BuyNode();newnode->data = val;//插入newnode->next = plist;//4newnode->prev = plist->prev;//3plist->prev = newnode;//2plist->prev->next = newnode;//1return true;
}

3.头插

直接调用按位置插入,插入位置为0

//头插
bool Push_Front(PDNode plist, Element val)
{assert(plist != NULL);return Insert_Pos(plist, 0, val);
}

5.查找

查找与普通链表的查找基本相同,但是在while循环中的结束条件不同,以结点指针是否指向表头来判断

//查找  返回结点地址
PDNode Find(PDNode plist, Element val)
{assert(plist != NULL);PDNode node = plist->next;//遍历链表查找与val 相同的值while (node != plist && node->data != val){node = node->next;}if (plist == node){return NULL;}return node;
}

6.数据删除

双向循环链表的删除大致与单链表相同,但在两相隔结点的重新链接时需要连结两个指针,即被删除结点的前置结点的后继指针指向被删除结点的后继结点,而后继结点的前置指针指向前置结点

1.按位置删除

//按位置删
bool Erease_Pos(PDNode plist, int pos)
{assert(plist != NULL);if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist)){return false;}PDNode delnode = plist;for (int i = 0; i <= pos; i++){delnode = delnode->next;}//重新连结delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}

2.按值删除

这里调用了查找函数,找到val值所在的结点

//按值删
bool Erease_Val(PDNode plist, Element val)
{assert(plist != NULL);PDNode delnode = Find(plist, val);if (delnode == NULL){return false;}delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}

3.尾删

同样的,因为表头的前置指针指向尾结点,则可以减少遍历链表所消耗的资源。

//尾删
bool Pop_Back(PDNode plist)
{assert(plist != NULL);if (IsEmpty(plist)){return false;}PDNode delnode = plist->prev;delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}

4.头删

直接调用按位置删除,删除位置为0

//头删
bool Pop_Front(PDNode plist)
{assert(plist != NULL);return Erease_Pos(plist, 0);
}

7.清空与销毁

1.清空

这里我们使用比较简单的方法,一直头删即可,当然这里的尾删也可以使用,消耗资源相同。

//清空
void Clear(PDNode plist)
{assert(plist != NULL);while (!IsEmpty(plist)){Pop_Front(plist);}
}

2.销毁

调用清空函数,并使链表达到未初始化的状态。

//销毁
void Destroy(PDNode plist)
{Clear(plist);plist->next = plist->prev = NULL;
}

8.双向循环链表源文件及整体函数实现

#include "dclist.h"//初始化链表
void InitList(PDNode plist)
{assert(plist != NULL);plist->next = plist->prev = plist;
}//购买结点
PDNode BuyNode()
{PDNode node = (PDNode)malloc(sizeof(DNode));if (node == NULL){exit(1);}memset(node, 0, sizeof(*node));return node;
}//头插
bool Push_Front(PDNode plist, Element val)
{assert(plist != NULL);return Insert_Pos(plist, 0, val);
}//尾插
bool Push_Back(PDNode plist, Element val)
{assert(plist != NULL);PDNode newnode = BuyNode();newnode->data = val;newnode->prev = plist->prev;newnode->next = plist;plist->prev->next = newnode;plist->prev = newnode;return true;
}//按位置插
bool Insert_Pos(PDNode plist, int pos, Element val)
{assert(plist != NULL);if (pos < 0 || pos > GetSize(plist)){return false;}PDNode node = plist;for (int i = 0; i < pos; i++){node = node->next;}PDNode newnode = BuyNode();newnode->data = val;newnode->next = node->next;newnode->prev = node;node->next->prev = newnode;node->next = newnode;return true;
}//头删
bool Pop_Front(PDNode plist)
{assert(plist != NULL);return Erease_Pos(plist, 0);
}//尾删
bool Pop_Back(PDNode plist)
{assert(plist != NULL);if (IsEmpty(plist)){return false;}PDNode delnode = plist->prev;delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}//按位置删
bool Erease_Pos(PDNode plist, int pos)
{assert(plist != NULL);if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist)){return false;}PDNode delnode = plist;for (int i = 0; i <= pos; i++){delnode = delnode->next;}delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}//按值删
bool Erease_Val(PDNode plist, Element val)
{assert(plist != NULL);PDNode delnode = Find(plist, val);if (delnode == NULL){return false;}delnode->prev->next = delnode->next;delnode->next->prev = delnode->prev;free(delnode);delnode = NULL;return true;
}//查找  返回结点地址
PDNode Find(PDNode plist, Element val)
{assert(plist != NULL);PDNode node = plist->next;while (node != plist && node->data != val){node = node->next;}if (plist == node){return NULL;}return node;
}//判空
bool IsEmpty(PDNode plist)
{assert(plist != NULL);return (plist->next == plist->prev && plist->next == plist);
}//获取元素个数
int GetSize(PDNode plist)
{assert(plist != NULL);PDNode node = plist->next;int count = 0;while (node != plist){node = node->next;count++;}return count;
}//打印链表
void Print(PDNode plist)
{assert(plist != NULL);PDNode node = plist->next;while (node != plist){printf("%d ", node->data);node = node->next;}printf("\n");
}//清空
void Clear(PDNode plist)
{assert(plist != NULL);while (!IsEmpty(plist)){Pop_Front(plist);}
}//销毁
void Destroy(PDNode plist)
{Clear(plist);plist->next = plist->prev = NULL;
}

总结

以上为双向循环链表的实现和方法讲解,双向循环链表为比较常用的链表形式,学习并理解双向循环链表会方便以后的学习。

【C语言数据结构】双向循环链表相关推荐

  1. c语言 数据结构 双向循环链表逆序

    双链循环链表排序: 原链表: 1 2 3 4 5 6 7 8 9 10 逆序后:10 9 8 7 6 5 4 3 2 1 思路: 把最后一个节点删除, 插到head下面去 数据 1 不用管, 把后面的 ...

  2. [C语言数据结构]双向循环链表

    目录

  3. 数据结构 -- 双向循环链表

    这篇文章写的是双向循环链表,这也是个非常经典的数据结构,我们在看 Linux 内核代码时就经常会遇到它.比如,在Linux的内核中实现进程描述符,使用的就是双向循环链表,因为它使用起来很方便,每个节点 ...

  4. 双向循环链表 (C语言实现双向循环链表) ------- 算法笔记003

    概念 我们常说的单链表是有向的链表,因为链表1中有next指针它对应指向链表2的地址 而链表2无法指向链表1,所以我们说它是单向的链表. 而双向链表则是链表1可以访问到链表2,链表2也可以访问到链表1 ...

  5. 数据结构--双向循环链表的实现

    前言 之前学习了单链表的实现,单链表在实现的过程中,尾删,尾插是比较麻烦的,时间复杂度为O(N),而双向循环链表就不会有这样的顾虑,双向循环链表的结构是很有优势的.具体优势在实现的时候会显现出来. 双 ...

  6. Python写数据结构:双向循环链表

    注:有头结点 #!/usr/bin/python3.5 # _*_coding:utf-8class Node:def __init__(self, value):self.data = values ...

  7. C语言之链表探究之单向链表(List)、附双向循环链表参考博文地址

    参考博文:C语言数据结构-创建链表的四种方法 链表结构图一 链表结构图二 链表结构图三 链表结构图四 一.静态链表 例1: 附例1代码: #include <stdio.h>typedef ...

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

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

  9. 【数据结构】带头双向循环链表的增删查改(C语言实现)

    文章目录 前言 一.什么是带头双向循环链表 二.带头双向循环链表的实现 1.结构的定义 2.链表的初始化 3.开辟新节点 4.在头部插入数据 5.在尾部插入数据 6.查找数据 7.在pos位置之前插入 ...

  10. 《恋上数据结构第1季》单向循环链表、双向循环链表以及约瑟夫环问题

    循环链表(CircleList) 链表的接口设计 单向循环链表 单向循环链表完整源码 双向循环链表 双向循环链表完整源码 双向循环链表解决约瑟夫环问题 如何发挥循环链表的最大威力? 静态链表 数据结构 ...

最新文章

  1. 施一公:优秀博士如何养成(全文) 清华大学演讲
  2. apex图表使用饼图居中_ppt图表技巧:如何制作美观简洁的百分比饼图
  3. TFS 2015 敏捷开发实践 – 看板的使用
  4. 动态连接库的两种方式
  5. std::string中的反向迭代器rbegin()和rend()
  6. 混乱开发,既伤身体又伤感情
  7. 清华博士后黄石生:深度神经网络实时三维重建和在线语义分割技术报告
  8. 程序员和注册会计师的地位_“注册会计师和律师哪个地位高?”这3张图给出了答案!...
  9. Python实现获取IP代码
  10. java xml解析框架_JAVA解析xml的五种方式对比
  11. html绘制中国地图,ECharts绘制中国地图、广西地图
  12. PHP中mysql查询全部过程_PHP到MySQL数据查询过程概述_MySQL
  13. 微信提示已连接到服务器失败,微信提示无法连接到服务器如何解决
  14. [TS初学]无法重新声明块范围变量
  15. HTAP数据库及应用场景简析
  16. 红米k40刷鸿蒙系统,红米K40开孔仅2.8mm,全球最小或命名为无感孔,到底有多极致...
  17. C++多线程编程资料清单
  18. 基于JavaWeb的网上购物系统开发(含代码)
  19. mPaas小程序(支付宝、钉钉...)自定义组件,组件传参
  20. stata批量读入excel文件,并合并为一个dta文件

热门文章

  1. ios 苹果手机适配代码
  2. 单干必备:论嵌入式模块化编程、驱动分离的重要性
  3. 运维 之 常用运维工具
  4. 数据包络分析法(DEA)_1
  5. magisk卸载内置软件_手机发烧友必备之 Magisk
  6. 艾宾浩斯30天计划表_干货丨艾宾浩斯遗忘曲线
  7. matlab 遗传算法 ga函数,matlab遗传算法ga函数
  8. 关于内存条的知识要点⑴
  9. 【时间同步】IEEE-1588总结
  10. 清明上河图对计算机技术的启发,吴良镛院士:《清明上河图》启示的城市方向...