双向链表
双向链表具体为带头循环双向链表
为保持接口的一致性,这里我们对具体的代码进行了优化,都是用一级指针。
1.双向链表的定义
定义一个双向链表的每一个结点又三个部分组成,分为该结点所存储的数据data,该结点的next指针指向该节点的下一个结点,该结点的prev指针指向该节点的前一个结点,这里我们定义为LTNode
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
2.双向链表的初始化
在初始化之前我们先要为链表申请空间,头结点的prev指针和next指针都指向自己。
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
初始化头结点
初始其数据为-1即可
LTNode* LTInit()
{
LTNode * phead = LTBuyNode(-1);
return phead;
}
3.头部插入
双向链表的头部插入与单链表的头部插入有所不同,双向链表的头部插入是在头结点的后一个位置插入,如果在头结点的前面插入,我们会发现和尾部插入相同,所以是在头结点的后方插入。
创建一个结点值为x
让新结点的next指针指向头结点的next结点
新结点的prev指针指向头结点
头结点的next指针指向的结点的prev指针指向新结点
头结点的next指针指向新结点
即完成头部插入。
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;
}
4.尾部插入
首先申请一个结点赋值为x
新结点的next指针指向头结点
因为头结点的prev指针所指向的就是最后一个数据,新结点的prev指针指向头结点的prev指针所指向的位置
原尾结点的next指针指向新结点
头结点的prev指针指向新结点
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;
}
5.判空
因为判空我们需要用到多次,我们直接封装成函数,用的时候直接调用即可。
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
6.尾部删除
首先要对链表进行判空,若为空,删除自然是不可以执行的
申请两个指针,del和prev
del指针指向头结点prev结点
prev指针执行那个del的头结点,也就是头结点的prev指针所指向的prev指针所指向的数据(头结点的前两个)
让prev指针的next的指针指向头结点
头结点的prev指针指向prev结点
最后不要忘记将del指针释放并置空
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
7.头部删除
头部删除也同理,并不是删除头结点,而是删除头结点的下一个结点
创建一个指针del指向头结点的next结点
del的next指针的prev指针指向头结点
头结点的next指针del的next指针所指向的位置
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
8.查找数据
在链表操作中,我们不能改变头指针,一旦更改头指针后,我们将找不到链表的头,我们创建一个指针pcur指向头结点的下一个结点
然后将pcur进行遍历,若找到了就return返回
找不到就返回空
LTNode* LTFind(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur ->data==x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
9.在指定位置之后插入数据
此操作比较简单,不再赘述
void LTInset(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
10.删除指定位置的结点
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
11.链表的销毁
先将头结点除外的所有结点释放,置空,最后对头结点也同样操作
在销毁的时候我们需要注意,因为我们想要保持接口的一致性,从而都用的一级指针,在链表的销毁的时候我们需要对其手动置零。
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = NULL;
}
free(phead);
phead = pcur = NULL;
}