> 文档中心 > 【初阶数据结构与算法】第五篇:双链表

【初阶数据结构与算法】第五篇:双链表

⭐️本篇博客我要给大家分享一下算法中的双链表。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页

目录

前言

🌏二、代码实现

        🍯1.双链表定义

        🍯2.双链表初始化两种形式

        🍯3.双链表申请空间

        🍯4.双链表尾插

        🍯5.双链表尾删

        🍯6.双链表打印

        🍯7.双链表头插

        🍯8.双链表头删

        🍯9.双链表查找

        🍯10.中间插入及头插和尾插优化

        🍯11.中间删除及头删和尾删优化

        🍯12.双链表销毁

🌏总结


前言


 🌏一、双链表概念

🍤第一个结点属于多开辟的空间,为了方便头插头删,后续结点为真正的链表内容。

内容:

        🍤一个指向前面结点的指针

        🍤一个存储数据的空间

        🍤一个指向后面结点的指针

🌏二、代码实现

        🍯1.双链表定义

🍤与链表不同的是,双链表结构更复杂,但其实实现起来比单链表简单,并且还有一个哨兵

//此双链表带哨兵位typedef int typedefData;typedef struct Double_linked_list {    typedefData Data; //数据    struct Double_linked_list* next;//下一个指针    struct Double_linked_list* prev;//上一个指针}Double_linked_list;

        🍯2.双链表初始化两种形式

🍤初始化时,我们要将哨兵位地址传过去,因为我们要修改哨兵位的值,将哨兵位值修改为0,两个指针都指向他自己

//初始化不带返回值void InitializeNotReturn_Double_linked_list(Double_linked_list** pphead) {    assert(pphead);    *pphead = Application_Double_linked_list(0);    (*pphead)->next = *pphead;    (*pphead)->prev = *pphead;}

 🍤这里不用传送哨兵位的值,因为直接在,函数中创建一个哨兵,并且将值设为0,两个指针指向他自己,最后将这个地址返回

//初始化带返回值Double_linked_list* InitializeReturn_Double_linked_list(void) {    Double_linked_list* phead = Application_Double_linked_list(0);    phead->next = phead;    phead->prev = phead;    return phead;}

        🍯3.双链表申请空间

🍤新的空间设为我们需要的值,将两个指针都置为空,返回新空间的地址

//申请一个空间Double_linked_list* Application_Double_linked_list(typedefData x) {    Double_linked_list* newnode = (Double_linked_list*)malloc(sizeof(Double_linked_list));    if (newnode == NULL) { printf("fail malloc:%s\n", strerror(errno)); exit(-1);    }    newnode->next = NULL;    newnode->prev = NULL;    newnode->Data = x;    return newnode;}

        🍯4.双链表尾插

🍤1.创建新结点存储数据

🍤2.原尾结点与新结点进行相互链接

🍤3.新结点与头结点相互链接

//尾插void PushBack_Double_linked_list(Double_linked_list* phead, typedefData x) {    assert(phead);    Double_linked_list* newnode = Application_Double_linked_list(x);    Double_linked_list* tail = phead->prev;    tail->next = newnode;    newnode->prev = tail;    phead->prev = newnode;    newnode->next = phead;}

        🍯5.双链表尾删

🍤1.找到尾结点和倒数第二个结点

🍤2.释放尾结点

🍤3.头结点和倒数第二个结点链接

void PopBack_Double_linked_list(Double_linked_list* phead) {    assert(phead);    Double_linked_list* tail = phead->prev;    Double_linked_list* tailprev = tail->prev;    phead->prev = tailprev;    tailprev->next = phead;    free(tail);    tail = NULL;}

        🍯6.双链表打印

🍤用一个cur节点指针来走,走到head的位置就停下来

void Print_Double_linked_list(Double_linked_list* phead) {    assert(phead);    Double_linked_list* cur = phead->next;    printf("Head ");    while (cur != phead) { printf(" %d ", cur->Data); cur = cur->next;    }    printf(" Head\n");}

        🍯7.双链表头插

🍤1.创建新结点存储数据

🍤2.新结点与头结点后的第一个结点链接

🍤3.第三步,新结点与头结点链接

void PushFront_Double_linked_list(Double_linked_list* phead, typedefData x) {    assert(phead);    Double_linked_list* newnode = Application_Double_linked_list(x);    Double_linked_list* cur = phead->next;    Double_linked_list* curnext = cur->next;    cur->next = newnode;    newnode->prev = cur;    curnext->prev = newnode;    newnode->next = curnext;}

        🍯8.双链表头删

🍤1.头结点与头删结点后的结点链接

🍤2.释放头删结点

void PopFront_Double_linked_list(Double_linked_list* phead) {    assert(phead);    Double_linked_list* cur = phead->next;    Double_linked_list* curnext = cur->next;    phead->next = curnext;    curnext->prev = phead;    free(cur);    cur = NULL;}

        🍯9.双链表查找

🍤直接遍历

Double_linked_list* Find_Double_linked_list(Double_linked_list* phead, typedefData x) {    assert(phead);    Double_linked_list* cur = phead->next;    while (cur != phead) { if (cur->Data == x) {     return cur; } cur = cur->next;    }    return NULL;}

        🍯10.中间插入及头插和尾插优化

                🍍中间插入

🍤任意位置前插入是在给定的pos结点之后插入

void Insert_Double_linked_list(Double_linked_list* pos, typedefData x) {    assert(pos);    Double_linked_list* newnode = Application_Double_linked_list(x);    Double_linked_list* next = pos->next;    newnode->next = next;    next->prev = newnode;    pos->next = newnode;    newnode->prev = pos;}

                🍍头插优化

🍤直接传头的下一个节点

void PushFront_Double_linked_list_other(Double_linked_list* pos, typedefData x) {    assert(pos);    //直接下一个节点插入就好了    Insert_Double_linked_list(pos->next, x);}

                🍍尾插优化

🍤直接传头的上一个节点

void PushBack_Double_linked_list_other(Double_linked_list* pos, typedefData x) {    assert(pos);    //直接在哨兵位之前插入即可,循环链表    Insert_Double_linked_list(pos->prev, x);}

        🍯11.中间删除及头删和尾删优化

                🍍中间删除

🍤判断是不是只有头了,只有头则不要删除

void Erase_Double_linked_list(Double_linked_list* phead, Double_linked_list* pos) {    assert(phead && pos);    //防止头节点为空    assert(phead->next != phead);    Double_linked_list* pospre = pos->prev;    Double_linked_list* posnext = pos->next;    pospre->next = posnext;    posnext->prev = pospre;    free(pos);    pos = NULL;}

                🍍头删优化

🍤同样判断是不是头,如果是头则不要删除

void PopFront_Double_linked_list_other(Double_linked_list* phead) {    assert(phead);    //防止头节点为空    assert(phead->next != phead);    Erase_Double_linked_list(phead, phead->next);}

                🍍尾删优化

🍤同样判断是不是头,如果是头则不要删除

void PopBack_Double_linked_list_other(Double_linked_list* phead) {    assert(phead);    //防止头节点为空    assert(phead->next != phead);    Erase_Double_linked_list(phead, phead->prev);}

        🍯12.双链表销毁

🍤轮流释放节点,最后释放头

void Destroy_Double_linked_list(Double_linked_list* phead) {    assert(phead);    Double_linked_list* cur = phead->next;    Double_linked_list* curnext = cur->next;    while (cur != phead) { free(cur); cur = curnext; curnext = curnext->next;    }    free(phead);    phead = NULL;}


🌏总结

链表和顺序表的对比

不同点 顺序表 链表
存储空间上 物理上连续 逻辑上连续
随机访问 支持 不支持
任意位置插入删除 要移动元素,O(N) 只要改变指针指向
插入数据 要考虑扩容,会带来一定的空间消耗 没有容量这个概念,可以按需申请和释放
缓存利用率
应用场景 元素高效存储+频繁访问 任意位置插入和删除频繁