双向链表原来是这样实现的!

文章目录

  • 前言
  • 1. 双向链表的结构
  • 2. 双链表的定义和结构
  • 3. 定义结构体`(ListNode)`
  • 2.创建返回链表的头结点`CreateList`
    • 函数实现:
  • 3.初始化双向链表`ListCreate`
    • 定义函数:
    • 实现函数:
  • 4. 双向链表打印`(ListPrint)`
    • 定义函数:
    • 实现函数:
  • 5. 尾插函数 `(ListPopBack)`
    • 定义函数:
    • 实现函数:
    • 函数测试:
  • 6. 头插函数 `(ListPushFront)`
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
    • 7. 尾删函数(`ListPopBack`)
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
    • 8. 头删函数(`ListPopFront`)
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
    • 在这里插入图片描述
  • 9.双向链表查找函数` ListFind`
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
  • 10. 双向链表在pos的前面进行插入函数(`ListInsert`)
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
  • 11.“删除pos位置”函数`ListErase`
    • 定义函数:
    • 实现函数:
    • 函数测试:
    • 运行结果:
  • 12.销毁双向链表函数`ListDestory`
    • 定义函数:
    • 实现函数:
  • 完整的代码
  • 应用场景
  • 性能分析
  • 结语

前言

“我会定期分享我的学习经验,也欢迎大家留言和交流,让我们共同学习和进步!感谢大家的支持,让我们一起开启这段充满技术乐趣的旅程吧!”


1. 双向链表的结构

数据结构在计算机科学中扮演着关键角色,其中双链表作为一种强大的动态数据结构在实际编程中发挥着重要作用。在本文中,我们将深入研究双链表的定义、结构、基本操作,以及它在不同应用场景下的性能表现。

2. 双链表的定义和结构

双链表是一种由节点组成的数据结构,每个节点都包含一个数据域和两个指针域,分别指向前一个节点和后一个节点。这种结构为双链表带来了高度的灵活性,使其适用于各种复杂的编程场景。

3. 定义结构体(ListNode)

注意下述代码皆是:
SList.h头文件中定义函数
SList.c文件中实现函数
Test.c文件中函数测试

在SList.h头文件中:

typedef int LTDataType;
typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDataType val;
}ListNode;

2.创建返回链表的头结点CreateList

在实现下面的初始化函数之前,还需要一个函数来开辟空间给新的节。
SList.c文件中:

函数实现:

// 创建返回链表的头结点.
ListNode* CreateList(LTDataType x)
{// 分配新节点的内存ListNode* newNode = (ListNode*)malloc(sizeof(struct ListNode));// 检查内存分配是否成功if (newNode == NULL){perror("malloc fail");exit(-1);}// 初始化节点的值和指针newNode->val = x;newNode->next = NULL;newNode->prev = NULL;return newNode;
}

3.初始化双向链表ListCreate

SeqList.h文件中:

定义函数:

在这里插入图片描述

SList.c文件中:

实现函数:

// 初始化双向链表
ListNode* ListCreate(ListNode* pHead)
{// 创建哨兵节点pHead = CreateList(-1);pHead->next = pHead;pHead->prev = pHead;return pHead;
}

4. 双向链表打印(ListPrint)

SeqList.h文件中

定义函数:

在这里插入图片描述

SList.c文件中

实现函数:

// 打印双向链表
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("哨兵位<=>");// 遍历链表并打印每个节点的值while (cur != pHead){printf("%d<=>", cur->val);cur = cur->next;}printf("哨兵位\n\n\n");
}

5. 尾插函数 (ListPopBack)

定义函数:

在这里插入图片描述

实现函数:

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* tail = pHead->prev;ListNode* newNode = CreateList(x);// 将新节点插入到尾节点之后tail->next = newNode;newNode->prev = tail;newNode->next = pHead;pHead->prev = newNode;//如果实现了pos前面插入,尾插可以改为在phead前面插入,即尾插ListInsert(pHead, x);
}

函数测试:

void Test1()
{printf("尾插测试:\n");ListNode* plist = ListCreate();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPrint(plist);
}

运行结果:
在这里插入图片描述


6. 头插函数 (ListPushFront)

定义函数:

在这里插入图片描述

实现函数:

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newNode = CreateList(x);ListNode* fast = pHead->next;// 将新节点插入到头节点之后pHead->next = newNode;newNode->next = fast;newNode->prev = pHead;fast->prev = newNode;//如果实现了pos前面插入,头插可以改为在phead->next前面插入,即头插ListInsert(pHead->next, x);
}

函数测试:

void Test3()
{printf("头插测试:\n");ListNode* plist = ListCreate();ListPushFront(plist, 2);ListPushFront(plist, 4);ListPushBack(plist, 2);ListPushBack(plist, 4);ListPrint(plist);
}

运行结果:

在这里插入图片描述


7. 尾删函数(ListPopBack)

定义函数:

在这里插入图片描述

实现函数:

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{// 确保pHead不是空指针assert(pHead);// 确保链表不为空(至少有一个节点)assert(pHead->next != pHead);// 获取尾节点和其前一个节点ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;// 释放尾节点占用的内存free(tail);// 更新指针以保持链表的完整性pHead->prev = tailPrev;tailPrev->next = pHead;//如果实现了pos位置删除,尾删可以改为在phead->prev删除,即尾删ListErase(pHead->prev);
}

函数测试:

void Test2()
{printf("尾删测试:\n");ListNode* plist = ListCreate();ListPushBack(plist, 2);ListPushBack(plist, 4);ListPushBack(plist, 6);ListPopBack(plist);ListPrint(plist);
}

运行结果:

在这里插入图片描述


8. 头删函数(ListPopFront)

定义函数:

在这里插入图片描述

实现函数:

// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->prev != pHead);ListNode* first = pHead->next;ListNode* second = first->next;// 从头部删除节点pHead->next = second;second->prev = pHead;// 释放被删除的节点的内存free(first);first = NULL;//如果实现了pos位置删除,头删可以改为在phead->next删除,即头删ListErase(pHead->next);
}

函数测试:

void Test4()
{printf("头删测试:\n");ListNode* plist = ListCreate();ListPushFront(plist, 2);ListPushFront(plist, 4);ListPushBack(plist, 2);ListPushBack(plist, 4);ListPopFront(plist);ListPrint(plist);
}

运行结果:

在这里插入图片描述

9.双向链表查找函数 ListFind

定义函数:

在这里插入图片描述

用来确定pos位置,方便后面调用

实现函数:

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;// 遍历链表查找值为x的节点while (cur != pHead){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}

函数测试:

void Test5()
{printf("查找测试:\n");ListNode* plist = ListCreate();ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);ListNode* pos = ListFind(plist, 3);if(pos){pos->val *= 100;	//找到了就乘以100}else{printf("没有找到\n\n\n");}ListPrint(plist);
}

运行结果:

找到了就乘以100
在这里插入图片描述


10. 双向链表在pos的前面进行插入函数(ListInsert)

定义函数:

在这里插入图片描述

实现函数:

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newNode = CreateList(x);ListNode* posPrev = pos->prev;// 在pos的前面插入新节点newNode->next = pos;pos->prev = newNode;newNode->prev = posPrev;posPrev->next = newNode;//如果在phead前面插入,即尾插ListInsert(pHead, x);//如果在phead->next前面插入,即头插ListInsert(pHead->next, x);
}

函数测试:

void Test6()
{printf("pos前插入测试:\n");ListNode* plist = ListCreate();ListPushBack(plist, 2);ListPushBack(plist, 4);ListNode* pos = ListFind(plist, 4);ListInsert(pos, 3);ListPrint(plist);
}

运行结果:

在这里插入图片描述


11.“删除pos位置”函数ListErase

定义函数:

在这里插入图片描述

实现函数:

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev;ListNode* posNext = pos->next;// 删除pos位置的节点posPrev->next = posNext;posNext->prev = posPrev;// 释放被删除的节点的内存free(pos);pos = NULL;
}//如果在phead->prev前面删除,即尾删ListErase(pHead->prev);//如果在phead->next前面删除,即头删ListErase(pHead->next);

函数测试:

void Test7()
{printf("pos位置删除测试:\n");ListNode* plist = ListCreate();ListPushBack(plist, 2);ListPushBack(plist, 4);ListNode* pos = ListFind(plist, 4);ListErase(pos);ListPrint(plist);
}

运行结果:

在这里插入图片描述


12.销毁双向链表函数ListDestory

定义函数:

在这里插入图片描述

实现函数:

void ListDestroy(ListNode* pHead)
{ListNode* cur = pHead->next;// 释放每个节点的内存while (cur != pHead){ListNode* next = cur->next;free(cur);cur = next;}// 释放哨兵节点的内存free(pHead);pHead = NULL;
}

完整的代码

大家可以参考,我上传到了gitee,希望对你有帮助!
点击这里:双向链表的实现(gitee)

应用场景

双链表在实际编程中有着广泛的应用,其中之一是在LRU缓存算法中。通过双链表,我们能够高效地管理缓存中的数据,提升程序性能


性能分析

了解双链表的性能是至关重要的:
插入和删除:O(1) - 在头部或尾部插入或删除节点的操作是常数时间复杂度。
遍历:O(n) - 遍历整个双链表的时间复杂度是线性的。
这种性能表现使得双链表在某些场景下比其他数据结构更为优越。


结语

通过本文的学习,希望你对双链表有了更深入的理解。双链表的灵活性使得它成为数据结构中的一颗璀璨明珠,在你的编程旅途中,它将成为你的得力助手。

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

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

相关文章

Python---多任务的介绍

1. 提问 利用现学知识能够让两个函数或者方法同时执行吗? 不能&#xff0c;因为之前所写的程序都是单任务的&#xff0c;也就是说一个函数或者方法执行完成另外一个函数或者方法才能执行&#xff0c;要想实现这种操作就需要使用多任务。 多任务的最大好处是充分利用CPU资源&…

算法学习——栈与队列

栈与队列 栈与队列理论基础用栈实现队列思路代码 用队列实现栈思路代码 删除字符串中的所有相邻重复项思路代码 有效的括号思路代码 逆波兰表达式求值思路代码 滑动窗口最大值思路代码未完待续 前 K 个高频元素思路代码拓展 总结栈在系统中的应用括号匹配问题字符串去重问题逆波…

redis之五种基本数据类型

redis存储任何类型的数据都是以key-value形式保存&#xff0c;并且所有的key都是字符串&#xff0c;所以讨论基础数据结构都是基于value的数据类型 常见的5种数据类型是&#xff1a;String、List、Set、Zset、Hash 一) 字符串(String) String是redis最基本的类型&#xff0c;v…

【SpringBoot篇】基于布隆过滤器,缓存空值,解决缓存穿透问题 (商铺查询时可用)

文章目录 &#x1f354;什么是缓存穿透&#x1f384;解决办法⭐缓存空值处理&#x1f388;优点&#x1f388;缺点&#x1f38d;代码实现 ⭐布隆过滤器&#x1f38d;代码实现 &#x1f354;什么是缓存穿透 缓存穿透是指在使用缓存机制时&#xff0c;大量的请求无法从缓存中获取…

docker-compose介绍和用法

docker-compose介绍和用法详解 1、docker-compose介绍2、docker-compose build3、docker-compose down4、docker-compose up -d 1、docker-compose介绍 Docker Compose是一个用于快速配置多个Docker容器的工具。它是一个定义和运行多容器的Docker应用工具&#xff0c;通过YAML…

设计模式-GOF对各个模式的定义

以下内容是对设计模式之父GOF的著作《设计模式——可复用面向对象软件的基础》定义的摘抄 1 抽象工厂 意图 提供一个接口以创建一系列相关或相互依赖的对象&#xff0c;而无须指定它们具体的类。 适用性 在以下情况下使用抽象工厂模式&#xff1a; 一个系统要独立于它的产…

docker基本命令

1.docker命令图解 2. 从仓库拉取镜像 #下载最新版 docker pull nginx # 镜像名:版本名&#xff08;标签&#xff09; docker pull nginx:1.20.1docker rmi 镜像名:版本号/镜像id3. 容器启动及停止 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] docker run [设置项] 镜…

栈——OJ题

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、最小栈1、题目讲解2、思路讲解3、代码实现 二、栈的压入、弹出序列1、题目讲解2、思路讲解…

Windows使用VNC Viewer远程桌面Ubuntu【内网穿透】

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

系列十、存储引擎

一、存储引擎 1.1、概述 大家可能没有听说过存储引擎&#xff0c;但是一定听过引擎这个词&#xff0c;引擎就是发动机&#xff0c;是一个机器的核心组件。 比如&#xff0c;对于舰载机、直升机、火箭来说&#xff0c;它们都有各自的引擎&#xff0c;引擎是它们最为核心的组件。…

【NI-RIO入门】使用LabVIEW进行数据采集测量

于ni kb摘录 选择合适的编程模式 CompactRIO系统具有至少两个用户可选模式。某些CompactRIO型号具有附加的用户可选模式&#xff0c;可以在实时NI-DAQmx中进行编程。请参考本文以判断您的CompactRIO是否能够使用实时NI-DAQmx。将目标添加到项目后&#xff0c;将提示您选择要使…

Jmeter接口测试断言

一、响应断言 对服务器的响应接口进行断言校验&#xff0c;来判断接口测试得到的接口返回值是否正确。 二、添加断言 1、apply to&#xff1a; 通常发出一个请求只触发一个请求&#xff0c;所以勾选“main sampie only”就可以&#xff1b;若发一个请求可以触发多个服务器请…