一.双向链表相关知识的介绍
1.双向链表又叫带头双向循环链表
注:1.只有带有哨兵位的链表才叫带头链表,前面所介绍的单链表是不带头单向不循环链表
2.哨兵位只起到不让链表为空链表的作用,它里面不存储有效数据,但是它不能被删除,节 点地址也不能被修改
2.由于双向链表是循环的,所以双向链表包含三部分:<1>指向前一个结点的指针
<2>该节点存储的数据
<3>指向下一个节点的指针
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
LTDataType data;
struct ListNode* next;
}LTNode;
3.双向链表为空表示链表中只剩下哨兵位一个节点
但单链表为空表示链表中不存在节点
二.双向链表的实现
1.双向链表的初始化
1>双向链表包含哨兵位,故初始化双向链表即为其创建一个哨兵位
2>由于哨兵位不存储有效数据,故而在开辟空间时传个小于0的值即可
3>代码实现
void LTInit(LTNode** pphead)
{
//给双向链表创建一个哨兵位
*pphead = LTBuyNode(-1);
}
2.打印双向链表
1>当pcur=phead时,说明已经循环遍历一次链表了,此时应跳出循环
2>代码实现
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
3.申请新节点
1>双向链表的打印应该从第一个有效数据开始打印,故而pcur=phead->next,指向哨兵位后的一 个节点
2>由于双向链表是循环的,所以应该让prev,next均指向newnode的位置
3>代码实现
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->prev = newnode;
newnode->data = x;
newnode->next = newnode;
return newnode;
}
4.尾插
1>尾插应该在哨兵位前面一个节点的后面插入
2>此时该改变的数据有phead->prev,phead->prev->next,node->prev,node->next
3>为了防止不慎改变原有的数据导致出错,我们可以先对node的相关指针进行赋值
node->prev = phead->prev;//让新节点的prev指向原来的“尾节点”
node->next = phead;//让新节点的prev指向哨兵位
phead->prev->next = node;//让原先“尾节点”的next指向新节点
phead->prev = node;//让node成为新的“尾节点”
4>代码实现
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = LTBuyNode(x);
node->prev = phead->prev;
node->next = phead;
phead->prev->next = node;
phead->prev = node;
}
5.头插
1>头插是插在哨兵位后一个节点的前面
2>还是先对node的指针进行赋值
node->prev = phead;
node->next = phead->next;
phead->next = node;
phead->next->prev = node;
3>代码实现
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = LTBuyNode(x);
node->prev = phead;
node->next = phead->next;
phead->next = node;
phead->next->prev = node;
}
6.尾删
1>链表不能为空且链表为有效链表
2>代码实现
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;
}
7.头删
1>与尾删前提相同
2>代码实现
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;
}
8.在指定位置后插入节点
1>代码实现
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* node = LTBuyNode(x);
node->prev = pos;
node->next = pos->next;
pos->next = node;
pos->next->prev = node;
}
9.删除指定位置处的节点
1>代码实现
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
10.查找数据
1>代码实现
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;
}
11.销毁双向链表
1>定义一个指针next记录pcur后的节点,保证销毁下一个节点时可以找到它
2>代码实现
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//此时pcur指向phead,phead还未被销毁
free(phead);
phead = NULL;
}