双向链表的概念
双向链表的全称是带头双向循环链表,它可以在任意位置进行时间复杂度为0(1)的插入删除数据,并且可以按照需求申请释放和开辟空间,但他也有复杂的实现方式,这是它如此好用的代价.
节点的定义
对于双向链表的节点构造和单链表有点小区别,它有结点值,前驱指针,后继指针,来看节点的定义:
//结点定义
LTNode* LTBuyNode(LTDatatype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
初始时结点的前驱和后继指针指向自己,然后返回结点的指针.
双向链表的初始化
由于双向链表带哨兵位,所以初始化要创建一个哨兵位,哨兵位的值是-1,因为他的值不需要修改,所以我们让哨兵位初始化为-1,(当然其他值也可以,因为我们不需要哨兵位的值,这个传什么数都不影响)然后返回哨兵位的结点.
//初始化
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
头插
代码注释很详尽.
//头插
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->next = newnode;
phead->next->prev = newnode;
我们先改变头节点的后继指向,但是我们原来头节点的后继指向就找不到了,这个地方就出现问题了,原来插入一个节点需要改变四个指针的指向,但是我们只改变了三个指针,第四个指针找不到了,所以指针的改变顺序也是要注意的,确保当前指针指向的改变不会影响其他指针.
尾插
尾插的顺序也要注意.
//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead; //改变插入结点的后继指向
newnode->prev = phead->prev; //改变插入节点的前驱指向
phead->prev->next = newnode; //改变原来尾节点的后继指向
phead->prev = newnode; //改变头结点的前驱指向
}
头删
头删先找到要删除的头节点,然后通过该头节点找到新的头节点,然后改变新头结点的前驱和后继指针的指向,最后free要删除的头节点,记得free后置为空哟.
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;//找到头节点
LTNode* next = del->next;//找到头结点的下一个,作为新的头结点
next->prev = phead;//将新头结点的前驱指向哨兵位
phead->next = next;//将哨兵位的后继指向新的头结点
free(del);
del = NULL;//释放掉del后记得值空,当然在这个地方del不置空也不影响
//但是还养成好习惯
}
尾删
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->prev;//找到尾节点
LTNode* prev = del->prev;//找到尾结点的前一个,作为新的尾结点
prev->next = phead;//将新尾结点的后继指向哨兵位
phead->prev = prev;//将哨兵位的前驱指向新的尾结点
free(del);
del = NULL;
}
指定位置插入
//在Pos位置之后插入数据
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上一个结点的后继指针
//删除pos位置的数据
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
查找
查找链表不能为空呀,记得要断言
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
assert(phead);
assert(phead->next != phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
链表打印
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
销毁链表
//销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//销毁哨兵位
free(phead);
phead = NULL;
}
总结
双向链表的增删查改比较复杂,要改变四个指针的指向,注意指针改变的先后顺序,代码注释很清楚了,可以配合画图理解,注意细节,理清思路,废话(有bug不要慌,听首歌慢慢找,最重要的是认真仔细,想清楚自己每写一行代码的意思)
一起加油吧!!!
附完整代码
//List.h
#pragma once
#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);
//初始化
LTNode* LTInit();
//尾插
void LTPushBack(LTNode* phead, LTDatatype x);
//头插
void LTPushFront(LTNode* phead, LTDatatype x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在Pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x);
//删除pos位置的数据
void LTErase(LTNode* pos);
//打印
void LTPrint(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x);
//销毁链表
void LTDestory(LTNode* phead);
//List.c
#include"List.h"
//结点定义
LTNode* LTBuyNode(LTDatatype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
//初始化
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//尾插需要四步
void LTPushBack(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead; //改变插入结点的后继指向
newnode->prev = phead->prev; //改变插入节点的前驱指向
phead->prev->next = newnode; //改变原来尾节点的后继指向
phead->prev = newnode; //改变头结点的前驱指向
}
//头插需要四步
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);
assert(phead->next != phead);
LTNode* del = phead->prev;//找到尾节点
LTNode* prev = del->prev;//找到尾结点的前一个,作为新的尾结点
prev->next = phead;//将新尾结点的后继指向哨兵位
phead->prev = prev;//将哨兵位的前驱指向新的尾结点
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;//找到头节点
LTNode* next = del->next;//找到头结点的下一个,作为新的头结点
next->prev = phead;//将新头结点的前驱指向哨兵位
phead->next = next;//将哨兵位的后继指向新的头结点
free(del);
del = NULL;
}
//在Pos位置之后插入数据
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位置的数据
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
assert(phead);
assert(phead->next != phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
//销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//销毁哨兵位
free(phead);
phead = NULL;
}
//Test.c
#include"List.h"
int main()
{
LTNode* p1= LTInit();
//LTInit(&p1);
LTPushBack(p1, 1);
LTPushBack(p1, 2);
LTPushBack(p1, 3);
LTPushBack(p1, 4);
LTPushBack(p1, 5);
LTPushFront(p1, 100);
LTPushFront(p1, 200);
LTPushFront(p1, 300);
LTPushFront(p1, 400);
LTPushFront(p1, 500);
LTNode* plist = LTFind(p1, 300);
LTInsert(plist,600);
LTErase(plist);
/*LTPopBack(p1);
LTPopBack(p1);
LTPopBack(p1);
LTPopFront(p1);
LTPopFront(p1);
LTPopFront(p1);*/
LTPrint(p1);
LTDestory(p1);
return 0;
}