【数据结构与算法】之双向链表及其实现

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
2025-01-23 20:37:09

1、双向链表的结构及概念

我们这里要实现的数据结构是带头双向循环的链表(简称双向链表)

下面就是该链表的物理模型啦~

https://i-blog.csdnimg.cn/blog_migrate/c070d929be82965fcfca6f06a598135c.png


2、双向链表的实现

2.1 要实现的接口(List.h)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDataType;
typedef struct ListNode
{
    struct ListNode* prev;//前驱指针
    LTDataType data;
    struct ListNode* next;//后驱指针
}LTNode;

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();

//链表销毁
void LTDestroy(LTNode* phead);

//链表的打印
void LTPrint(LTNode* phead);

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

//链表的尾删
void LTPopBack(LTNode* phead);

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

//链表的头删
void LTPopFront(LTNode* phead);

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置节点
void LTErase(LTNode* pos);

2.2 链表的初始化

注意:在初始化的时候一定要让头结点的prev指针和next指针都指向自己!

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{
    //初始化时创建一个带哨兵卫的头结点
    LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
    if (phead == NULL)
    {
        perror("malloc fail!\n");
        return NULL;
    }
    phead->next = phead->prev = phead;
    phead->data = -1;
    return phead;
}

2.3 链表的销毁

注意:我们一定是从链表的头结点(头结点中并没有有效数据的存储)的下一个位置开始销毁链表的!

//链表销毁
void LTDestroy(LTNode* phead)
{
    assert(phead);
    LTNode* pcur=phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur!=phead)
    {
        next = pcur->next;
        free(pcur);
        pcur = next;
    }
}

并且我们在调用链表的销毁函数后依然要手动释放动态内存开辟的phead头结点 !

为尽量确保接口传递参数的一致性我们并没有传递头结点的地址,所以我们并不能在链表的销毁函数中free我们的头结点!

LTDestroy(plist);
//动态开辟的头结点需要手动释放
free(plist);
plist = NULL;

2.4 链表的打印

遍历链表打印头结点,循环结束的条件是pcur=phead,继续的条件是pcur!=phead

//链表的打印
void LTPrint(LTNode* phead)
{
    assert(phead);
    LTNode* pcur = phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur != phead)
    {
        next = pcur->next;
        printf("%d->", pcur->data);
        pcur = next;
    }
    printf("\n");
}

2.5 链表的尾插

新节点的创建(单独封装成为一个函数)

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{
    LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间
    if (NewNode == NULL)//判断空间是否开辟成功
    {
        perror("malloc fail");
        return NULL;
    }
    NewNode->data = x;//赋值
    NewNode->next = NULL;//置空
    NewNode->prev = NULL;
    return NewNode;
}

链表的尾插 (在为尾插接口中直接调用创建节点的函数)

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    LTNode* tail = phead->prev;
    newNode->prev = tail;
    newNode->next = phead;
    tail->next = newNode;
    phead->prev = newNode;
}

2.6 链表的尾删

注意各个节点的指向!

//链表的尾删
void LTPopBack(LTNode* phead)
{
    //尾删的前提是双向链表不为空
    assert(phead && phead->next != phead);
    LTNode* tail = phead->prev;
    phead->prev = tail->prev;
    tail->prev->next=phead;
    free(tail);
    tail = NULL;
}

2.7 链表的头插

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    newNode->next = phead->next;
    newNode->prev = phead;
    phead->next->prev = newNode;
    phead->next = newNode;
}

2.8 链表的头删

//链表的头删
void LTPopFront(LTNode* phead)
{
    //头删的前提是双向链表不为空
    assert(phead && phead->next != phead);
    LTNode* start = phead->next;
    phead->next = start->next;
    start->next->prev = phead;
    free(start);
    start= NULL;
}

2.8 链表的查找

返回值是该指向该数据节点的结构体指针,如没有找到,直接返回空!

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* pcur = phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        next = pcur->next;
        pcur = next;
    }
    return NULL;
}

2.9 pos位置插入数据

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    newNode->next = pos->next;
    newNode->prev = pos;
    pos->next->prev = newNode;
    pos->next = newNode;
}

2.10 pos位置删除数据

//删除pos位置节点
void LTErase(LTNode* pos)
{
    assert(pos);
    pos->prev->next = pos->next;
    pos->next->prev = pos->prev;
    free(pos);
    pos = NULL;
}

3、完整代码

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDataType;
typedef struct ListNode
{
    struct ListNode* prev;//前驱指针
    LTDataType data;
    struct ListNode* next;//后驱指针
}LTNode;

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();

//链表销毁
void LTDestroy(LTNode* phead);

//链表的打印
void LTPrint(LTNode* phead);

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

//链表的尾删
void LTPopBack(LTNode* phead);

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

//链表的头删
void LTPopFront(LTNode* phead);

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置节点
void LTErase(LTNode* pos);

List.c

#include"List.h"

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{
    //初始化时创建一个带哨兵卫的头结点
    LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
    if (phead == NULL)
    {
        perror("malloc fail!\n");
        return NULL;
    }
    phead->next = phead->prev = phead;
    phead->data = -1;
    return phead;
}

//链表销毁
void LTDestroy(LTNode* phead)
{
    assert(phead);
    LTNode* pcur=phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur!=phead)
    {
        next = pcur->next;
        free(pcur);
        pcur = next;
    }
}

//链表的打印
void LTPrint(LTNode* phead)
{
    assert(phead);
    LTNode* pcur = phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur != phead)
    {
        next = pcur->next;
        printf("%d->", pcur->data);
        pcur = next;
    }
    printf("\n");
}

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{
    LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间
    if (NewNode == NULL)//判断空间是否开辟成功
    {
        perror("malloc fail");
        return NULL;
    }
    NewNode->data = x;//赋值
    NewNode->next = NULL;//置空
    NewNode->prev = NULL;
    return NewNode;
}

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    LTNode* tail = phead->prev;
    newNode->prev = tail;
    newNode->next = phead;
    tail->next = newNode;
    phead->prev = newNode;
}

//链表的尾删
void LTPopBack(LTNode* phead)
{
    //尾删的前提是双向链表不为空
    assert(phead && phead->next != phead);
    LTNode* tail = phead->prev;
    phead->prev = tail->prev;
    tail->prev->next=phead;
    free(tail);
    tail = NULL;
}

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    newNode->next = phead->next;
    newNode->prev = phead;
    phead->next->prev = newNode;
    phead->next = newNode;
}

//链表的头删
void LTPopFront(LTNode* phead)
{
    //头删的前提是双向链表不为空
    assert(phead && phead->next != phead);
    LTNode* start = phead->next;
    phead->next = start->next;
    start->next->prev = phead;
    free(start);
    start= NULL;
}

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* pcur = phead->next;
    LTNode* next = NULL;
    //结束条件是当pcur不等于篇phead
    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        next = pcur->next;
        pcur = next;
    }
    return NULL;
}

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newNode = ListCreatNode(x);//先创建一个新节点
    newNode->next = pos->next;
    newNode->prev = pos;
    pos->next->prev = newNode;
    pos->next = newNode;
}

//删除pos位置节点
void LTErase(LTNode* pos)
{
    assert(pos);
    pos->prev->next = pos->next;
    pos->next->prev = pos->prev;
    free(pos);
    pos = NULL;
}

Test.c(本人在实现双向链表时的测试代码)

#define _CRT_SECURE_NO_WARNINGS

#include"LIst.h"

void TestList1()
{
    LTNode* plist;
    plist = LTInit();//初始化链表
    LTPushBack(plist,1);
    LTPushBack(plist,2);
    LTPushBack(plist,3);
    LTPushFront(plist, 4);
    LTPushFront(plist, 4);
    /*LTPopFront(plist);
    LTPopFront(plist);*/
    LTNode* pos=LTFind(plist, 2);
    printf("删除pos位置之前\n");
    LTPrint(plist);
    LTErase(pos);
    printf("删除pos位置之后\n");
    LTPrint(plist);
    //LTInsert(pos, 5);
    //LTPopBack(plist);
    //LTPopBack(plist);
    //LTPopBack(plist);
    LTDestroy(plist);
    //动态开辟的头结点需要手动释放
    free(plist);
    plist = NULL;
}

int main()
{
    TestList1();

    return 0;
}

文章来源: https://blog.csdn.net/2301_80221228/article/details/137842914
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
58 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
昨日,11.19,最新整理了,第61-80题,现在公布上传。 另加上之前公布的第1-60 题,在此做一次汇总上传,以飨各位。 可以这么说,绝大部分的面试题,都是这100 道题系列的翻版, 此微软等公司数据结构+算法面试100 题系列,是极具代表性的经典面试题。 而,对你更重要的是,我自个还提供了答案下载,提供思路,呵。 所以,这份资料+答案,在网上是独一无二的。 ------------------------------------ 整理资源,下载地址: 答案系列: 1.[最新答案V0.3 版]微软等数据结构+算法面试100 题[第21-40 题答案] http://download.csdn.net/source/2832862 2.[答案V0.2 版]精选微软数据结构+算法面试100 题[前20 题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1 版本,进行的校正与修正。 3.[答案V0.1 版]精选微软数据结构+算法面试100 题[前25 题] http://download.csdn.net/source/2796735 题目系列: 4.[第一部分]精选微软等公司数据结构+算法经典面试100 题[1-40 题] http://download.csdn.net/source/2778852 5.[第1 题-60 题汇总]微软等数据结构+算法面试100 题 http://download.csdn.net/source/2826690 更多资源,下载地址: http://v_july_v.download.csdn.net/ 若你对以上任何题目或任何答案,有任何问题,欢迎联系我: My E-mail: zhoulei0907@yahoo.cn ------------- 作者声明: 本人July 对以上公布的所有任何题目或资源享有版权。转载以上公布的任何一题, 或上传百度文库资源,请注明出处,及作者我本人。 向你的厚道致敬。谢谢。 ---July、2010 年11 月20 日。 ------------------------------------------------------ 各位,若对以上100题任何一道,或对已上传的任何一题的答案, 有任何问题,请把你的思路、想法,回复到此帖子上, 微软等100题系列,永久维护地址(2010年11.26日): http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9.html
此为我个人搜集整理的, 精选微软等公司,有关 数据结构和算法的面试100题[前40题], 此绝对值得你下载收藏。 网友yui评论,真是够多的了,从此,不用再看其它面试题.... 一句话,请享用。 其它资源,下载地址: 1.[最新答案V0.3版]微软等数据结构+算法面试100题[第21-40题答案] http://download.csdn.net/source/2832862 2.[第1题-60题汇总]微软等数据结构+算法面试100题 http://download.csdn.net/source/2826690 3.[答案V0.2版]精选微软数据结构+算法面试100题[前20题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1版本,进行的校正与修正。 4.[答案V0.1版]精选微软数据结构+算法面试100题[前25题] http://download.csdn.net/source/2796735 5.[第二部分]精选微软等公司结构+算法面试100题[前41-60题]: http://download.csdn.net/source/2811703 6.[第一部分]精选微软等公司数据结构+算法经典面试100题[1-40题] http://download.csdn.net/source/2778852 更多资源,下载地址: http://v_july_v.download.csdn.net/ //请继续期待,后续内容。 ------------------------------------------------------ 各位,若对以上100题任何一道,或对已上传的任何一题的答案, 有任何问题,请把你的思路、想法,回复到此帖子上, 微软等100题系列,永久维护地址(2010年11.26日): http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9.html -------July、2010年12月2日。
1. C 语言中的指针和内存泄漏 5 2. C语言难点分析整理 10 3. C语言难点 18 4. C/C++实现冒泡排序算法 32 5. C++中指针和引用的区别 35 6. const char*, char const*, char*const的区别 36 7. C中可变参数函数实现 38 8. C程序内存中组成部分 41 9. C编程拾粹 42 10. C语言中实现数组的动态增长 44 11. C语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. C语言复杂表达式的执行步骤 66 16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与return的差别 141 30. extern与static存储空间矛盾 145 31. PC-Lint与C\C++代码质量 147 32. spirntf函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 170 35. 内存对齐与ANSI C中struct内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 186 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针 224 46. 标准C中字符串分割的方法 228 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解C语言指针的奥秘 236 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 265 54. 结构体对齐的具体含义 269 55. 连连看AI算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级C语言程序员测试必过的十六道最佳题目+答案详解 297 61. C语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 380 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C和C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 389 76. 顺序表及其操作 390 77. 单链表的实现及其操作 391 78. 双向链表 395 79. 程序员数据结构笔记 399 80. Hashtable和HashMap的区别 408 81. hash 表学习笔记 410 82. C程序设计常用算法源代码 412 83. C语言有头结点链表的经典实现 419 84. C语言惠通面试题 428 85. C语言常用宏定义 450

3,165

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧