文章目录
前言
上次介绍了不带头单向不循环链表,接下来我们一起看看双向带头循环链表。
一、双向链表的概念
双向带头循环链表是一种特殊的双向链表,具有以下特点:
-
双向链表: 每个节点有两个指针,一个指向前驱节点(
_prev
),一个指向后继节点(_next
)。因此,从任意节点可以沿链表向前或向后遍历。 -
带头节点: 链表中有一个特殊的头节点(通常不存储实际数据,称为哨兵节点)。头节点的主要作用是简化操作,如插入和删除。它提供统一的起始点,使得链表的头尾操作更加方便。
-
循环链表: 链表的最后一个节点的
_next
指向头节点,头节点的_prev
指向最后一个节点。因此,链表是循环的,没有明确的开始和结束,方便从任意节点进行循环遍历。
1. 结构特点:
- 头节点作为链表的起始和终止标记,解决了普通链表操作时需要判断空链表或链表尾部的问题。
- 空链表时,头节点的
_next
和_prev
都指向自己。
2. 优点:
- 统一操作: 无论链表是否为空,头节点总是存在,插入和删除操作不需要特殊处理空链表或只有一个节点的情况。
- 双向遍历: 可以从任意节点向前或向后遍历,非常灵活。
- 循环结构: 链表没有明确的尾部,方便处理循环场景。
双向带头循环链表常用于需要频繁插入和删除操作的场景,能够高效地处理这些操作。
二、双向链表的实现
1.双向链表的结构
typedef int LTDataType;
typedef struct ListNode
{
LTDataType date;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
双向链表有三个数据,保存它自身数据,他的前驱指针,以及他的后继指针。
2. 双向链表初始化
//双向链表初始化
void LTInit(LTNode** pphead);
因为初始化要创造头节点,而且为了方便后续插入操作创造节点,我们要先写出创造节点的方法
创造出的节点本身自己也必须是双向循环的才可以,他的prev以及next都指向他自己,自身成环。
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->date = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
//双向链表初始化
void LTInit(LTNode** pphead)
{
*pphead = LTBuyNode(-1);
}
3. 双向链表销毁
//双向链表销毁
void LTDesTory(LTNode** pphead);
销毁的时候要遍历链表,将链表中的每一个节点都销毁,这里我们传的是二级指针,头节点会被会被销毁,但后面为了统一接口需要手动释放。
//双向链表销毁
void LTDesTory(LTNode** pphead)
{
assert(pphead && *pphead);
LTNode* pcur = (*pphead)->next;
while (pcur != *pphead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
free(*pphead);
*pphead = NULL;
pcur = NULL;
}
4. 双向链表打印
//双向链表打印
void LTPrint(LTNode* phead);
和单链表一样,打印链表就是遍历链表.
注意,当时我们的循环条件是pcur != NULL,但是双链表是循环的,还这样写的话就成了死循环,因此需要让循环条件变成 pcur != phead
//双向链表打印
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->date);
pcur = pcur->next;
}
printf("\n");
}
5. 双向链表尾插
//双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x);
我们来看下面这张图,看一下尾插涉及到了哪些节点。
//双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
6. 双向链表头插
//双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);
我们来看下面这张图,看一下头插涉及到了哪些节点。
//双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
7. 双向链表尾删
//双向链表尾删
void LTPopBack(LTNode* phead);
删除操作要判断当前链表是不是空链表,如果为空就不进行删除。
为空就是链表只有头节点。
//双向链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
我们来看这张图。
//双向链表尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
8. 双向链表头删
//双向链表头删
void LTPopFront(LTNode* phead);
//双向链表头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
9. 双向链表查找
//双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x);
查找遍历链表,返回结点。
//双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->date == x)
{
//找到了
return pcur;
}
pcur = pcur->next;
}
//没找到
return NULL;
}
10. 双向链表指定位置插入
//双向链表指定位置插入
void LTInsert(LTNode* pos, LTDataType x);
//双向链表指定位置插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
11. 双向链表指定位置删除
//双向链表指定位置删除
void LTErase(LTNode* pos);
//双向链表指定位置删除
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
三、双线链表优化
因为有哨兵位的存在,我们不用改变头结点,因此就可以传一级指针
1. 初始化优化接口
将接口改为一级指针,因为形参无法影响实参,因此需要手动接收所创造出来的结点。
//优化让接口统一为 LTNode* phead
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
这样来接收~
LTNode* plist = LTInit();
2. 销毁优化接口
将接口改为一级指针,因为形参无法影响实参,因此创造的plist需要手动释放,将plist放置为NULL。
//优化接口统一为 LTNode* phead,但是需要手动释放让 *plist=NULL;!!!
void LTDesTory2(LTNode* phead)
{
assert(phead);
LTNode* pcur = (phead)->next;
while (pcur != phead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
free(phead);
phead = NULL;
pcur = NULL;
}
像这样置空~
//LTDesTroy(&plist);
LTDesTroy2(plist);
plist = NULL;
总结
到这里相信大家对双向链表有一个深入的了解了,最后的总结双向链表实现的源码奉上需要的小伙伴们自取哦~
//List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType date;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
//双向链表初始化
//void LTInit(LTNode** pphead);
//优化让接口统一为 LTNode* phead
LTNode* LTInit();
//双向链表销毁
void LTDesTory(LTNode** pphead);
//优化接口统一为 LTNode* phead,但是需要手动释放让 *plist=NULL;
void LTDesTory2(LTNode* phead);
//双向链表打印
void LTPrint(LTNode* phead);
//双向链表是否为空
bool LTEmpty(LTNode* phead);
//双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x);
//双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);
//双向链表尾删
void LTPopBack(LTNode* phead);
//双向链表头删
void LTPopFront(LTNode* phead);
//双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//双向链表指定位置插入
void LTInsert(LTNode* pos, LTDataType x);
//双向链表指定位置删除
void LTErase(LTNode* pos);
//Lish.c
#include"List.h"
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->date = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
//双向链表初始化
//void LTInit(LTNode** pphead)
//{
// *pphead = LTBuyNode(-1);
//}
//优化让接口统一为 LTNode* phead
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//双向链表销毁
//void LTDesTory(LTNode** pphead)
//{
// assert(pphead && *pphead);
// LTNode* pcur = (*pphead)->next;
// while (pcur != *pphead)
// {
// LTNode* Next = pcur->next;
// free(pcur);
// pcur = Next;
// }
// free(*pphead);
// *pphead = NULL;
// pcur = NULL;
//}
//优化接口统一为 LTNode* phead,但是需要手动释放让 *plist=NULL;!!!
void LTDesTory2(LTNode* phead)
{
assert(phead);
LTNode* pcur = (phead)->next;
while (pcur != phead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
free(phead);
phead = NULL;
pcur = NULL;
}
//双向链表打印
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->date);
pcur = pcur->next;
}
printf("\n");
}
//双向链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
//双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
//双向链表尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
//双向链表头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
//双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->date == x)
{
//找到了
return pcur;
}
pcur = pcur->next;
}
//没找到
return NULL;
}
//双向链表指定位置插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//双向链表指定位置删除
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//测试样例
#include"List.h"
void ListTest01()
{
//创建双向链表变量
//LTNode* plist = NULL;
//LTInit(&plist);
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPrint(plist);
//LTPushFront(plist, 1);
//LTPrint(plist);
//LTPushFront(plist, 2);
//LTPrint(plist);
//LTPushFront(plist, 3);
//LTPrint(plist);
//LTPushFront(plist, 4);
//LTPrint(plist);
//
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTNode* pos = LTFind(plist, 3);
//if (pos == NULL)
//{
// printf("没有找到!\n");
//}
//else
//{
// printf("找到了!\n");
//}
//LTInsert(pos, 11);
//LTErase(pos);
//LTPrint(plist);
//LTDesTroy(&plist);
LTDesTroy2(plist);
plist = NULL;
}
int main()
{
ListTest01();
return 0;
}
标签:List,next,链表,LTNode,phead,双向,数据结构,plist From: https://blog.csdn.net/Jdxxwu/article/details/142435545真相永远只有一个!