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

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
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 知识共享协议,转载请附上原文出处链接和本声明。


...全文
78 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用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日。

5,265

社区成员

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

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

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

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