目录
一、双向链表的定义
双向链表:双向带头不循环链表
组成: 数据+指向下一个节点的指针+指向上一个节点的指针
定义双向链表节点的结构:
c
typedef int LTDataType;//方便替换数据类型
typedef struct ListNode
{
LTDataType data;//节点存储的数据
struct ListNode* next;//指向下一个节点的指针
struct ListNode* prev;//指向上一个节点的指针
}LTNode;
二、申请节点
c
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node=(LTNode*)malloc(sizeof(LTNode));
//申请失败
if(node==NULL)
{
perror("malloc fail!");
exit(1);
}
//申请成功
node->data=x;//给节点赋值
node->next=node->prev=node;//创建头节点,前一个指向下一个和上一个节点的指针都指向自己
return node;
}
三、初始化

初始化申请一个自循环的哨兵卫节点
双向链表初始化时若next和prev都初始化为NULL,不满足循环要求
c
void LTInit(LTNode** pphead)
{
//创建哨兵位
*pphead=LTBuyNode(-1);//哨兵卫不存储有效值,但是这里参数必须传值
}
这里可以以参数形式返回哨兵卫,也可以以返回值的形式返回哨兵卫
c
LTNode* LTInit()
{
LTNode* phead=LTBuyNode(-1);
return phead;
}
四、尾插
不管是尾插还是头插或者是后面的删除操作的过程中,都没有去改变哨兵卫
头结点phead不能被删除,地址也不能被改变,因此这里传一级指针即可
因为有哨兵卫节点,所以这里不用再考虑头结点为空的情况
受到影响的是原尾节点的next指针和哨兵卫的prev指针
为避免影响原链表,先修改新节点newnode,让新节点的prve指针指向原尾节点,next指针指向哨兵卫;通过phead的prev指针找到原尾节点,让原尾节点的next指针指向新节点;让phead的prev指向新节点

Tips:最后两步顺序不能交换,否则无法找到原尾节点
空链表尾插:
让新节点newnode的prev指针指向phead,next指针指向phead,phead的next指针和prev指针都指向newnode,仍然成立

c
void LTPushBack(LTNode* phead,LTDataType x)
{
assert(phead);//哨兵卫不为空
LTNode* newnode=LTBuyNode(x);//申请新节点
newnode->prev=phead->prev;
newnode->next=phead;
phead->prev->next=newnode;
phead->prev=newnode;
}
五、打印链表
这里循环条件不能是(pcur!=NULL)否则会造成死循环
且这里不需要打印phead,phead无有效数据,因此从phead的下一个节点开始打印
结合来看,循环结束条件可以为pcur!=phead
c
void LTPrint(LTNode* phead)
{
LTNode* pcur=phead->next;
while(pcur!=phead)
{
printf("%d\n",pcur->data);
pcur=pcur->next;
}
printf("\n");
}
六、头插
往第一个有效节点前面插入(哨兵卫的后一个节点)
受到影响的有phead、phead->next、newnode三个节点
首先还是修改newnode,让它的prev指向哨兵卫,next指向哨兵卫的原下一个节点;哨兵卫的原下一个节点的prev指向newnode;让phead的next指向newnode
Tips:这里的最后两步也不能交换

c
void LTPushFront(LTNode* phead,LTDataType x)
{
assert(phead);//哨兵卫不得为空
LTNode* newnode=LTBuyNode(x);
newnode->next=phead->next;
newnode->prev=phead;
phead->next->prev=newnode;
phead->next=newnode;
}
七、尾删
先修改原尾节点的前一个节点(phead->prev->prev),让它的next指向phead;让phead的prev指向原尾节点的前一个节点;
这个时候无法再通过phead找到原尾节点了,因此这里可以给原尾节点改个名字叫del,这个时候可以简化上面的书写:修改phead->prev->prev->next为del->prev->next;phead->prev=del->prev;销毁del节点

c
void LTPopBack(LTNode* phead)
{
assert(phead&&phead->next!=phead);//哨兵卫不得为空,链表也不得为空(只有一个哨兵卫)
LTNode* del=phead->prev;
del->prev->next=phead;
phead->prev=del->prev;
free(del);//销毁del节点
del=NULL;
}
八、头删
由尾删可知,在删除节点的过程中我们可能找不到要删除的节点,因此我们可以先用del将该节点存下来,del=phead->next
再让第一个有效节点的下一个节点指向phead,接着让phead的next指向第一个有效节点的下一个节点,再销毁del节点

c
void LTPopFront(LTNode* phead)
{
assert(phead&&phead->next!=phead);//链表有效且链表不能为空
LTNode* del=phead->next;
phead->next=del->next;
del->next->prev=phead;
//这里上面两步可以交换
//删除del节点
free(del);
del=NULL;
}
九、查找
定义一个指针pcur指向第一个有效节点,遍历链表
c
LTNode* LTFind(LTNode* phead,LTDataType x)
{
LTNode* pcur=phead->next;
while(pcur!=phead)
{
if(pcur->data==x)
{
return pcur;
}
pcur=pcur->next;
}
return NULL;//没有找到
}
十、在pos位置之后插入数据
先修改newnode指向,让newnode的prev指向pos,next指向pos->next;让pos的原下一个节点(newnode->next)的prev指向newnode;让pos的next指向newnode

如果指定位置是原尾节点:

c
void LTInsert(LTNode* pos,LTDataType x)
{
assert(pos);
LTNode*newnode=LTBuyNode(x);//申请新节点
newnode->next=pos->next;
newnode->prev=pos;
pos->next->prev=newnode;
pos->next=newnode;
}
十一、删除pos节点
让pos的下一个节点的prev指针指向pos的前一个节点;让pos的前一个节点的next指针指向pos的下一个节点,释放pos节点

c
void LTErase(LTNode* pos)
{
assert(pos);//pos不能为空
assert(pos->next!=pos);//pos不能为哨兵卫节点
pos->next->prev=pos->prev;
pos->prev->next=pos->next;
free(pos);
pos=NULL;
}
这里删除pos并置空,是形参改变实参,可传二级指针,但是为了保持接口的一致性,这里我们传一级指针,降低记忆成本
LTErase的参数是节点指针,不是数据值,要先通过LTFind拿到对应节点地址
这里调用完LTErase之后要把find手动置空,避免野指针
十二、销毁链表
从第一个有效节点开始删除,删除了当前节点pcur无法走到下一个节点,因此要先用next把下一个节点存储起来,让pcur走到next的位置,next往后走,pcur删除next原来所在位置,不断将有效节点全部删除
出循环后,消除哨兵卫

c
void LTDesTroy(LTNode* phead)
{
assert(phead);//phead不能为空,这里是销毁,所以链表可以为空
LTNode* pcur=phead->next;
while(pcur!=phead)
{
LTNode*next=pcur->next;
free(pcur);
pcur=next;
}
//此时pcur指向phead,但phead还没有被销毁
free(phead);
phead=NULL;
}
销毁完之后phead置为空了,但是plist还没有置为空,因此调用时需要手动置空一下
十三、完整代码
List.h
c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
LTNode* LTBuyNode(LTDataType x);
void LTInit(LTNode** pphead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPrint(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
void LTDesTroy(LTNode* phead);
List.c
c
#include"List.h"
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->next = node->prev = node;
return node;
}
void LTInit(LTNode** pphead)
{
*pphead = LTBuyNode(-1);
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d\n", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
phead->next=del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
assert(pos->next!=pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}