【数据结构】速速收藏,一文带你参透双向链表各接口实现_使用双向链表实现数据接收的方法

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

1.工程文件建立:

我们仍使用模块化开发格式,使用 List.h 、List.c 、test.c 三个文件进行代码书写:

  • List.h存放函数声明、包含其他头文件、定义宏
  • List.c书写函数定义,书写函数实现
  • test.c书写程序整体执行逻辑

这其中,我们的接口实现主要研究的是函数实现文件 List.c中的内容,对 test.c 文件中的内容分不关心

2.接口实现(本文重点):

这里是本文重点中的重点,即 List.c 文件中的接口具体实现:

Ⅰ.双向链表初始化:
  • 双向链表初始化:
  • 动态申请首节点,并使两个指向直接前置节点与直接后继节点的指针均指向自己,形成循环结构。
  • 最后返回初始化完成的头节点
LNode* LInit()
{
	//哨兵位头节点:
	LNode* phead = (LNode*)molloc(sizeof(LNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
Ⅱ.打印双向链表:
  • 执行操作前需对传入指针进行非空判断,防止对空指针进行操作。
  • 双向链表的打印方式与单链表相似,采用方式均为由头节点开始,通过节点指针指向寻找下一节点的方式循环进行遍历打印
  • 不同点是,由于双向循环链表首尾相连,形成闭环,因此终止循环打印条件将不再是执行至空指针而是执行回到头节点(即完成整个循环)
void LPrint(LNode* phead)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}
Ⅲ.申请新节点:
  • 新节点的申请与单链表基本相同,动态申请新节点后,再对新节点进行操作。
  • 不同点是双向链表多出一个指向直接前驱节点的指针,因此该指针也需要在使用前进行置空操作,防止造成野指针错误。
LNode* BuyListNode(LDataType x)
{
    LNode* newnode = (LNode*)malloc(sizeof(LNode));
    newnode->data = x;
    newnode->next = NULL;
    newnode->prev = NULL;
    return newnode;
}
Ⅳ.双向链表尾插:
  • 执行操作前应当进行非空判断,防止传入空指针
  • 在进行尾插操作前应当首先找到尾节点,采用的方式是,通过双向循环链表中头节点的前驱指针指向来找到尾节点。
  • 接着动态申请新节点
  • 最后执行尾插操作。首先使前面找到的尾节点的后继指针指向新节点,并使新节点的前驱指针也指向尾节点;接着使头节点的前驱指针指向新节点,并使新节点的后继指针指向头节点
void LPushBack(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾节点
    LNode* newnode = BuyListNode(x);
    //新尾互指:
    tail->next = newnode;
    newnode->prev = tail;
    //新头互指:
    phead->prev = newnode;
    newnode->next = phead;
}
  • 测试尾插接口功能实现:

Ⅴ.双向链表尾删:
  • 在双向链表进行尾删时,不仅要防止传入空指针,同时也要注意避免链表为空(只含有哨兵节点)的情况发生
  • 首先找到尾节点及尾节点的前驱节点,接着使该前驱节点与头节点跳过尾节点互指,最后释放原尾节点并置空即可
void LPopBack(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾
    LNode* tailprev = tail->prev;    //找到尾的前驱
    tailprev->next = phead;    //尾前驱节点的后继指针指向头
    phead->prev = tail->prev;  //头的前驱指针指向尾的前驱
    free(tail);
    tail = NULL;
}
  • 测试尾删接口功能实现:

Ⅵ.双向链表头插:
  • 执行操作前应当对传入指针进行判断,防止传入空指针
  • 在改变指向前,应当首先保存哨兵节点的后继节点,因为当我们插入新节点后链表结构将会发生改变,再想要找到该节点将变得麻烦
  • 进行插入操作时,使哨兵节点的后继指针指向新节点,再使新节点的前驱指针指向哨兵节点,接着使用同样的操作使新节点与哨兵节点的原后继节点互指
void LPushFront(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* newnode = BuyListNode(x);
    LNode* next = phead->next;    //保存哨兵节点的后继节点
    phead->next = newnode;    //哨兵点的后继指针指向新节点
    newnode->prev = phead;    //新节点的前驱指针指向哨兵节点
    newnode->next = next;    //新节点的后继指针指向哨兵节点的原后继节点
    next->prev = newnode;    //哨兵节点的原后继节点前驱指针指向新节点
}
  • 测试头插接口功能实现:

Ⅶ.双向链表头删:
  • 首先**进行非空判断,并排除链表为空(只含有哨兵节点)**的情况。
  • 开始进行头删操作前找到并保存头节点,防止改变指向后链表结构发生变化难以找到原本的头节点,以便于最后进行释放
  • 并且应当找到并保存原头节点的后继节点,防止防止改变指向后链表结构发生变化而难以找到
  • 具体操作便是使哨兵节点与头节点的后继节点跳过头节点互指,再将要删除的头节点释放并置空即可。
void LPopFront(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* next = phead->next;    //保存头节点,便于释放
    LNode* nextNext = next->next;    //保存头节点的后继节点
    phead->next = nextNext;
    nextNext->prev = phead;
    free(next);
    next = NULL;
}
  • 测试头删接口功能实现:

Ⅷ.双向链表查找:
  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 查找逻辑很简单,遍历整个链表,直至链表完整循环一遍,若比对存在匹配元素返回该节点,否则返回空。
LNode* LFind(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }
    return NULL;
}
  • 测试查找接口功能实现:

Ⅸ.双向链表给定节点前插:
  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 执行逻辑很简单,将数据存入通过动态申请的来的新节点中后,找到目标节点,使目标节点的前驱节点与新节点互指,再使新节点与目标节点互指接即可。
void LInsert(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* newnode = BuyListNode(x);
    posPrev->next = newnode;
    newnode->prev = posPrev;
    newnode->next = pos;
    pos->prev = newnode;
}
  • 测试前插接口功能实现:

Ⅹ.双向链表给定节点后插:
  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 执行逻辑与前插高度类似,不同的是使目标节点的后继节点与新节点互指,再使新节点与目标节点互指
void LInsertBack(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->next;
    LNode* newnode = BuyListNode(x);
    posPrev->prev = newnode;
    newnode->next = posPrev;
    newnode->prev = pos;
    pos->next = newnode;
}
  • 测试后插接口功能实现:

ⅩⅠ.双向链表删除给定节点:
  • 执行操作前需进行非空判断,防止操作空指针。
void LErase(LNode* pos)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* posNext = pos->next;
    posPrev->next = posNext;
    posNext->prev = posPrev;
    free(pos);
    pos = NULL;
}
  • 测试删除接口功能实现:

ⅩⅡ.双向链表销毁:
  • 哨兵节点为空,即表示链表内没有有效数据节点,则无需进行销毁、释放与置空操作
  • 含有有效节点则遍历所有节点,将每一个节点均进行释放,特别注意所有数据节点释放完毕之后,不要忘记释放哨兵节点
void LDestroy(LNode* phead)
{
    if(phead==NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead);
    {
        LNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(phead);
    phead = NULL;
}

🍄三、完整接口实现代码🍄:

1.List.h:

#pragma once

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

//自定数据类型LDataType:
typedef int LDataType;

//双向链表节点结构:
typedef struct ListNode
{
	LDataType data;
	struct LNode* next;
	struct LNode* prev;
}LNode;

LNode* LInit();    //初始化双向循环链表
void LPrint(LNode* phead);    //打印双向循环链表
LNode* BuyListNode(LDataType x);//双向循环链表新节点申请
void LPushBack(LNode* phead, LDataType x);    //双向循环链表尾插
void LPopBack(LNode* phead);    //双向循环链表尾删
void LPushFront(LNode* phead, LDataType x);    //双向循环链表头插
void LPopFront(LNode* phead);    //双向循环链表头删
LNode* LFind(LNode* phead, LDataType x);    //双向循环链表查找
void LInsertFront(LNode* pos, LDataType x);    //双向循环链表给定节点前插
void LInsertBack(LNode* pos, LDataType x);    //双向循环链表给定节点后插
void LErase(LNode* pos);    //双向循环链表给定节点删除
void LDestroy(LNode* phead);    //双向循环链表的销毁

2.List.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

//初始化双向循环链表初始化:
LNode* LInit()
{
	//哨兵位头节点:
	LNode* phead = (LNode*)malloc(sizeof(LNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

//打印双向循环链表
void LPrint(LNode* phead)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

//双向循环链表申请新节点:
LNode* BuyListNode(LDataType x)
{
    LNode* newnode = (LNode*)malloc(sizeof(LNode));
    newnode->data = x;
    newnode->next = NULL;
    newnode->prev = NULL;
    return newnode;
}

//双向循环链表尾插:
void LPushBack(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾节点
    LNode* newnode = BuyListNode(x);
    //新尾互指:
    tail->next = newnode;
    newnode->prev = tail;
    //新头互指:
    phead->prev = newnode;
    newnode->next = phead;
}

//双向循环链表尾删:
void LPopBack(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾
    LNode* tailprev = tail->prev;    //找到尾的前驱
    tailprev->next = phead;    //尾前驱节点的后继指针指向头
    phead->prev = tail->prev;  //头的前驱指针指向尾的前驱
    free(tail);
    tail = NULL;
}

//双向循环链表头插:
void LPushFront(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* newnode = BuyListNode(x);
    LNode* next = phead->next;    //保存哨兵节点的后继节点
    phead->next = newnode;    //哨兵点的后继指针指向新节点
    newnode->prev = phead;    //新节点的前驱指针指向哨兵节点
    newnode->next = next;    //新节点的后继指针指向哨兵节点的原后继节点
    next->prev = newnode;    //哨兵节点的原后继节点前驱指针指向新节点
}

//双向循环链表头删:
void LPopFront(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* next = phead->next;    //保存头节点,便于释放
    LNode* nextNext = next->next;    //保存头节点的后继节点
    phead->next = nextNext;
    nextNext->prev = phead;
    free(next);
    next = NULL;
}

//双向循环链表查找:
LNode* LFind(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }


![img](https://img-blog.csdnimg.cn/img_convert/a6e65fc78094d0442dde9025c76b6db7.png)
![img](https://img-blog.csdnimg.cn/img_convert/34ae2291fe057c5d2ce05554583bc6ca.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**


{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }


[外链图片转存中...(img-n0D2SuVJ-1715368550559)]
[外链图片转存中...(img-trEu6hia-1715368550559)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值