数据结构之——单循环链表和双向循环链表

一、单循环链表的奥秘

        单循环链表是一种特殊的链表结构,它在数据结构领域中具有重要的地位。其独特的循环特性使得它在某些特定的应用场景中表现出强大的优势。

(一)结构与初始化

单循环链表的结构由节点组成,每个节点包含数据域和指向下一个节点的指针域。与普通单链表不同的是,单循环链表的最后一个节点的指针指向头节点,从而形成一个循环。在初始化单循环链表时,通常先创建一个头节点,头节点的数据域可以存储一些特定信息,比如链表长度等。头节点的指针域指向自身,代表此时链表为空。例如在 C 语言中,可以这样初始化单循环链表:

typedef int ElemType;typedef struct Node 
{ElemType data;struct Node *next;
} Node;typedef struct Node *LinkList;// 初始化单循环链表
void InitList(LinkList &L) 
{// 分配内存空间用于创建头节点L = (LinkList)malloc(sizeof(Node));if (!L) {// 如果内存分配失败,退出程序并返回错误码 OVERFLOWexit(OVERFLOW);}// 将头节点的 next 指针指向自身,形成单循环链表L->next = L;// 头节点的数据域可以用于存储链表的长度等信息,这里初始化为 0L->data = 0;
}

(二)插入与删除操作

1.头插法:头插法是将新节点插入到单循环链表的头部。首先创建新节点,将新节点的指针指向头节点的下一个节点,然后将头节点的指针指向新节点,完成插入操作。例如:

// 在单循环链表头部插入节点
void headInsert(LinkList &L, int data) 
{// 分配新节点内存空间Node *newNode = (Node *)malloc(sizeof(Node));// 设置新节点的数据域为传入的数据newNode->data = data;// 让新节点的 next 指针指向当前链表头部的下一个节点newNode->next = L->next;// 更新链表头部的 next 指针,使其指向新节点L->next = newNode;
}

2.尾插法:尾插法是将新节点插入到单循环链表的尾部。首先找到链表的尾节点,然后将新节点插入到尾节点之后,使其成为新的尾节点。例如:

// 在单循环链表尾部插入节点
void tailInsert(LinkList &L, int data) 
{// 分配新节点内存空间Node *newNode = (Node *)malloc(sizeof(Node));newNode->data = data;newNode->next = L;// 创建一个临时指针用于遍历链表找到尾节点Node *p = L;// 遍历链表直到找到尾节点(即当前节点的下一个节点是头节点)while (p->next!= L) {p = p->next;}// 将尾节点的 next 指针指向新节点p->next = newNode;
}

3.删除操作:删除操作可以分为删除头节点和删除其他节点两种情况。删除头节点时,只需将头节点的指针指向头节点的下一个节点即可。删除其他节点时,需要找到要删除节点的前一个节点,然后将其指针指向要删除节点的下一个节点,完成删除操作。例如:

// 删除指定值的节点
void deleteNode(LinkList &L, int data) 
{// 创建两个指针 p 和 q,用于遍历链表Node *p = L;Node *q = L->next;// 遍历链表,直到找到要删除的节点或遍历到链表尾部(q == L)while (q!= L && q->data!= data) {p = q;q = q->next;}// 如果遍历到链表尾部仍未找到要删除的节点if (q == L) {printf("节点不存在!\n");} else {// 调整指针,将待删除节点从链表中移除p->next = q->next;// 释放待删除节点的内存空间free(q);}
}

(三)总结与应用

单循环链表的优势在于可以方便地进行循环遍历,无需担心链表的末尾。在一些需要循环处理数据的场景中,如约瑟夫问题、魔术师发牌问题等,单循环链表表现出了强大的应用价值。然而,单循环链表也存在一些问题,比如在插入和删除操作时,需要特别注意链表的循环特性,否则容易出现错误。解决这些问题的方法是在编写代码时,仔细考虑各种情况,确保代码的正确性。总之,单循环链表是一种非常有用的数据结构,掌握它的特点和操作方法,对于提高编程能力和解决实际问题具有重要意义。

二、双向循环链表的魅力

        双向循环链表作为一种复杂而强大的数据结构,具有独特的魅力和价值。

(一)结构与初始化

双向循环链表的节点结构包含数据域、指向前驱节点的指针域和指向后继节点的指针域。这使得在链表中可以方便地双向遍历,提高了操作的灵活性。在 C 语言中,初始化双向循环链表通常先创建一个头节点,头节点的数据域一般不存储实际数据,其前驱和后继指针都指向自身,形成一个循环。例如:

#include <stdio.h>
#include <stdlib.h>typedef int ElemType;// 定义双向链表节点结构体
typedef struct DNode 
{ElemType data;struct DNode *prev; // 指向前驱节点的指针struct DNode *next; // 指向后继节点的指针
} DNode;// 定义指向双向链表节点的指针类型别名
typedef struct DNode *DLinkList;// 初始化双向循环链表
void initDList(DLinkList &DL) 
{// 分配内存空间用于创建头节点DL = (DLinkList)malloc(sizeof(DNode));if (!DL) {// 如果内存分配失败,退出程序并返回错误码 OVERFLOWexit(OVERFLOW);}// 将头节点的 prev 和 next 指针都指向自身,形成双向循环链表DL->prev = DL;DL->next = DL;// 头节点的数据域可以用于存储链表的长度等信息,这里初始化为 0DL->data = 0;
}

(二)插入操作

1.头部插入:头部插入操作先创建新节点,然后调整新节点的前驱和后继指针,使其指向头节点和原来的头节点的下一个节点,最后调整头节点和原来头节点的下一个节点的指针,使其指向新节点。例如:

// 在双向循环链表头部插入节点
void headInsertDList(DLinkList &DL, int data) 
{// 分配新节点内存空间DNode *newNode = (DNode *)malloc(sizeof(DNode));newNode->data = data;// 设置新节点的前驱指针指向头节点newNode->prev = DL;// 设置新节点的后继指针指向原来头节点的下一个节点newNode->next = DL->next;// 原来头节点下一个节点的前驱指针更新为新节点DL->next->prev = newNode;// 头节点的后继指针更新为新节点DL->next = newNode;
}

2.尾部插入:尾部插入操作先找到链表的尾节点,然后创建新节点,调整新节点的前驱和后继指针,使其指向尾节点和头节点,最后调整尾节点和头节点的指针,使其指向新节点。例如:

// 在双向循环链表尾部插入节点
void tailInsertDList(DLinkList &DL, int data) 
{// 分配新节点内存空间DNode *newNode = (DNode *)malloc(sizeof(DNode));newNode->data = data;// 设置新节点的前驱指针指向原尾节点(即头节点的前驱)newNode->prev = DL->prev;// 设置新节点的后继指针指向头节点newNode->next = DL;// 更新原尾节点的后继指针为新节点DL->prev->next = newNode;// 更新头节点的前驱指针为新节点DL->prev = newNode;
}

3.指定位置插入:指定位置插入操作先找到要插入位置的前一个节点,然后创建新节点,调整新节点的前驱和后继指针,使其指向该节点和该节点的下一个节点,最后调整该节点和该节点的下一个节点的指针,使其指向新节点。例如:

// 在双向循环链表指定位置插入节点
void insertAtPositionDList(DLinkList &DL, int pos, int data) 
{// 创建一个指针 p,初始指向头节点DNode *p = DL;// 用于计数当前位置的变量int i = 0;// 遍历链表找到要插入的位置while (p->next!= DL && i < pos - 1) {p = p->next;i++;}// 如果遍历完没有找到指定位置,输出插入位置无效的提示信息并返回if (i!= pos - 1) {printf("插入位置无效!\n");return;}// 分配新节点的内存空间DNode *newNode = (DNode *)malloc(sizeof(DNode));newNode->data = data;// 设置新节点的前驱指针指向当前位置的节点 pnewNode->prev = p;// 设置新节点的后继指针指向当前位置节点 p 的下一个节点newNode->next = p->next;// 将当前位置节点 p 的下一个节点的前驱指针更新为新节点p->next->prev = newNode;// 将当前位置节点 p 的后继指针更新为新节点p->next = newNode;
}

(三)删除操作

1.删除头部节点:删除头部节点操作先保存头节点的下一个节点,然后调整头节点和头节点的下一个节点的下一个节点的指针,使其指向对方,最后释放头节点的下一个节点。例如:

// 删除双向循环链表的头部节点
void deleteHeadDList(DLinkList &DL) 
{// 如果链表为空(头节点的下一个节点还是头节点),输出提示信息并返回if (DL->next == DL) {printf("链表为空,无法删除头部节点!\n");return;}// 保存要删除的头节点DNode *temp = DL->next;// 更新头节点的 next 指针指向原来头节点的下一个节点DL->next = temp->next;// 更新新的头节点的前驱指针为原来的头节点的前驱(现在还是尾节点)temp->next->prev = DL;// 释放被删除的头节点的内存空间free(temp);
}

2.删除尾部节点:删除尾部节点操作先找到链表的尾节点的前一个节点,然后调整该节点和头节点的指针,使其指向对方,最后释放尾节点。例如:

// 删除双向循环链表的尾部节点
void deleteTailDList(DLinkList &DL) 
{// 如果链表为空(头节点的下一个节点还是头节点),输出提示信息并返回if (DL->next == DL) {printf("链表为空,无法删除尾部节点!\n");return;}// 找到尾节点的前驱节点DNode *p = DL->prev->prev;// 保存要删除的尾节点DNode *temp = DL->prev;// 更新尾节点前驱节点的 next 指针指向头节点p->next = DL;// 更新头节点的 prev 指针指向新的尾节点(原来尾节点的前驱)DL->prev = p;// 释放被删除的尾节点的内存空间free(temp);
}

3.删除指定位置节点:删除指定位置节点操作先找到要删除位置的前一个节点,然后保存要删除的节点,调整该节点和该节点的下一个节点的指针,使其指向对方,最后释放要删除的节点。例如:

// 删除双向循环链表指定位置的节点
void deleteAtPositionDList(DLinkList &DL, int pos) 
{// 创建一个指针 p,初始指向头节点DNode *p = DL;// 用于计数当前位置的变量int i = 0;// 遍历链表找到要删除的位置的前一个节点while (p->next!= DL && i < pos - 1) {p = p->next;i++;}// 如果遍历完没有找到指定位置或者链表为空,输出删除位置无效的提示信息并返回if (i!= pos - 1 || p->next == DL) {printf("删除位置无效!\n");return;}// 保存要删除的节点DNode *temp = p->next;// 更新当前节点的 next 指针指向要删除节点的下一个节点p->next = temp->next;// 更新要删除节点的下一个节点的 prev 指针指向当前节点temp->next->prev = p;// 释放被删除的节点的内存空间free(temp);
}

(四)遍历与查找

1.遍历:遍历双向循环链表可以从任意一个节点开始,向前或向后遍历。例如从头节点开始向后遍历:

// 遍历双向循环链表
void traverseDList(DLinkList DL) 
{// 如果链表为空(头节点的下一个节点还是头节点),输出提示信息并返回if (DL->next == DL) {printf("链表为空!\n");return;}// 创建一个指针 p,初始指向头节点的下一个节点DNode *p = DL->next;// 遍历链表并输出每个节点的数据while (p!= DL) {printf("%d ", p->data);p = p->next;}printf("\n");
}

2.查找:查找指定元素可以从任意一个节点开始,向前或向后遍历,比较节点的数据域和要查找的元素是否相等。例如从头节点开始向后查找:

// 在双向循环链表中查找指定元素
DNode *findInDList(DLinkList DL, int data) 
{// 如果链表为空(头节点的下一个节点还是头节点),输出提示信息并返回 NULLif (DL->next == DL) {printf("链表为空!\n");return NULL;}// 创建一个指针 p,初始指向头节点的下一个节点DNode *p = DL->next;// 遍历链表查找指定元素while (p!= DL && p->data!= data) {p = p->next;}// 如果遍历完没有找到指定元素,输出未找到的提示信息并返回 NULLif (p == DL) {printf("未找到指定元素!\n");return NULL;}// 返回找到的节点指针return p;
}

(五)总结与展望

双向循环链表具有双向遍历、高效插入和删除等特点,在很多应用场景中都有重要的价值。例如在操作系统的内存管理中,可以使用双向循环链表来管理空闲内存块;在数据库系统中,可以用双向循环链表来实现事务的回滚和重做。未来,随着计算机技术的不断发展,双向循环链表可能会在更多的领域得到应用,并且可能会与其他数据结构结合,产生更强大的数据管理和处理能力。同时,对于双向循环链表的优化和改进也将是一个持续的研究方向,比如提高插入和删除操作的效率、减少内存占用等。总之,双向循环链表作为一种重要的数据结构,将在计算机科学的发展中继续发挥重要作用。

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

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

相关文章

343.整数拆分

刷题刷题找工作&#xff01; 题目链接 DP入门之整数拆分&#xff01; 题目&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 自己的思路 。。…

引入Scrum激发研发体系活力

引言 在当今快速变化的技术环境中&#xff0c;IT企业面临着持续的市场压力和竞争&#xff0c;传统的瀑布式开发模式已经难以满足现代企业的需要。瀑布模型过于僵化&#xff0c;缺乏灵活性&#xff0c;导致项目经常延期&#xff0c;成本增加&#xff0c;最终可能无法达到预期效果…

bert系列模型区别(bert-base-cased/bert-base-uncased/bert-base-chinese)

文章目录 BERT模型介绍bert-base-casedbert-base-uncasedbert-base-chineseBERT-BILSTM-CRF模型介绍模型下载地址BERT模型介绍 BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的语言模型,由Google开发并于2018年发布。BERT的目标是通过将大量…

【Qualcomm】高通SNPE框架的使用 | 原始模型转换为量化的DLC文件 | 在Android的DSP端运行模型

目录 ① 激活snpe环境 ② 设置环境变量 ③ 模型转换 ④ run 首先&#xff0c;默认SNPE工具已经下载并且Setup相关工作均已完成。同时&#xff0c;拥有原始模型文件&#xff0c;本文使用的模型文件为SNPE 框架示例的inception_v3_2016_08_28_frozen.pb文件。image_file_list…

数据集-目标检测系列-口罩检测数据集 mask>> DataBall

数据集-目标检测系列-口罩检测数据集 mask>> DataBall 数据集-目标检测系列-口罩检测数据集 mask 数据量&#xff1a;1W DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;享有百种数据集&#xff0c;持续增加中。 数据项目地址&#xff1a; gitcode: https…

【Python报错已解决】TypeError: list indices must be integers or slices, not str

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

Linux中通过cgroups限制进程的资源

文章目录 1. 安装和配置cgroups 2. 创建cgroup 3. 设置资源限制 1&#xff09;CPU限制 2&#xff09;内存限制 4. 将Java进程加入到cgroup 5. 监控cgroup 在Linux系统中&#xff0c;cgroups&#xff08;Control Groups&#xff09;是一种可以对一组进程进行细粒度资源控…

打造同城O2O平台:外卖跑腿APP的架构与功能设计详解

今天&#xff0c;小编将于大家共同讨论外卖跑腿APP的架构设计及其核心功能&#xff0c;旨在为开发者提供一份详尽的参考。 一、外卖跑腿APP的架构设计 1.整体架构概述 通常包括前端、后端和数据库。 2.前端设计 用户端提供直观的界面&#xff0c;方便用户下单、查询订单状态…

初学51单片机之I2C总线与E2PROM

首先先推荐B站的I2C相关的视频I2C入门第一节-I2C的基本工作原理_哔哩哔哩_bilibili 看完视频估计就大概知道怎么操作I2C了&#xff0c;他的LCD1602讲的也很不错&#xff0c;把数据建立tsp和数据保持thd&#xff0c;比喻成拍照时候的摆pose和按快门两个过程&#xff0c;感觉还是…

C语言实现归并排序(Merge Sort)

目录 一、递归实现归并排序 1. 归并排序的基本步骤 2.动图演示 3.基本思路 4.代码 二、非递归实现 1.部分代码 2.代码分析 修正后代码&#xff1a; 归并过程打印 性能分析 复杂度分析 归并排序是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Con…

华为仓颉语言入门(6):if条件表达式

解锁Python编程的无限可能&#xff1a;《奇妙的Python》带你漫游代码世界 仓颉语言中的 if 表达式用于根据条件的值来决定是否执行相关代码逻辑。if 表达式有三种形式&#xff1a;单分支的 if 表达式、双分支的 if 表达式和嵌套的 if 表达式。 单分支的 if 表达式 单分支的 …

javase复习day35反射

反射 获取class对象的方法 public class Demo1 {public static void main(String[] args) throws ClassNotFoundException {//获取反射的三种方式//第一种 Class.forName(全类名)//用法&#xff1a;最为常用Class<?> clazz1 Class.forName("Reflection.Student&q…

程序员如何以最快的方式提升自己?分享4个有效方法!

作家周国平说&#xff1a;人与人之间最重要的区别&#xff0c;不在物质的贫富和社会方面的境遇&#xff0c;是内在的素质和层次&#xff0c;把人分出了伟大与渺小、优秀与平庸。有的人醉心于三五成群的消遣&#xff0c;有的人专注于一步一脚印的努力&#xff0c;人和人之间的差…

docker修改默认存储路径和网段

在安装完成 Docker 后&#xff0c;可以修改 Docker 的数据目录和默认网段(172.17.0.0/16)&#xff0c;以防止与其他系统或应用的网络配置冲突。以下是详细步骤&#xff1a; 停止 Docker 服务&#xff1a; sudo systemctl stop docker 修改 Docker 配置文件&#xff1a; 编辑…

Shiro-550—漏洞分析(CVE-2016-4437)

文章目录 漏洞原理源码分析加密过程解密过程 漏洞复现 漏洞原理 Shiro-550(CVE-2016-4437)反序列化漏洞 在调试cookie加密过程的时候发现开发者将AES用来加密的密钥硬编码了&#xff0c;并且所以导致我们拿到密钥后可以精心构造恶意payload替换cookie&#xff0c;然后让后台最…

利用Puppeteer-Har记录与分析网页抓取中的性能数据

引言 在现代网页抓取中&#xff0c;性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据&#xff0c;并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库&#xff0c;提供…

VUE.js笔记

1.介绍vue Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 Vue 应用程序的基本…

初识C语言(三)

感兴趣的朋友们可以留个关注&#xff0c;我们共同交流&#xff0c;相互促进学习。 文章目录 前言 八、函数 九、数组 &#xff08;1&#xff09;数组的定义 &#xff08;2&#xff09;数组的下标和使用 十、操作符 &#xff08;1&#xff09;算数操作符 &#xff08;2&#xff…

统计本周的订单数,统计最近7天的订单数

3个函数 DATE_SUB和SUBDATE在MySQL中的作用是一样的&#xff0c;它们都是用于执行日期的减法运算。具体来说&#xff0c;这两个函数都允许你从给定的日期或日期时间值中减去一个指定的时间间隔&#xff0c;然后返回一个新的日期或日期时间值。 DATE函数 DATE(time) 用于获取…

Composition API 与 React Hook 的区别

从 React Hook 的实现角度看&#xff0c;React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState&#xff0c;所以出现了以下限制&#xff1a; 不能在循环、条件、嵌套函数中调用 Hook必须确保总是在你的 React 函数的顶层调用 HookuseEff…