目录
双向链表的基本概念和结构
**双向链表(Doubly Linked List)**是一种链式存储结构,每个节点除了存储数据外,还包含两个指针,分别指向前一个节点(prev)和后一个节点(next)。这种结构使得双向链表可以从头到尾遍历,也可以从尾到头遍历,非常灵活。
双向链表的每个节点包含以下部分:
- 数据元素:可以是任何类型的数据,如整数、浮点数、字符串或对象。
- prev指针:指向前一个节点的指针。
- next指针:指向下一个节点的指针。
双向链表通常包含一个头节点(哨兵位节点),这个节点不存储有效数据,只有指向第一个有效节点的next指针和指向尾节点的prev指针。只要链表存在,哨兵位节点就存在。
优点:
- 双向遍历:可以从两个方向遍历链表,既可以从头到尾,也可以从尾到头。
- 插入和删除操作高效:在已知前后节点的情况下,插入或删除操作可以直接进行,不需要遍历整个链表。
- 动态内存分配:链表可以在运行时动态地分配和释放内存,适合需要频繁增删操作的场景。
初始化
cpp
LTNode* ByNode(LTDatatype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("申请失败!\n");
exit(1);
}
//自成循环
newnode->val = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
void LTInit(LTNode **phead)
{
//创建一个哨兵卫节点
//*phead指向哨兵卫
*phead = ByNode(-1);
}
初始化的目的就是创建一个哨兵卫节点,传参为二级指针是因为初始化时要对哨兵卫进行改变,改变一个指针就要传这个指针的地址,传指针的地址就要用二级指针来接收。
让每一个新增节点都先自成循环,这样方便与链表连接起来。
尾插
cpp
void LTPushBack(LTNode* phead, LTDatatype x)
{
//不对哨兵卫进行改变,所以传一级指针
assert(phead); //哨兵卫不能为空
LTNode* newnode = ByNode(x);
newnode->prev = phead->next;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
与尾插相关的只有哨兵卫的prev指针指向的数据和哨兵卫。哨兵卫的prev指针指向的是链表中的最后一个数据,不管该数据是否是哨兵卫都没有关系。先让newnode的prev指针指向最后一个数据,next指针指向哨兵卫,再让最后一个数据的next指针指向newnode。
头插
cpp
void LTPushFront(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = ByNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
头插是往第一个有效数据前面插入,而不是在哨兵卫前面插入。先让newnode的next指针指向最后一个数据,newnode的prev指针指向哨兵卫,再让第一个有效数据的prev指针指向newnode,哨兵卫的next指针指向newnode就完成了头插。
尾删
cpp
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;
}
尾删的前提是你要有有效的数据,那么哨兵卫节点的next指针就不能指向哨兵卫自己。首先要记录最后的一个数据存储在del变量里,先让del的前一个数据的next指针指向哨兵卫,哨兵卫的prev指针指向del的前一个数据。然后释放del空间,将del置为空。
头删
cpp
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
首先要记录第一个有效数据存储在del变量里,先让del的下一个数据的prev指针指向哨兵卫,哨兵卫的next指针指向del的下一个数据。然后释放del空间,将del置为空。
查找
cpp
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->val == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
直接从链表的第一个有效数据开始遍历,结束条件是遍历到哨兵卫就结束,如果节点的值是你要查询的值,就返回这个节点。如果链表中没有你要查找的数据,就返回空。
在指定位置之后插入
cpp
void LTInsertBack(LTNode* pos, LTDatatype x)
{
assert(pos);
LTNode* newnode = ByNode(x);
newnode->prev = pos;
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
}
这个接口的原理是根据查找函数返回的节点进行插入。如果你指定在d2的后面插入数据,你就要先查找d2在哪里,返回这个节点。newnode的next指针指向pos的next指针指向的节点,newnode的prev指针指向pos节点,pos的next指针指向的节点的prev指针指向newnode,pos的next指针指向newnode。
删除指定位置节点
cpp
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
这个接口的原理是根据查找函数返回的节点进行删除。让pos节点的next指向的节点和prev指向的节点连接起来,然后释放pos节点,将pos节点置为空。
判空
cpp
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
判断该链表是否有有效数据,直接查看哨兵卫指向第一个有效节点的next指针是否指向哨兵卫,如果是,则说明该链表中只有一个哨兵卫,没有有效数据,就说该链表为空链表。
销毁
cpp
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur->next != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
链表的销毁要先从第一个有效数据开始删,最后释放掉哨兵卫节点。先定义一个指针pcur指向哨兵卫节点的next指针指向的节点,然后开始遍历链表进行删除。
完整代码
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
//申请节点
LTNode* ByNode(LTDatatype x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("申请失败!\n");
exit(1);
}
//自成循环
newnode->val = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
void LTInit(LTNode **phead)
{
//创建一个哨兵卫节点
//*phead指向哨兵卫
*phead = ByNode(-1);
}
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur->next != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->val);
pcur = pcur->next;
}
printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
//不对哨兵卫进行改变,所以传一级指针
assert(phead); //哨兵卫不能为空
LTNode* newnode = ByNode(x);
newnode->prev = phead->next;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = ByNode(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;
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->val == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
//在指定位置之后插入
void LTInsertBack(LTNode* pos, LTDatatype x)
{
assert(pos);
LTNode* newnode = ByNode(x);
newnode->prev = pos;
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
}
//在指定位置之前插入
void LTInitFront(LTNode* pos, LTDatatype x)
{
assert(pos);
LTNode* newnode = ByNode(x);
newnode->next = pos;
newnode->prev = pos->prev;
pos->prev->next = newnode;
pos->prev = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
//判空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
测试代码
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
void test()
{
LTNode *plist = NULL;
LTInit(&plist); //创建了一个哨兵卫
头插
//LTPushBack(plist, 1);
//LTPrint(plist);
//LTPushBack(plist, 2);
//LTPrint(plist);
//LTPushBack(plist, 3);
//LTPrint(plist);
//尾插
LTPushFront(plist, 1);
LTPrint(plist);
LTPushFront(plist, 2);
LTPrint(plist);
LTPushFront(plist, 3);
LTPrint(plist);
尾删
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
头删
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//查找
LTNode* find = LTFind(plist, 2);
//if (find)
// printf("找到了\n");
//else
// printf("没有找到\n");
//在指定位置之后插入
//LTInsertBack(find, 66);
//LTPrint(plist);
//LTInsertBack(find, 88);
//LTPrint(plist);
在指定位置之前插入
//LTInitFront(find, 66);
//LTPrint(plist);
//LTInitFront(find, 88);
//LTPrint(plist);
删除pos节点
//LTErase(find);
//LTPrint(plist);
//判空
//if (LTEmpty(plist))
// printf("为空\n");
//else
// printf("不为空\n");
LTDestory(plist);
}
int main()
{
test();
return 0;
}