C语言实例_双向链表设计(队列)

举报
DS小龙哥 发表于 2024/08/08 09:14:33 2024/08/08
【摘要】 双向链表是一种线性数据结构,由一系列节点组成,每个节点包含三个主要部分:数据、一个指向前一个节点的前向指针和一个指向后一个节点的后向指针。这种结构可以在两个方向上遍历列表,而不仅仅是单向。

一、前言

1.1 双向链表结构

双向链表是一种线性数据结构,由一系列节点组成,每个节点包含三个主要部分:数据、一个指向前一个节点的前向指针和一个指向后一个节点的后向指针。这种结构可以在两个方向上遍历列表,而不仅仅是单向。

在双向链表中,第一个节点的前向指针通常为null(表示没有前一个节点),最后一个节点的后向指针也通常为null(表示没有后一个节点)。除了这些端点,每个中间节点都与其前一个和后一个节点相连。

双向链表的主要优点是可以在两个方向上进行操作,这在某些情况下可以提高效率。例如,在需要频繁地在列表的任意位置插入或删除元素的情况下,双向链表比单向链表更有效,因为可以从当前节点向前或向后移动,而不需要从头开始或从尾开始遍历整个列表。

双向链表的基本操作包括:插入节点(在头部、尾部或指定位置)、删除节点、查找节点、遍历列表等。这些操作的具体实现依赖于具体的编程语言和数据结构库。

双向链表是一种更复杂的链表结构,其中每个节点不仅包含数据元素,还包含指向前一个节点和后一个节点的指针。这种结构使得双向链表在数据插入和删除时具有更高的灵活性。

1.2 链表结构与数组结构

数组结构是一种连续的内存分配方式,允许通过索引快速访问任意位置的元素。数组的优点在于其随机访问性能优异,即给定索引,可以立即计算出元素的物理地址,实现常数时间的访问。数组在内存中是连续存储的,因此遍历数组也非常高效。数组的缺点在于其大小固定,一旦在声明时指定了数组的长度,就不能在运行时动态改变。如果需要存储的数据量超过了数组的长度,就必须重新分配一个更大的数组,并将原数组中的数据复制到新数组中,这会消耗大量的时间和内存。

相比之下,链表结构是一种非连续的内存分配方式,通过节点(Node)来存储数据,每个节点包含数据部分和指向下一个节点的指针(或引用)。链表的优点在于其动态性,即可以在运行时动态地添加或删除节点,而不需要重新分配整个数据结构。这使得链表在处理不确定大小的数据集时非常有用。链表可以轻松地实现插入和删除操作,因为只需要修改相邻节点的指针即可,而不需要移动其他元素。链表的缺点在于其随机访问性能较差,因为需要从头节点开始逐个遍历链表来查找元素,这会导致时间复杂度增加。

在实际的使用中,选择哪种结构取决于具体的应用场景。如果需要频繁地进行随机访问和遍历操作,且数据量相对固定,那么数组可能是一个更好的选择。而如果数据量不确定,或者需要频繁地进行插入和删除操作,那么链表可能更为合适。

1.3 双向链表的使用场景

(1)文件系统管理:在文件系统中,双向链表可以用来维护目录和文件的链接。例如,每个目录项可以是一个节点,其中包含文件名、文件属性和指向前一个和下一个目录项的指针。这样,系统就可以快速地在目录项之间前后移动,而无需重新扫描整个目录。

(2)浏览器的历史记录:浏览器使用双向链表来存储历史记录,使得用户可以方便地通过“前进”和“后退”按钮在网页之间切换。每个网页都是一个节点,其中包含网页的URL和其他相关信息。当用户访问新的页面时,新页面将被添加到链表的末尾,并成为当前节点。

(3)缓存机制:双向链表在实现LRU(最近最少使用)缓存策略中非常有用。当缓存空间满时,可以快速找到并移除最久未使用的项目,同时保持对最近使用项目的快速访问。

(4)音乐播放器的播放列表:双向链表可以用来存储播放列表,允许用户轻松地在歌曲之间前后跳转,而无需重新加载整个列表。

(5)多任务操作系统:在操作系统中,双向链表可以用来管理进程调度。每个进程都是一个节点,系统可以根据需要在进程之间快速切换。

(6)数据库管理系统:在数据库中,双向链表可以用于索引结构,以提高查询效率。例如,B树是一种使用双向链表的常见索引结构。

(7)图形用户界面:在GUI编程中,双向链表可以用来管理窗口堆栈,使得应用程序可以轻松地在窗口之间切换。

(8)游戏开发:在游戏开发中,双向链表可以用来管理游戏对象,如敌人、子弹等,以便于快速添加和删除对象。

一句话: 双向链表适用于任何需要在元素之间高效前后移动的场景。

1.4 双向链表和队列结构什么关系?

链表和队列之间的关系在于,链表是可以作为实现队列的一种数据结构,一种实现方式。

从概念上来区分,它们之间既有联系又有区别。链表是一种动态数据结构,通过节点之间的链接关系来存储数据。每个节点包含数据域和指针域,数据域用于存储数据,而指针域则指向下一个节点,形成一条链。这种结构使得数据的插入和删除操作非常灵活。队列则是一种特殊的线性表,它遵循“先进先出”的原则。在队列中,新元素总是被添加到队尾,而读取或删除操作总是从队头开始。

链表和队列之间的关系在于,链表可以作为实现队列的一种数据结构。特别是双向链表,由于其灵活的插入和删除特性,非常适合用来实现队列。在双向链表中,可以轻松地在链表尾部添加新元素(入队操作),以及从链表头部移除元素(出队操作),从而满足队列的FIFO特性。因此,可以说链表是队列实现的一种基础数据结构,而队列则是基于链表(或其他线性结构)实现的一种特殊数据结构,专门用于处理需要按照先进先出顺序处理数据的情况。简而言之,链表提供了实现队列所需的灵活性和动态性,而队列则是在链表基础上定义了一种特定的数据操作规则。

二、代码实现

2.1 C语言实现一个双向链表结构

下面是一个使用C语言实现的双向链表的示例。这个示例包含了双向链表的基本操作,如初始化、插入、删除、打印以及销毁链表。


c
#include <stdio.h>
#include <stdlib.h>

// 定义双向链表的节点结构体
typedef struct Node {
    int data;           // 节点数据
    struct Node *prev;  // 指向前一个节点的指针
    struct Node *next;  // 指向后一个节点的指针
} Node;

// 初始化双向链表
Node* initList() {
    return NULL;
}

// 在链表末尾插入一个新节点
void insertAtEnd(Node **head, int data) {
    Node *newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    newNode->next = NULL;
    
    if (*head == NULL) {
        newNode->prev = NULL;
        *head = newNode;
        return;
    }

    Node *temp = *head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
    newNode->prev = temp;
}

// 删除链表中的一个节点
void deleteNode(Node **head, Node *node) {
    if (*head == NULL || node == NULL) {
        return;
    }
    
    if (*head == node) {
        *head = node->next;
    }
    
    if (node->next != NULL) {
        node->next->prev = node->prev;
    }
    
    if (node->prev != NULL) {
        node->prev->next = node->next;
    }
    
    free(node);
}

// 打印链表的所有元素
void printList(Node *head) {
    Node *temp = head;
    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}

// 销毁链表
void destroyList(Node **head) {
    Node *current = *head;
    Node *next;
    while (current != NULL) {
        next = current->next;
        free(current);
        current = next;
    }
    *head = NULL;
}

int main() {
    Node *head = initList();
    
    insertAtEnd(&head, 1);
    insertAtEnd(&head, 2);
    insertAtEnd(&head, 3);
    
    printList(head);  // 输出: 1 2 3
    
    Node *nodeToDelete = head->next;  // 获取要删除的节点
    deleteNode(&head, nodeToDelete);  // 删除节点
    
    printList(head);  // 输出: 1 3
    
    destroyList(&head);  // 销毁链表
    
    return 0;
}

代码解释:

(1)struct Node 定义了一个双向链表的节点结构,包含数据域 data 和两个指针域 prev  next

(2)initList 函数用于初始化链表,返回一个空链表。

(3)insertAtEnd 函数用于在链表的末尾插入一个新节点。

(4)deleteNode 函数用于删除链表中的一个指定节点。

(5)printList 函数用于打印链表的所有元素。

(6)destroyList 函数用于销毁整个链表,释放所有节点的内存。

(7)main 函数中创建了链表,插入了几个元素,打印了链表,删除了一个节点,再次打印了链表,最后销毁了链表。

2.2 C语言实现一个队列结构

在C语言中,队列可以通过链表或者数组来实现。这个章节展示如何使用链表来实现一个队列,因为链表在插入和删除操作上通常比数组更有效率,尤其是在队列的前端进行操作时。

下面是使用链表实现队列的完整代码:


c
#include <stdio.h>
#include <stdlib.h>

// 定义队列的节点结构
typedef struct QueueNode {
    int data;
    struct QueueNode *next;
} QueueNode;

// 定义队列结构
typedef struct Queue {
    QueueNode *front;
    QueueNode *rear;
} Queue;

// 初始化队列
Queue* initQueue() {
    Queue *q = (Queue *)malloc(sizeof(Queue));
    q->front = q->rear = NULL;
    return q;
}

// 检查队列是否为空
int isEmpty(Queue *q) {
    return q->front == NULL;
}

// 向队列中添加元素(入队)
void enqueue(Queue *q, int data) {
    QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
    if (newNode == NULL) {
        printf("Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    newNode->next = NULL;
    
    if (isEmpty(q)) {
        q->front = q->rear = newNode;
    } else {
        q->rear->next = newNode;
        q->rear = newNode;
    }
}

// 从队列中删除元素(出队)
int dequeue(Queue *q) {
    if (isEmpty(q)) {
        printf("Queue is empty\n");
        return -1;
    }
    
    QueueNode *temp = q->front;
    int data = temp->data;
    
    q->front = q->front->next;
    
    if (q->front == NULL) {
        q->rear = NULL;
    }
    
    free(temp);
    return data;
}

// 打印队列中的所有元素
void printQueue(Queue *q) {
    QueueNode *temp = q->front;
    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}

int main() {
    Queue *q = initQueue();
    
    enqueue(q, 1);
    enqueue(q, 2);
    enqueue(q, 3);
    
    printQueue(q);  // 输出: 1 2 3
    
    printf("Dequeued element: %d\n", dequeue(q));  // 输出: Dequeued element: 1
    
    printQueue(q);  // 输出: 2 3
    
    return 0;
}

代码解释:

(1)定义队列节点结构QueueNode 结构体包含数据 data 和指向下一个节点的指针 next

(2)定义队列结构Queue 结构体包含两个指针 front  rear,分别指向队列的前端和后端。

(3)初始化队列initQueue 函数创建一个新的队列,并将其前端和后端指针设置为 NULL

(4)检查队列是否为空isEmpty 函数检查队列是否为空。

(5)入队操作enqueue 函数在队列的后端添加一个新元素。

(6)出队操作dequeue 函数从队列的前端删除一个元素。

(7)打印队列printQueue 函数打印队列中的所有元素。

(8)主函数main 函数中创建了一个队列,进行了几次入队和出队操作,并打印了队列的状态。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。