前言
哈喽大家好呀,前段时间学习了双向链表,这部分知识虽然在单链表之后学但是呢代码少了很多,也不难理解,容易自己写出来,我们一起梳理下吧。
一,链表的结构
双向链表是带头双向循环链表,在它的每个节点中,都包含有数据和一个指向前一个节点和后一个节点的指针,最后一个节点又指向头节点实现循环,如图
于是,我们便可以通过申请新节点修改指针指向来修改链表的内容,双向链表的优势在于,知道其中一个节点,我们可以轻易地找到这个节点地上一个节点和下一个节点,因此,我们不用通过遍历原链表来获取链表的上一个节点,这使我们的数据查找变得容易,增加和删除数据比单链表更加高效,代码也更简洁,适合自己书写,根据链表的结构,可以看出我们要在头文件中定义这样一个结构体,(顺便偷偷包含好我们所需的头文件~)
有了这样一个结构体,我们便可以开始实现其他方法啦
链表初始化
与单链表不同,双向链表有一个不会被使用或者修改的头节点“哨兵卫”,这个头节点避免了链表为NULL的情况,简化了插入删除操作,我们首先malloc一个节点,同时考虑到后面需要动态开辟空间,我们把申请节点封装成一个函数,(注意上面把我们定义了int为SLDataType)
需要注意我们申请的每个节点都是自循环的(前后指针都指向自己)这样,才能实现循环结构,有了这个函数,我们便可以实现初始化
直接malloc一块空间放一个不需要的数字,注意这里需要通过传一级指针的地址以修改头节点的值,但是后面不会访问这个头节点,也不需要对他进行任何修改,因此在后面的方法传一级指针即可,这个后面不再赘述。
尾插方法
进行尾插,首先链表不为NULL,申请节点后我们先让新节点的尾指针指向“哨兵卫”,然后找尾操作,新节点的前指针指向指向尾节点,然后在让原链表尾指针指向型节点,“哨兵卫”前指针指向尾节点
代码实现
这里各个指针一定要理解清楚
头插方法
与尾插类似,只需找单到“哨兵卫”节点和下一个节点,就能简单实现头插操作,不在赘述
打印链表
为了方便调试,我们选择先实现打印链表方法,遍历答应即可,只需注意结束条件
头删操作
头删操作需要我们在保存了需要删除的节点后,先把指针指向正确位置在进行删除操作,并不难理解
尾删操作
这部分和头删类似,理不清指针指向最好画图
查找操作
查找操作和后面指定位置插入删除息息相关,需要查找到指定位置才能实现对指定位置的操作,也需要遍历链表查找,和上面的打印链表非常类似,需要注意这里要返回一个结构体指针
指定位置之后插入
大家完全可以理解为头节点为指定位置的头插操作,流程依然为:判空-申请节点-申请的节点指针指向指定位置和指定位置下一个节点-修改原链表指针
大家可以发现这里的代码和前面头插非常类似,可以说是包含关系,注意这里pos形参需要先通过查找操作找到对应指针才能传
指定位置删除
对应这一部分,我们需要先调整pos位置的前后节点的指针使其成为一个新的完整链表在进行释放操作,这里的pos不需要另外创建指针保存了
双链表的销毁
注意在释放一个节点前要先保存释放节点下一个节点,这样才能不断找到节点并释放节点,注意我们创建的“哨兵卫”需要手动置空,当然这里也可以传二级指针可以免去手动置空操作,不过为了代码可读性选择传一级指针。
尾声
好啦,到这里双向链表实现完成啦,笔者在写的时候确实感觉理解后写起来比单链表容易不少,这个代码很顺畅就写出来了,这里也鼓励大家多多去写哈,源码贴在最后啦,大家多多支持呀
SL.c
#include"SL.h"
SlqList* buySL(SLDataType x)
{
SlqList* newnode = (SlqList*)malloc(sizeof(SlqList));
if (newnode == NULL)
{
perror("malloc fail");
exit(1);
}
newnode->data=x;
newnode->next = newnode->prev = newnode;//自循环
return newnode;
}
void SLin(SlqList** pphead)
{
*pphead = buySL(-1);
}
void SLprint(SlqList* phead)
{
SlqList* plist = phead->next;//不访问“哨兵卫”节点
while (plist != phead)//注意条件
{
printf("%d->", plist->data);
plist = plist->next;
}
printf("\n");
}
void SLpushBack(SlqList* phead, SLDataType x)
{
assert(phead);
SlqList* newnode = buySL(x);//申请新节点
newnode->next = phead;
newnode->prev = phead->prev;//这两句代码是为了先让新节点的指针指向正确位置,避免出错
phead->prev/*找到尾节点*/->next = newnode;
phead->prev = newnode;
}
void SLpushFront(SlqList* phead, SLDataType x)
{
assert(phead);
SlqList* newnode = buySL(x);
newnode->next = phead->next;
newnode->prev = phead;//先让申请节点指针指向正确位置不容易出错
phead->next->prev = newnode;
phead->next = newnode;//在原链表修改只需要两句代码
}
void SLpopFront(SlqList* phead)
{
assert(phead && phead->next != phead);//必须链表存在并且存在节点才能删
SlqList* pcur = phead->next;//保存需要删除的节点
pcur->next->prev = phead;//让需删除节点后一个节点前指针指向“哨兵卫”
phead->next = pcur->next;
free(pcur);
pcur = NULL;
}
void SLpopBack(SlqList* phead)
{
assert(phead && phead->next != phead);
SlqList* pcur = phead->prev;//找尾,保存节点
pcur->prev/*找尾节点前一个节点*/->next = phead;
phead->prev = pcur->prev;
free(pcur);
pcur = NULL;
}
SlqList* SLFind(SlqList* phead,SLDataType x)
{
SlqList* plist = phead->next;//不动,不找头节点
while (plist != phead)
{
if (plist->data == x)
{
return plist;
}
plist = plist->next;//指针往后走
}
return NULL;//说明没找到
}
void SLPush(SlqList* pos, SLDataType x)
{
assert(pos);
SlqList* newnode = buySL(x);//下面代码和头插基本相同
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
void SLpop(SlqList* pos)
{
assert(pos);
pos->next->prev/*pos位置下一个节点前指针*/ = pos->prev;
pos->prev->next/*pos位置前一个节点后指针*/ = pos->next;
free(pos);
pos = NULL;
}
void SLDisdory(SlqList* phead)
{
SlqList* pcur = phead->next;
while (pcur != phead)
{
SlqList* next = pcur->next;//保存了pcur下一个节点,这样我们才能找到
free(pcur);
pcur = next;
}
phead = NULL;//这里形参改变不会影响实参,需要我们在执行完销毁操作后手动置空
}
SL.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SlqList
{
SLDataType data;
struct SlqList* prev;//指向前一个节点指针
struct SlqList* next;//指向后一个节点指针
}SlqList;
void SLin(SlqList** pphead);//链表初始化
void SLprint(SlqList* phead);//链表打印
void SLpushBack(SlqList* phead,SLDataType x);//尾插
void SLpushFront(SlqList* phead, SLDataType x);//头插
void SLpopFront(SlqList* phead);//头删
void SLpopBack(SlqList* phead);//尾删
SlqList* SLFind(SlqList* phead,SLDataType x);//数据查找
void SLPush(SlqList* pos, SLDataType x);//指定位置插入
void SLpop(SlqList* pos);//指定位置删除
void SLDisdory(SlqList* phead);//销毁链表
test.c(可以自己调用方法测试一下)
#include"SL.h"
void Test()
{
SlqList* plist=NULL;
SLin(&plist);
SLpushBack(plist, 1);
SLpushBack(plist, 2);
SLpushBack(plist, 3);
SLpushBack(plist, 4);
SlqList* ret = SLFind(plist, 3);
SLPush(ret, 44);
//SLDisdory(plist);
//plist = NULL;
SLprint(plist);
/*if (ret != NULL)
printf("找到啦\n");
else
printf("找不到");*/
}
int main()
{
Test();
return 0;
}
有问题欢迎指正~