当前位置:首页>行业动态> 正文

C数据结构之双链表详细示例分析

定义节点结构体,“c,typedef struct Node {, int data;, struct Node* prev;, struct Node* next;,} Node;,` 初始化头节点,`c,Node* initList() {, Node* head = (Node*)malloc(sizeof(Node));, head->data = 0;, head->prev = NULL;, head->next = NULL;, return head;,},` 插入节点到头部,`c,void insertHead(Node* head, int value) {, Node* newNode = (Node*)malloc(sizeof(Node));, newNode->data = value;, newNode->prev = NULL;, newNode->next = head;, if (head != NULL) {, head->prev = newNode;, }, head = newNode;,},` 删除指定节点,`c,void deleteNode(Node* head, Node* node) {, if (node == NULL) return;, if (node->prev != NULL) {, node->prev->next = node->next;, } else {, head = node->next;, }, if (node->next != NULL) {, node->next->prev = node->prev;, }, free(node);,},` 遍历双链表,`c,void traverse(Node* head) {, Node* current = head;, while (current != NULL) {, printf("%d ", current->data);, current = current->next;, }, printf(",");,},` 示例使用,`c,int main() {, Node* head = initList();, insertHead(head, 1);, insertHead(head, 2);, insertHead(head, 3);, traverse(head); // 输出: 3 2 1 , deleteNode(head, head->next); // 删除值为2的节点, traverse(head); // 输出: 3 1 , return 0;,},

C 数据结构之双链表详细示例分析

在数据结构的学习中,双链表是一种非常重要的线性数据结构,它相较于单链表,在节点结构上增加了指向前驱节点的指针,这使得双链表在插入、删除等操作上具有更高的灵活性和效率,下面将通过详细的代码示例和分析,深入探讨 C 语言中双链表的实现与应用。

一、双链表节点结构定义

我们需要定义双链表的节点结构体,一个典型的双链表节点包含三个部分:数据域、指向前驱节点的指针(prev)和指向后继节点的指针(next),以下是在 C 语言中的节点结构定义示例:

typedef struct DLinkNode {
    int data;                // 数据域,存储节点的数据
    struct DLinkNode *prev;  // 指向前驱节点的指针
    struct DLinkNode *next;  // 指向后继节点的指针
} DLinkNode, *DLinkList;     // 定义节点类型和链表类型

在这个结构体中,data用于存储节点的实际数据,prevnext则分别用于连接前驱和后继节点,从而实现双向的链接关系。

二、双链表基本操作函数

(一)初始化双链表

初始化操作是创建一个空的双链表,即头节点的nextprev都指向自己,表示链表中暂时没有其他节点。

void InitDLinkList(DLinkList *L) {
    *L = (DLinkNode *)malloc(sizeof(DLinkNode));  // 为头节点分配内存空间
    if (*L == NULL) {
        printf("Memory allocation failed!
");
        exit(1);
    }
    (*L)->data = 0;  // 头节点数据域可根据实际情况设定,这里设为 0
    (*L)->prev = *L;
    (*L)->next = *L;
}

(二)插入节点

在双链表中插入节点可以分为在头部插入、在尾部插入和在指定位置插入等情况,这里以在头部插入为例进行说明。

void InsertHead(DLinkList L, int e) {
    DLinkNode *s = (DLinkNode *)malloc(sizeof(DLinkNode));  // 创建新节点
    if (s == NULL) {
        printf("Memory allocation failed!
");
        exit(1);
    }
    s->data = e;  // 设置新节点的数据
    s->next = L->next;  // 新节点的后继指向原头节点的后继
    s->prev = L;         // 新节点的前驱指向头节点
    L->next->prev = s;   // 原头节点的后继的前驱指向新节点
    L->next = s;         // 头节点的后继指向新节点
}

在这个过程中,我们首先为新节点分配内存并设置其数据,然后调整新节点与原有节点之间的指针关系,使其正确地插入到链表头部,插入操作完成后,链表的结构会相应地发生变化,新节点成为新的头节点的后继节点,而原头节点的后继节点的前驱指针也更新为新节点。

(三)删除节点

同样,删除节点也可以根据不同的需求在头部、尾部或指定位置进行删除,以下以删除指定值为e的节点为例:

int DeleteElem(DLinkList L, int e) {
    DLinkNode *p = L->next;  // 从头节点的后继开始遍历链表
    while (p != L && p->data != e) {
        p = p->next;
    }
    if (p == L) {
        return 0;  // 未找到值为 e 的节点
    }
    p->prev->next = p->next;  // 前驱节点的后继指向后继节点
    p->next->prev = p->prev;  // 后继节点的前驱指向前驱节点
    free(p);  // 释放被删除节点的内存空间
    return 1;  // 删除成功
}

在删除操作中,我们先遍历链表找到值为e的节点p,如果找到了,就调整其前驱和后继节点的指针,跳过该节点,然后释放节点的内存空间,如果遍历完整个链表都没有找到,则返回删除失败的标志。

三、示例程序

下面是一个完整的示例程序,展示了如何使用上述定义和操作函数来创建和操作一个双链表:

#include <stdio.h>
#include <stdlib.h>
typedef struct DLinkNode {
    int data;
    struct DLinkNode *prev;
    struct DLinkNode *next;
} DLinkNode, *DLinkList;
void InitDLinkList(DLinkList *L) {
    *L = (DLinkNode *)malloc(sizeof(DLinkNode));
    if (*L == NULL) {
        printf("Memory allocation failed!
");
        exit(1);
    }
    (*L)->data = 0;
    (*L)->prev = *L;
    (*L)->next = *L;
}
void InsertHead(DLinkList L, int e) {
    DLinkNode *s = (DLinkNode *)malloc(sizeof(DLinkNode));
    if (s == NULL) {
        printf("Memory allocation failed!
");
        exit(1);
    }
    s->data = e;
    s->next = L->next;
    s->prev = L;
    L->next->prev = s;
    L->next = s;
}
int DeleteElem(DLinkList L, int e) {
    DLinkNode *p = L->next;
    while (p != L && p->data != e) {
        p = p->next;
    }
    if (p == L) {
        return 0;
    }
    p->prev->next = p->next;
    p->next->prev = p->prev;
    free(p);
    return 1;
}
void PrintDLinkList(DLinkList L) {
    DLinkNode *p = L->next;
    while (p != L) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("
");
}
int main() {
    DLinkList L;
    InitDLinkList(&L);
    InsertHead(L, 1);
    InsertHead(L, 2);
    InsertHead(L, 3);
    PrintDLinkList(L);  // 输出:3 2 1
    DeleteElem(L, 2);
    PrintDLinkList(L);  // 输出:3 1
    return 0;
}

在这个示例程序中,我们首先初始化了一个空的双链表L,然后使用InsertHead函数在链表头部依次插入了元素 1、2、3,接着打印链表,可以看到元素按照插入的顺序排列,然后我们删除了值为 2 的节点,再次打印链表时,发现链表中只剩下了元素 3 和 1。

四、性能分析与应用场景

(一)性能分析

时间复杂度:在双链表中,插入和删除操作的时间复杂度平均为 O(1),因为不需要像单链表那样遍历链表来找到插入或删除的位置,查找操作的时间复杂度为 O(n),最坏情况下需要遍历整个链表才能找到目标元素,总体而言,双链表在插入和删除操作上具有较高的效率,但在查找操作上相对单链表没有明显优势。

空间复杂度:由于每个节点需要额外的指针来存储前驱和后继信息,所以双链表的空间复杂度相对较高,每个节点占用的内存空间比单链表多一些,这种额外的空间开销换来了操作上的便利性和高效性。

(二)应用场景

数据缓存:在一些操作系统或应用程序中,经常需要对数据进行缓存以提高访问速度,双链表可以方便地实现数据的插入和删除操作,例如在实现 LRU(最近最少使用)缓存算法时,可以使用双链表来维护缓存数据的访问顺序,快速地将最近访问的数据移动到链表头部,而将最久未访问的数据移动到链表尾部并最终删除。

图的邻接表表示:在图的数据结构中,可以使用双链表来实现图的邻接表表示,每个顶点对应一个链表,链表中的节点表示与该顶点相邻的其他顶点,通过双链表可以方便地遍历顶点的邻接顶点,进行图的遍历、搜索等操作。

任务调度系统:在一些实时系统中,任务调度是一个关键的功能,双链表可以用来维护任务队列,根据任务的优先级或其他属性将任务插入到合适的位置,并且能够快速地从队列中取出任务进行处理,保证系统的高效运行。

五、归纳

C 语言中的双链表是一种重要的数据结构,它具有双向链接的特点,使得插入、删除等操作更加灵活和高效,通过合理地定义节点结构和实现各种操作函数,我们可以方便地使用双链表来解决实际问题,在实际应用中,需要根据具体的需求和场景选择合适的数据结构,充分发挥双链表的优势,提高程序的性能和效率,在使用双链表时也需要注意内存管理等问题,避免出现内存泄漏等错误,希望本文对您理解和掌握 C 语言数据结构中的双链表有所帮助。

操作时间复杂度空间复杂度备注
初始化O(1)O(1)为头节点分配内存
插入(头部)O(1)O(1)创建新节点并调整指针
删除(指定值)O(n)O(1)遍历查找并调整指针
查找(指定值)O(n)O(1)遍历链表查找元素
打印O(n)O(1)遍历输出节点数据
插入(尾部)O(1)O(1)同头部插入类似
删除(头部)O(1)O(1)直接调整头节点指针
插入(指定位置)O(n)O(1)先遍历找到位置再插入
删除(指定位置)O(n)O(1)先遍历找到位置再删除
获取长度O(n)O(1)遍历统计节点个数
反转链表O(n)O(1)调整每个节点的前驱和后继指针
合并链表O(1)O(1)将两个链表的头尾相连
拆分链表O(n)O(1)根据条件遍历拆分节点
判断是否为空O(1)O(1)检查头节点指针
获取头节点数据O(1)O(1)直接访问头节点数据成员