你好,这里是新人 Sunfor
这篇是我最近对于数据结构 双链表的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步
前言
一、双链表的概念
带头双向循环链表简称为:双链表
那我们之前提到的单链表是怎样的链表的简称呢?
答案是:不带头单向不循环链表
二、双链表的基本操作
创建双链表、插入、删除、遍历、查找、获取长度
三、双链表用C语言实现
类比于我们之前实现顺序表和单链表,同样是多文件操作
- List.h:主要存放代码实现过程中需要的头文件,同时充当目录
- List.c:函数的具体功能的实现
- text.c:测试函数的功能,包含主函数
List.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义双向链表的结构
typedef int LTDataType;
typedef struct ListNode
{
LTDataType x;
struct ListNode* next;//后继
struct ListNode* prev;//前驱
}LTNode;
//初始化
LTNode* LTInit();
//插入
```c
//判断传一级指针还是二级指针,就看phead指向的结点会不会发生改变
//如果发生改变,那么phead的改变要影响实参,传二级指针
//如果不发生改变,那么phead的改变不会影响实参,传一级指针
//在双向链表中 哨兵位不变(不能被修改 不能被释放)
void LTPushBack(LTNode* phead,LTDataType x);//尾插
void LTPushFront(LTNode* phead,LTDataType x);//头插
//打印
void LTPrint(LTNode* phead);
//判空
bool LTEmpty(LTNode* phead);
//删除
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
//查找
LTNode* LTFind(LTNode* phead,LTDataType x);
//在指定结点之后插入数据
void LTInsert(LTNode* pos,LTDataType x);
//删除指定结点
void LTErase(LTNode* pos);
//销毁
void LTDesTroy(LTNode* phead);//保持接口的一致性,传一级指针
List.c
#include"List.h"
//申请一个结点
LTNode* LTBuyNode(LTNDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if(newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->prev = newnode->next = NULL;
}
//初始化
//为了统一接口,传一级指针,就直接返回哨兵位
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//尾插
void LTPushBack(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev(tail) newnode
//先修改newnode
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);
//phead newnode phead->next
newnode->prev = phead;
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
}
//打印
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while(pcur != phead)
{
printf("%d->",pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//判空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
void LTPopBack(LTNode* phead)//尾删
{
assert(phead);
//phead prev(del->prev) del(phead->prev)
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)//头删
{
assert(phead);
//phead del(phead->next) del->next
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->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void LTInsert(LTNode* pos,LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->prev = pos;
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while(pcur != phead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
free(phead);//需要手动将哨兵位置空
phead = pcur = NULL;
}
text.c
#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);
/*LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);*/
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPrint(plist);
LTPrint(plist);
LTPrint(plist);
/*LTPushFront(plist, 1);
LTPrint(plist);
LTPushFront(plist, 2);
LTPrint(plist);
LTPushFront(plist, 3);
LTPrint(plist);
LTPushFront(plist, 4);
LTPrint(plist);*/
LTNode* pos = LTFind(plist, 1);
/*if (pos == NULL)
{
printf("没有找到!\n");
}
else
{
printf("找到了!\n");
}*/
/*LTInsert(pos, 11);
LTPrint(plist);*/
/*LTErase(pos);
LTPrint(plist);*/
LTDesTroy(&plist);
LTDesTroy(plist);
plist = NULL;
}
int main()
{
ListTest01();
return 0;
}
四、双链表的优缺点
优点
- 双向遍历:每个结点都有两个指针,一个指向前一个结点,另一个指向下一个结点,这使得从任一结点都可以方便地向前或向后遍历链表
- 插入和删除操作更灵活:在双链表中,删除一个结点时,只需要更改前后结点地指针,不需要从头遍历链表来找前一个结点,这样提高了效率
- 支持更复杂的数据结构:双链表适合实现一些更复杂的数据结构,例如双向队列,因为它们需要频繁地在两端插入和删除结点
- 实现回退功能:在某些应用中,双链表可以更容易地实现向前和向后导航功能
缺点
- 空间开销大:每个结点需要额外的存储空间来存储指向前一个结点的指针,这导致比单链表占用更多的空间
- 实现复杂性:由于需要管理两个指针,双链表的实现和维护相对较复杂,容易出现指针错误或内存泄漏的问题
- 缓存局部性差:双链表的结点在内存中可能不连续分布,这会导致较差的缓存性能,相比于数组和单链表,访问效率可能降低
- 操作开销:尽管插入和删除操作在已知结点时是O(1),但是在某些情况下(查找结点),整体操作可能会变得不如单链表高效,因为需要两次遍历,来找特定结点
五、双链表 VS 单链表
后记
双链表的知识就先在此告一段落啦,之后还会有相关知识随机掉落~
接下来就是对栈,队,堆 等知识的展开和了解啦
大家可以持续关注 会稳定更新~