一.链表的分类
链表的分类有3种分类,一共有8种(2*2*2):
(1)带头和不带头。
(2)单向和双向。
(3)循环和不循环
说明:
(1)单向链表只能从头结点遍历到尾结点,双向链表能从头走向为尾也能从
二.常见的两种链表
**1.**单链表 - 不带头单向不循环链表。
**2.**双向链表 - 双向带头循环链表。
三.双向链表的结点
**1.结点组成:**保存的数据 + 指向下一个节点的指针 + 指向前一个结点的指针。
2.结点定义:
typedef int LTDataType;
typedef struct ListNode {
int data;
struct ListNode* next;//指向后一个节点的指针
struct ListNode* prev;//指向前一个结点的指针
}LTNode;
四.双向链表节点的创建
用 malloc 申请结点内存,若内存开辟失败就打印错误信息并结束程序,给结点存入数据 x,同时把该结点的 next 和 prev 指针都指向自身,形成自循环,最后返回新结点的地址。
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
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);
//phead phead->prev(尾结点) newnode
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
七.双向链表的头插
断言校验哨兵头指针有效后创建新结点,先设置新结点的前后指针,再修改原首结点的前驱和哨兵头的后继,把新结点插入到哨兵头结点之后,成为链表第一个有效节点。
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
八.尾删
先断言确保链表不为空,拿到尾结点地址保存到 del,将尾结点的前一个结点的 next 指向哨兵头,再更新哨兵头的 prev 指向新的尾部结点,释放被删除结点的内存并置空局部指针,完成尾部结点的删除。
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
九.头删
断言保证链表非空,把哨兵头后的第一个有效结点记为待删结点 del,修改后继结点的前驱和哨兵头的后继指针,跳过待删结点,释放该结点内存并将局部指针置空,完成链表头部元素删除。
void LTPopFront(LTNode* phead, LTDataType x)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
//phead del del->next
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
十.查找
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;
}
十一.删除pos位置的结点
校验 pos 结点有效后创建新结点,先设置新结点的前后指针,再修改 pos 原后继结点的前驱与 pos 的后继指针,把新结点插入到 pos 结点的后面。
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
十二.删除pos位置的结点
断言确认待删结点 pos 有效,修改 pos 前后结点的双向指针,让前后结点直接相连绕过 pos,释放 pos 结点内存,将局部 pos 指针置空完成指定结点的删除。
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
十三.销毁
void LTDestroy(LTNode** pphead)
{
LTNode* pcur = (*pphead)->next;
while (pcur != *pphead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(*pphead);
*pphead = NULL;
}