双链表(DoubleLinkList)数据结构的基本操作实现详解,重磅来袭!结构复杂但高效~

Posted SuchABigBug

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双链表(DoubleLinkList)数据结构的基本操作实现详解,重磅来袭!结构复杂但高效~相关的知识,希望对你有一定的参考价值。

一、前言

今天讲一下最实用的双链表,这个比单链表更厉害了,下图中,头尾节点的增删都为O(1),虽结构复杂一点,但是效率更高



时间复杂度好坏可以参考前面所写的数据结构进行比较

🔗 🔗 🔗 顺序表 🔗 🔗 🔗

👉 👉 👉 单链表 👈 👈 👈

二、整体框架设计


还是和之前一样实现doubleLinkList分为三个文件,这样可读性更高,便于调试

test.c 专门用于函数调用、调试
doubleList.h 只用于函数声明
doubleList.c 用于函数实现

三、函数实现

在实现函数之前,我们想一想双链表需要怎样设计呢?如果按照单链表的思想去设计,那么尾插要达到O(1)是根本做不到的,除非头节点可以直接去访问尾节点,但单链表中当前节点要访问前一个节点的话,不做记录是根本不可能访问到的呀

那怎么办呢?
有人肯定想到了在结构体中添加一个新的成员,创建一个pre指针用于存储当前节点的前一个节点的地址不就可以了嘛

那么问题又来了,头节点怎么就能直接访问到尾节点了呢?
带着这个疑惑我们参考一下上图,发现head节点的前一个节点是指向最后一个节点的,那么也就实现了一步到位

我们先来看下头文件的构

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int DataType;

typedef struct doubleLinkList{
    DataType data;
    struct doubleLinkList* pre;
    struct doubleLinkList* next;
}DBLST;

DataType的定义是为了方便修改数据类型,这样定义一下,下次变char类型就实现了全局修改,awesome~

结构体中有三个成员分别用于数据存储,当前节点的前一个节点地址记录和当前节点的下一个节点的地址记录。

1. createDBLSTNode

首先最基本的函数就是专用于新节点创建,因为这个函数后续需要频繁使用

DBLST* createDBLSTNode(DataType x){
    DBLST* newNode = (DBLST*)malloc(sizeof(DBLST));
    if(newNode == NULL){
        printf("Failed to create a new node");
        exit(-1);
    }
    newNode->pre = NULL;
    newNode->next = NULL;
    newNode->data = x;
    return newNode;
}

2. doubleListInit

这个初始化和前面的顺序表,单链表不太一样
我们先创建一个节点,将新节点的地址传给原来的head,然后将此节点的pre和next都指向自己,就完成了双链表的创建

void doubleListInit(DBLST** head){
    *head = createDBLSTNode(-1);
    (*head)->pre = *head;
    (*head)->next = *head;
}

⚠️ 注意:这里参数传的是二级指针
因为需要改变外面指针的地址,debug来看一下

一开始head的地址为0x7ffeefbff4c8
创建的新地址为0x100688100


函数跑完出来后,可以看到原先传进去的地址现在变成了0x100688100,同时可以看到pre和next的地址都指向自己

3. doubleListPrint

打印节点数据需要注意的是,这是一个循环链表,如果没有判断停止的条件,那么就会导致死循环

所以我们需要判断一下,当cur转一圈回来等于头节点时,停止打印

void doubleListPrint(DBLST* head){
    assert(head);
    DBLST* cur = head->next;
    while(cur != head){
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\\n");
}

4. doubleListDestory

这里要注意如果我们直接free掉当前节点,那么下一个节点就找不到了,因此我们要先将下一个节点next进行存储,然后再释放当前节点,转一圈回来后就只剩一个head了,将其释放置空。

//释放链表中的所有节点
void doubleListDestory(DBLST* head){    
    DBLST* cur = head->next;
    while(cur!=head){
        DBLST* next = head->next;
        free(cur);
        cur->next = NULL;
        cur = next;
    }
    free(head);
    head = NULL;
}

5. doubleListEmpty

判断链表是否为空,当cur的下一个还是等于自己返回true

bool doubleListEmpty(DBLST* head){
    assert(head);
    return head == head->next;
}

6. doubleListSize

//计算节点个数
size_t doubleListSize(DBLST* head){
    assert(head);
    size_t count = 0;
    DBLST* tail = head;
    while(tail->next != head){
        count++;
        tail = tail->next;
    }
    return count;
}

7. doubleListPushBack

前面讲到实现尾插,时间复杂度为O(1),那么这里我们就先存储tail,然后创建一个新的节点,并与tail和头节点建立链接即可

void doubleListPushBack(DBLST* head, DataType x){
    assert(head);
    
    //首先,找到尾节点
    DBLST* tail = head->pre;
    
    DBLST* newNode = createDBLSTNode(x);
    
    tail->next = newNode;
    newNode->pre = tail;
    newNode->next = head;
    head->pre = newNode;
}

8. doubleListPushFront

和pushback一样,先将哨兵的下一个节点记录下来,然后创建新节点将其链接

void doubleListPushFront(DBLST* head, DataType x){
    assert(head);
    
    //先记录next node信息
    DBLST* next = head->next;

    //创建新节点
    DBLST* newNode = createDBLSTNode(x);

    head->next = newNode;
    newNode->pre = head;
    newNode->next = next;
    next->pre = newNode;
}

9. doubleListInsert

其实我们发现,如果一个函数可以专门用来创建新节点并和前后建立链接那么pushBack和pushFront的操作就可以免去很多步骤

//在pos前去s插入
void doubleListInsert(DBLST* pos, DataType x){
    assert(pos);
    
    DBLST* newNode = createDBLSTNode(x);
    DBLST* posPre = pos->pre;
    
    posPre->next = newNode;
    newNode->pre = posPre;
    newNode->next = pos;
    pos->pre = newNode;
}

我们的pushBack和pushFront就可以这么写

void doubleListPushFront(DBLST* head, DataType x){
    assert(head);
    doubleListInsert(head->next, x);
}
void doubleListPushBack(DBLST* head, DataType x){
    assert(head);
    doubleListInsert(head, x);
}

10. doubleListErase

删除front和back的思想也可以创建一个函数来实现

void doubleListErase(DBLST* pos){
    assert(pos);
    DBLST* pre = pos->pre;
    DBLST* next = pos->next;
    
    free(pos);
    pre->next = next;
    next->pre = pre;
}

11. doubleListPopBack

这里我们直接调用erase函数就可以了
⚠️ 这里要判断一下当前节点是否为空

void doubleListPopBack(DBLST* head){
    assert(head);
    assert(!doubleListEmpty(head));
    doubleListErase(head->next);
}

12. doubleListPopFront

void doubleListPopFront(DBLST* head){
    assert(head);
    assert(!doubleListEmpty(head));
    doubleListErase(head->pre);
}

13. doubleListFind

找特定的值,如果找不到返回NULL

DBLST* doubleListFind(DBLST* head, DataType x){
    DBLST* cur = head->next;
    while(cur != head){
        if(cur->data == x){
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

四、完整代码

下面是博主跑的测试用例

//
//  test.c
//  DoubleList_SecondTime
//
//  Created by Henry on 2021/8/19.
//  Copyright © 2021 Henry. All rights reserved.
//

#include "doubleList.h"

void TestFunc1(){
    DBLST* list;
    doubleListInit(&list);
    
    doubleListPushBack(list, 2);
    doubleListPushBack(list, 4);
    doubleListPushBack(list, 6);
    doubleListPushBack(list, 8);
    
    doubleListPushFront(list, 200);
    doubleListPushFront(list, 100);
    
    doubleListPrint(list);
    
    doubleListPopBack(list);
    doubleListPrint(list);
    
    doubleListPopBack(list);
    doubleListPrint(list);
    
    doubleListPopFront(list);
    doubleListPrint(list);
    
    //在找到这个值,在他前面插入
    DBLST* pos =  doubleListFind(list, 6);
    if(pos){
        doubleListInsert(pos, 60);
    }else{
        printf("Cannot find this element");
    }
    doubleListPrint(list);
    
    doubleListDestory(list);
}

int main(int argc, const char * argv[]) {
    TestFunc1();
    
    return 0;
}

Gitee链接🔗 🔗 🔗

👉 👉 👉 DoubleLinkList Folder👈 👈 👈

创作不易,如果文章对你帮助的话,点赞三连哦:)

以上是关于双链表(DoubleLinkList)数据结构的基本操作实现详解,重磅来袭!结构复杂但高效~的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之双链表

数据结构之双链表

双链表的结构和插入节点

数据结构-编程实现一个双链表的建立,双链表的打印,双链表的测长

(王道408考研数据结构)第二章线性表-第三节2:双链表的定义及其操作(插入和删除)

数据结构--线性表的链式存储之循环双链表