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

C/C++数据结构之双向链表

概述

        双向链表由一系列节点组成,每个节点不仅包含数据域,还包含指向前一个节点和后一个节点的引用。与单向链表相比,双向链表允许我们从任意一个节点出发,既能够向前遍历也能向后遍历,这使得某些操作可以更加高效。

        想象一下一列火车,每节车厢就像双向链表中的一个节点。每节车厢都有前后两个连接点,通过这些连接点,车厢相互连接形成整列火车。我们可以从任意一节车厢出发,向前或向后移动到相邻的车厢,这与双向链表支持双向遍历的特点非常相似。

声明与初始化

        在C/C++中实现双向链表时,我们需要定义一个节点结构体。该结构体至少应包含三个部分:前驱指针、数据域和后继指针。前驱指针用于指向当前节点的直接前驱,而后继指针则指向当前节点的直接后继。

        在下面的示例代码中,Node结构体包含了三个成员:nData用于存放整型数据;pPrev是一个指向Node类型的指针,用于链接上一个节点;pNext也是一个指向Node类型的指针,用于链接下一个节点。

struct Node
{int nData;              // 数据域struct Node* pPrev;     // 前驱指针,指向上一个节点struct Node* pNext;     // 后继指针,指向下一个节点
};

        双向链表的初始化通常涉及到创建一个新的节点,并将其设置为整个链表的头节点。我们可以编写一个函数来创建并初始化一个新的节点,编写另一个函数以在链表头部插入节点,具体实现可参考下面的示例代码。

Node* CreateNode(int nData)
{Node *pNode = new Node();memset(pNode, 0, sizeof(Node));pNode->nData = nData;return pNode;
}void InsertAtHead(Node** pHead, Node* pNode)
{if (*pHead == NULL){// 若链表为空,直接赋值*pHead = pNode;}else{// 新节点的next指向原头节点pNode->pNext = *pHead;// 原头节点的prev指向新节点(*pHead)->pPrev = pNode;// 更新头指针*pHead = pNode;}
}int main()
{Node* pHead = NULL;// 创建一些新节点,并插入到双向链表中InsertAtHead(&pHead, CreateNode(66));InsertAtHead(&pHead, CreateNode(99));return 0;
}

基本操作

        在双向链表中,最基本的操作包括:遍历节点、插入节点和删除节点。

遍历节点

        遍历双向链表有两种方式:从前向后遍历和从后向前遍历。具体如何实现,可参考下面的示例代码。

void TraverseForward(Node* pNode)
{Node* pTemp = pNode;while (pTemp != NULL){printf("%d ", pTemp->nData);pTemp = pTemp->pNext;}printf("\n");
}void TraverseBackward(Node* pNode)
{Node* pTemp = pNode;while (pTemp != NULL){printf("%d ", pTemp->nData);pTemp = pTemp->pPrev;}printf("\n");
}

插入节点

        插入操作可以分为以下三种情况:在链表头部插入、在链表尾部插入、在指定位置之前插入。

        在链表头部插入节点,前面已经介绍过了,这里不再赘述。在链表尾部插入节点相对简单,只需找到链表的最后一个节点,并将它的pNext指针指向新节点,同时设置新节点的pPrev指针指向最后一个节点。

void InsertAtTail(Node** pHead, Node* pNode)
{if (*pHead == NULL){// 若链表为空*pHead = pNode;}else{Node* pTemp = *pHead;// 找到最后一个节点while (pTemp->pNext != NULL){pTemp = pTemp->pNext;}pTemp->pNext = pNode;pNode->pPrev = pTemp;}
}

        在指定位置之前插入节点,需要先找到目标位置的前一个节点,然后调整前后节点之间的连接关系。

void InsertBefore(Node* pTarget, Node* pNode)
{if (pTarget == NULL || pTarget->prev == NULL || pNode == NULL){return;}Node* pPrevNode = pTarget->pPrev;pPrevNode->pNext = pNode;pNode->pPrev = pPrevNode;pNode->pNext = pTarget;pTarget->pPrev = pNode;
}

删除节点

        根据待删除节点的位置不同,删除操作可以分为:删除头节点、删除尾节点、删除中间节点。

        在下面的示例代码中,我们根据删除的位置,进行了不同的处理。如果要删除的节点是当前链表的头节点,则将头指针指向下一个节点;如果新的头节点不为空,则将其前驱指针设置为NULL,因为它是新的第一个节点,没有前驱。如果不是头节点,则将前驱节点的pNext指向目标节点的后继节点;如果目标节点有后继节点,将其pPrev指向前驱节点,从而将目标节点从链表中移除。

void DeleteNode(Node** pHead, Node* pNode)
{if (pHead == NULL || pNode == NULL){return;}// 如果要删除的是头节点if (pNode == *pHead){*pHead = pNode->pNext;// 更新头指针if (*pHead != NULL){(*pHead)->pPrev = NULL;}}else{// 中间或尾部节点,先调整前驱节点的pNextif (pNode->pPrev != NULL){pNode->pPrev->pNext = pNode->pNext;}// 如果不是尾节点,则需要设置后继节点的pPrevif (pNode->pNext != NULL){pNode->pNext->pPrev = pNode->pPrev;}}delete pNode;pNode = NULL;
}

总结

        双向链表的优势主要在于:灵活性和高效性。在需要频繁进行插入和删除操作的应用场景中,双向链表通常比数组更优,因为不需要移动其他元素以保持连续性。另外,由于可以双向遍历,查找特定元素或执行反向遍历时也更为便捷。

http://www.dtcms.com/a/324595.html

相关文章:

  • scala 样例类
  • Spring的三层架构及其各个层用到注解详细解释。
  • 零基础学Java第三讲---运算符
  • android 使用openimagelib OpenImage 实现点击放大图片,浏览
  • 【Docker实战】Spring Boot应用容器化
  • 蓝牙认证流程:BQB 测试、互操作性验证与品牌授权指南 —— 面试高频考点与历年真题解
  • Bean的实例化方式
  • WinForm之TreeView控件
  • 深入解析React Diff 算法
  • 基于 InfluxDB 的服务器性能监控系统实战(三)
  • Windchill 11.0使用枚举类型自定义实用程序实现角色管理
  • Web API开发中的数据传输:MIME类型配置与编码最佳实践
  • vulnhub-Doubletrouble靶机
  • 医学统计(随机对照研究分类变量结局数据的统计策略3)
  • AI正自我觉醒!
  • C4.5算法:增益率(Gain Ratio)
  • 洛谷 P2404 自然数的拆分问题-普及-
  • 3.3keep-alive
  • Windows11 [Close Folder Shortcut]
  • vue2升级vue3:单文件组件概述 及常用api
  • Android Intent 解析
  • 【Linux】通俗易懂讲解-正则表达式
  • 从Redisson源码角度深入理解Redis分布式锁的正确实现
  • JetPack系列教程(三):Lifecycle——给Activity和Fragment装个“生命探测仪“
  • redis主从模型与对象模型
  • Beelzebub靶机练习
  • 代码随想录算法训练营第五十九天|图论part9
  • 下一代防火墙总结
  • 【软考中级网络工程师】知识点之 PPP 协议:网络通信的基石
  • Stlink识别不到-安装驱动