🍃 如果觉得本系列文章内容还不错,欢迎订阅🚩
🎊个人主页:小编的个人主页
🎀 🎉欢迎大家点赞👍收藏⭐文章
✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
目录
🐼前言
🌟在上一节我们实现了单链表,如果感兴趣的小伙伴,可以阅读我的上一篇文章:> 单链表,这一节小编给大家介绍另一种常见的链表:带头双向循环链表
🐼双链表
✨链表组合起来有8种,包括带头不带头 ,循环 ,不循环 ,双向 ,单向 。
今天我们要介绍的是常用的一种带头双向循环链表如图:
🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟
✨双向链表节点定义如下:
c
typedef int LTDataType;
//双链表的结点定义
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;//指向下一个节点的指针
struct ListNode* prev;//指向前一个节点的指针
}LTNode;
🐼初始化双链表
c
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror(" LTBuyNode()::fail");
exit(-1);
}
node->data = x;
node->next = node->prev = node;//自循环
return node;
}
//初始化双链表
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;;
}
🌻代码解析
💫我们向堆上申请了一个节点的空间,这里封装了一个函数LTBuyNode()因为是自循环,初始将每个节点的next和prev指向自已。将返回值给头结点。即LTNode* plist = LTInit(); //创建双链表的头结点
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
💫由于不改变头结点,以下函数接口都用一级指针接收
🐼尾插
c
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev newnode
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
🌻代码解析
💫 插入操作需要申请新节点(newnode),先修改新节点(newnode)的next和prev指针,再修改尾结点(phead->prev)的指针,将尾结点指针((phead->prev)的next指向头结点(phead),将头结点(phead)的prev指向新节点(newnode)
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼打印双链表
c
//打印双链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
🌻代码解析
💫pcur指向第一个有效节点,让pcur往后走,直到pcur走到头结点,结束。
💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量
🐼判断双链表是否为空
c
//判断双链表是否为空
bool LTEmpty(LTNode* phead)
{
return (phead->next == phead);
}
🌻代码解析
通过布尔类型的返回值,如果头结点是自循环的,那么链表为空,返回true,否则,返回false
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🐼尾删
c
//尾删
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
🌻代码解析
先断言判断链表是否为空,将链表尾结点记作del,更新链表倒数第二个节点(del->prev)和头结点(phead)的指向。最后将尾结点(del)释放
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼头插
c
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//phead newnode phead->next
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->prev = newnode;
phead->next = newnode;
}
🌻代码解析
插入操作首先要申请新节点(newnode),先修改新节点(newnode)的指向,再修改头节点(phead)的前继指针和后继指针。
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼头删
c
//头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
//phead del->next
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
}
🌻代码解析
断言确保链表不为空,将头结点的第一个节点记作del,改变第二个有效节点(del->next)的前继指针prev和头结点的后继指针。
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼查找元素
c
//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(!LTEmpty(phead));
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
🌻代码解析
查找链表不能为空,将第一个有效节点记作pcur,pcur往后走,当pcur不为头结点时,循环继续,每一次循环都判断pcur->data == x,如果存在值,返回该节点;遍历结束,未找到,返回NULL
💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量
🍀测试结果:
🐼在pos位置之后插⼊数据
c
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x) {
assert(pos);
//pos newnode pos->next
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
🌻代码解析
插入操作首先要申请新节点,先改变新节点的前,后继指针,再修改pos->next的前继节点,和pos的后继节点。
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼删除指定位置
c
//删除指定位置
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;
}
🌻代码解析
修改的指针有pos->prev pos pos->next,将pos的前一个指针(pos->prev)的后继指针和pos的后一个节点(pos->next)的前继指针.
💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼销毁双链表
c
//销毁顺序表
void LTDestroy(LTNode* phead)
{
assert(phead && !LTEmpty(phead));
LTNode* pcur = phead->next;
while (pcur!=phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//头结点
free(phead);
phead = NULL;
}
🌻代码解析
将第一个有效节点记作pcur,保存pcur的下一个节点,删除pcur节点,让pcur走到next,最后将头结点释放,
💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量
🍂画图剖析:
🍀测试结果:
🐼全部源码
List.h
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int LTDataType;
//双链表的结点定义
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;//指向下一个节点的指针
struct ListNode* prev;
}LTNode;
//void LTInit(LTNode** pphead);
//初始化双链表
LTNode* LTInit();
//销毁双链表
void LTDestroy(LTNode* phead);
//打印双链表
void LTPrint(LTNode* phead);
//判断双链表是否为空
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
//删除指定位置
void LTErase(LTNode* pos);
//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x);
List.c
c
#define _CRT_SECURE_NO_WARNINGS
#include "Listnode.h"
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror(" LTBuyNode()::fail");
exit(-1);
}
node->data = x;
node->next = node->prev = node;//自循环
return node;
}
//初始化双链表
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;;
}
//销毁顺序表
void LTDestroy(LTNode* phead)
{
assert(phead && !LTEmpty(phead));
LTNode* pcur = phead->next;
while (pcur!=phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//头结点
free(phead);
phead = NULL;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev newnode
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//打印双链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//判断双链表是否为空
bool LTEmpty(LTNode* phead)
{
return (phead->next == phead);
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//phead newnode phead->next
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->prev = newnode;
phead->next = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
//phead del->next
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
}
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x) {
assert(pos);
//pos newnode pos->next
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(!LTEmpty(phead));
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
//删除指定位置
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;
}
test.c
c
#define _CRT_SECURE_NO_WARNINGS
#include "Listnode.h"
void test01()
{
LTNode* plist = LTInit(); //创建双链表的头结点
尾插
//LTPushBack(plist, 1);
//LTPushBack(plist, 2);
//LTPushBack(plist, 3);
//LTPushBack(plist, 4);
//LTPrint(plist);
尾删
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
/*LTPopBack(plist);
LTPrint(plist);*/
//头插
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 4);
LTPrint(plist);
//头删
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
/*LTNode* ret = LTFind(plist, 4);*/
/*if (ret != NULL)
printf("找到了\n");
else
printf("没找到\n");*/
//在pos位置之后插⼊数据
//LTInsert(ret, 99);
//LTPrint(plist);
//删除指定位置
//LTErase(ret);
//LTPrint(plist);
//销毁顺序表
LTDestroy(plist);
}
int main()
{
test01();
return 0;
}
🐼文末
感谢你看到这里,如果觉得本篇文章对你有帮助,点个赞👍 吧,你的点赞就是我更新的最大动力 ⛅️🌈 ☀️