目录
双向链表的基本概念和结构
初始化
尾插
头插
尾删
头删
查找
在指定位置之后插入
删除指定位置节点
判空
销毁
完整代码
测试代码
双向链表的基本概念和结构
双向链表(Doubly Linked List)是一种链式存储结构,每个节点除了存储数据外,还包含两个指针,分别指向前一个节点(prev)和后一个节点(next)。这种结构使得双向链表可以从头到尾遍历,也可以从尾到头遍历,非常灵活。
双向链表的每个节点包含以下部分:
- 数据元素:可以是任何类型的数据,如整数、浮点数、字符串或对象。
- prev指针:指向前一个节点的指针。
- next指针:指向下一个节点的指针。
双向链表通常包含一个头节点(哨兵位节点),这个节点不存储有效数据,只有指向第一个有效节点的next指针和指向尾节点的prev指针。只要链表存在,哨兵位节点就存在。
优点:
- 双向遍历:可以从两个方向遍历链表,既可以从头到尾,也可以从尾到头。
- 插入和删除操作高效:在已知前后节点的情况下,插入或删除操作可以直接进行,不需要遍历整个链表。
- 动态内存分配:链表可以在运行时动态地分配和释放内存,适合需要频繁增删操作的场景。
初始化
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 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。
头插
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就完成了头插。
尾删
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置为空。
头删
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置为空。
查找
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;
}
这个接口的原理是根据查找函数返回的节点进行插入。如果你指定在d2的后面插入数据,你就要先查找d2在哪里,返回这个节点。newnode的next指针指向pos的next指针指向的节点,newnode的prev指针指向pos节点,pos的next指针指向的节点的prev指针指向newnode,pos的next指针指向newnode。
删除指定位置节点
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节点置为空。
判空
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
判断该链表是否有有效数据,直接查看哨兵卫指向第一个有效节点的next指针是否指向哨兵卫,如果是,则说明该链表中只有一个哨兵卫,没有有效数据,就说该链表为空链表。
销毁
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指针指向的节点,然后开始遍历链表进行删除。
完整代码
#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;
}
测试代码
#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;
}