博客主页:Code_文晓
本文专栏:数据结构与算法
欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!
✨✨想要学习更多数据结构与算法点击专栏链接查看✨✨
目录
一.双向循环链表
二.双向循环链表基础操作
1.存储结构
2.生成新结点
3.初始化链表
4.判断链表是否为空
5.打印链表
三.双向循环链表进阶操作
1.尾插法
2.头插法
3.尾删法
4.头删法
5.查找结点
6.在pos位置之前插入结点
7.删除pos位置的结点
8.改进后的尾插法和头插法
9.改进后的尾删法和头删法
10.链表的销毁
四.归纳总结
我们说双向循环链表,它也被称为循环双链表。在学习双向循环链表之前先来看一一下双链表的结构:
虽然双链表可以很方便地找到某个结点前后的所有结点,但寻找效率却不一定高。比如已经拿到了双链表中最后一个节点的指针,那我们要如何快速寻找第 1 个节点呢?双循环链表可以很好的解决这个问题。
在双链表的基础上,我们将链表中最后一个结点的后继指针由指向 nullptr 修改为指向头结点,将链表头结点的前趋指针由指向 nullptr 修改为指向最后一个结点,也就构成了双循环链表。
为了让实现代码简单,依旧在链表中引入头结点。当双循环链表为空时,头结点的前趋指针和后继指针都指向自身,如图所示:
空双循环链表头结点的后继和前趋指针都指向自身
当双循环链表不为空时,最后一个结点的后继指针指向头结点,头结点的前趋指针指向最后一 个结点,如图所示:
带头结点的双循环链表数据存储描述图
从上图可以看到,双循环链表的所有后继指针形成了一个环,所有前趋指针也形成了一个环 (一共两个环)。这样的话,给定一个数据结点,无论访问链表的后继结点还是前趋结点,都非常灵活和方便。
这里先给出以下双向循环链表各种操作的接口函数,以便查看所实现的功能:
// 存储结构
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}ListNode;
// 双向循环链表生成新结点
ListNode* ListCreate(LTDataType x);
// 双向循环链表初始化
ListNode* ListInit();
// 双向循环链表销毁
void ListDestroy(ListNode* phead);
// 双向循环链表打印
void ListPrint(ListNode* phead);
//判断循环链表是否为空
bool ListEmpty(ListNode* phead);
// 双向循环链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向循环链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向循环链表尾删
void ListPopBack(ListNode* phead);
// 双向循环链表头删
void ListPopFront(ListNode* phead);
// 双向循环链表结点查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向循环链表在pos位置前面插入结点
ListNode* ListInsert(ListNode* pos, LTDataType x);
// 双向循环链表删除pos位置结点
void ListErase(ListNode* pos);
双向循环链表的存储结构内容有一个前驱指针prev用来指向该结点的前一个结点;一个后继指针next指向后后一个结点;以及本结点存储的数据data。
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}ListNode;
为了方便后续对双向循环链表的各种操作,这里把malloc新结点封装成一个函数。
// 双向循环链表生成新结点
ListNode* ListCreate(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc failed");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
首先创建一个头结点,初始化双向循环链表的时候, 头结点的前驱指针和后继指针都要指向头结点,这样做有后续操作有很大的优势。例如:更方便判断链表是否为空;新结点头插到链表时的操作可以统一等。
// 双向循环链表初始化
ListNode* ListInit()
{
ListNode* phead = ListCreate(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
由上图也知,当头结点的next指针指向本身时,链表是空的。
// 双向循环链表判断是否为空
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
从链表的第一个结点(头结点后面的一个结点)开始往后遍历,每遍历到一个结点打印该结点的data数值。这里有一个问题,遍历带头结点的双向循环链表的终止条件是什么?
我们知道双向循环链表的特点是链表的最后一个结点的next指针是指向头结点的,遍历结束最后一个结点时再往后遍历就回到了头结点。所以cur指针指向头结点时,整个链表从第一个结点到最后一个结点都已经遍历一遍了。
// 双向循环链表打印
void ListPrint(ListNode* phead)
{
assert(phead);
printf("DummyHead<==>");
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向循环链表尾插方式1:
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = ListCreate(x);
ListNode* tail = phead->prev; //找到记录尾结点
tail->next = newnode; //尾结点的next指针指向要插入的新结点
newnode->prev = tail; //新结点的前驱指针指向尾结点
newnode->next = phead; //新结点的后继指针指向头结点
phead->prev = newnode; //头结点的前驱指针指向新结点
// 最后形成了一个新的双向循环链表
}
头插法的两种方式,基本原理是一样的。一种没有记录第一个结点,操作过程中顺序不可以改变,否则头插就会失败。另外一种用first指针记录第一个结点,这样顺序随意更自由。
第一种:
// 双向循环链表头插方式1:step1和step2顺序不能变
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = ListCreate(x);
//step1:
newnode->next = phead->next;
phead->next->prev = newnode;
//step2:
phead->next = newnode;
newnode->prev = phead;
}
第二种:
// 双向循环链表头插方式 2:step1和step2顺序随意
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = ListCreate(x);
ListNode* first = phead->next; //用指针first记录首元结点,即链表的第一个有效结点
//step1:
phead->next = newnode;
newnode->prev = phead;
//step2:
newnode->next = first;
first->prev = newnode;
}
// 双向循环链表尾删方式1:
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
// 双向循环链表头删方式1:
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
//ListNode* first = phead->next;
//phead->next = first->next;
//phead->next->prev = phead;
//free(first);
//phead->next = first->next;
//first->next->prev = phead;
//free(first);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
遍历链表,找到data为X的结点,找到的话返回这个结点的地址,找不到返回NULL。
// 双向循环链表结点查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 双向循环链表在pos位置之前插入结点
ListNode* ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//方式1:
/*ListNode* newnode = ListCreate(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;*/
//方式2:
ListNode* newnode = ListCreate(x);
ListNode* posPrev = pos->prev;
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向循环链表删除pos位置的结点
void ListErase(ListNode* pos)
{
assert(pos);
//方式1:
/*
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
*/
//方式2:
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
由方法6在pos位置之前插入结点 ,可以改进尾插法和头插法。
尾插法:是在最后一个结点后面位置插入新的结点,就相当于头结点位置的前一个,所以pos相当于头结点。
头插法:就相当于在第一个结点位置前插入新的结点,所以pos相当于第一个结点。
尾插法:
// 双向循环链表尾插方式2:
void ListPushBack(ListNode* phead, LTDataType x)
{
ListInsert(phead, x);
}
头插法:
// 双向循环链表头插方式3:
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
由方法7删除pos位置的结点 ,可以改进尾删法和头删法。
尾删法:就是删除最后一个结点位置的结点,就相当于头结点前一个位置的结点,所以pos相当于头结点前一个。
头删法:就是删除在第一个结点位置的结点,所以pos相当于第一个结点。
尾删法:
// 双向循环链表尾删方式2:
void ListPopBack(ListNode* phead)
{
assert(phead);
ListEras(phead->prev);
}
头删法:
// 双向循环链表头删方式2:
void ListPopFront(ListNode* phead)
{
assert(phead);
ListErase(phead->next);
}
// 双向循环链表销毁
void ListDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(phead);
}
这里提供所有代码,可以直接运行。创建三个文件,分别是List.h,List.c,Test.c。
List.h:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}ListNode;
// 双向循环链表生成新结点
ListNode* ListCreate(LTDataType x);
// 双向循环链表初始化
ListNode* ListInit();
// 双向循环链表销毁
void ListDestroy(ListNode* phead);
// 双向循环链表打印
void ListPrint(ListNode* phead);
//判断循环链表是否为空
bool ListEmpty(ListNode* phead);
// 双向循环链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向循环链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向循环链表尾删
void ListPopBack(ListNode* phead);
// 双向循环链表头删
void ListPopFront(ListNode* phead);
// 双向循环链表结点查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向循环链表在pos位置前面插入结点
ListNode* ListInsert(ListNode* pos, LTDataType x);
// 双向循环链表删除pos位置结点
void ListErase(ListNode* pos);
List.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
// 双向循环链表生成新结点
ListNode* ListCreate(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc failed");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
// 双向循环链表初始化
ListNode* ListInit()
{
ListNode* phead = ListCreate(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
// 双向循环链表打印
void ListPrint(ListNode* phead)
{
assert(phead);
printf("DummyHead<==>");
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向循环链表判断是否为空
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
// 双向循环链表尾插方式1:
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = ListCreate(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
// 双向循环链表尾插方式2:
//void ListPushBack(ListNode* phead, LTDataType x)
//{
// ListInsert(phead, x);
//}
// 双向循环链表头插方式1:step1和step2顺序不能变
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = ListCreate(x);
//step1:
newnode->next = phead->next;
phead->next->prev = newnode;
//step2:
phead->next = newnode;
newnode->prev = phead;
}
// 双向循环链表头插方式2:step1和step2顺序随意
//void ListPushFront(ListNode* phead, LTDataType x)
//{
// assert(phead);
// ListNode* newnode = ListCreate(x);
// ListNode* first = phead->next; //用指针first记录首元结点,即链表的第一个有效结点
//
// //step1:
// phead->next = newnode;
// newnode->prev = phead;
// //step2:
// newnode->next = first;
// first->prev = newnode;
//}
// 双向循环链表头插方式3:
//void ListPushFront(ListNode* phead, LTDataType x)
//{
// assert(phead);
// ListInsert(phead->next, x);
//}
// 双向循环链表尾删方式1:
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
// 双向循环链表尾删方式2:
//void ListPopBack(ListNode* phead)
//{
// assert(phead);
//
// ListEras(phead->prev);
//}
// 双向循环链表头删方式1:
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
//ListNode* first = phead->next;
//phead->next = first->next;
//phead->next->prev = phead;
//free(first);
//phead->next = first->next;
//first->next->prev = phead;
//free(first);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
// 双向循环链表头删方式2:
//void ListPopFront(ListNode* phead)
//{
// assert(phead);
//
// ListErase(phead->next);
//}
// 双向循环链表结点查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 双向循环链表在pos位置之前插入结点
ListNode* ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//方式1:
/*ListNode* newnode = ListCreate(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;*/
//方式2:
ListNode* newnode = ListCreate(x);
ListNode* posPrev = pos->prev;
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向循环链表删除pos位置的结点
void ListErase(ListNode* pos)
{
assert(pos);
//方式1:
/*
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
*/
//方式2:
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
// 双向循环链表销毁
void ListDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(phead);
}
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void TestList()
{
ListNode* plist = ListInit();
printf("尾插1,2:\n");
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPrint(plist);
printf("头插3,4,5:\n");
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPushFront(plist, 5);
ListPrint(plist);
printf("尾删一个元素:\n");
ListPopBack(plist);
ListPrint(plist);
printf("头删一个元素:\n");
ListPopFront(plist);
ListPrint(plist);
int i = 0;
printf("请输入你要查找的结点:\n");
scanf("%d", &i);
ListNode* pos = ListFind(plist, i);
if (pos)
{
printf("查找data为%d的结点并打印其data:\n", i);
printf("%d\n", pos->data);
}
else
{
printf("没有data为%d的结点\n",i);
}
printf("请输入两个结点,一是插入的位置前面,二是插入的结点:\n");
int k = 0, j = 0;
scanf("%d%d", &k, &j);
pos = ListFind(plist, k);
if (pos)
{
ListInsert(pos, j);
printf("在data为%d的结点前插入一个值为%d的结点后的链表:\n", k, j);
ListPrint(plist);
}
else
{
printf("没有data为%d的结点,插入失败\n",k);
}
printf("请输入你要删除的结点:\n");
int m = 0;
scanf("%d", &m);
pos = ListFind(plist, m);
if (pos)
{
ListErase(pos);
printf("删除结点%d后的链表:\n", m);
ListPrint(plist);
}
else
{
printf("没有data为%d的结点,删除失败\n",m);
}
ListDestroy(plist);
plist = NULL;
}
int main()
{
TestList();
return 0;
}