数据结构是计算机科学中的核心概念,它为程序提供了高效的数据管理和操作方式。在C语言中,链表、栈和队列是最常用的数据结构之一。本文将通过讲故事的方式,带领大家深入理解这些数据结构的实现与应用,并通过实例帮助读者巩固知识。


一、链表:灵活的数据存储结构

1. 链表的基本概念

链表是一种动态的数据结构,由一系列节点(Node)组成。每个节点包含两个部分:数据域(Data)和指针域(Pointer)。数据域用于存储实际的数据,指针域用于指向下一个节点。

示例验证:链表的实现

// 包含标准输入输出库,用于printf等函数
#include <stdio.h>
// 包含标准库函数,用于malloc动态内存分配
#include <stdlib.h>

// 定义链表节点结构体
struct Node {
    int data;            // 节点存储的数据
    struct Node* next;   // 指向下一个节点的指针
};

// 链表插入函数(头插法)
// 参数:head 链表头指针的地址(二级指针),data 要插入的数据
void insert(struct Node** head, int data) {
    // 动态分配内存创建新节点
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;   // 设置新节点的数据域
    newNode->next = *head;  // 新节点指向原头节点
    *head = newNode;        // 更新头指针指向新节点
}

// 链表遍历显示函数
// 参数:head 链表头指针
void display(struct Node* head) {
    struct Node* ptr = head; // 创建遍历指针
    while (ptr != NULL) {    // 遍历直到链表末尾
        printf("%d -> ", ptr->data);  // 打印当前节点数据
        ptr = ptr->next;     // 移动指针到下一个节点
    }
    printf("NULL\n");        // 打印链表结束标记
}

// 主函数
int main() {
    struct Node* head = NULL; // 初始化链表头指针为空

    // 依次插入3个节点(头插法形成倒序)
    insert(&head, 3);  // 插入数据3,链表:3 -> NULL
    insert(&head, 2);  // 插入数据2,链表:2 -> 3 -> NULL
    insert(&head, 1);  // 插入数据1,链表:1 -> 2 -> 3 -> NULL

    printf("链表内容: ");
    display(head);     // 显示链表内容

    return 0;          // 程序正常退出
}

 

问题验证:

  1. 链表的基本组成是什么?
  2. 如何实现链表的插入和删除操作?

二、栈:先进后出的数据结构

1. 栈的基本概念

栈(Stack)是一种先进后出(LIFO)的数据结构,支持两种主要操作:入栈(Push)和出栈(Pop)。栈通常用于实现函数调用、括号匹配等场景。

示例验证:栈的实现

// 包含标准输入输出库,用于printf等函数
#include <stdio.h>
// 包含标准库函数,用于malloc动态内存分配和free内存释放
#include <stdlib.h>

// 定义栈节点结构体
struct Stack {
    int data;            // 节点存储的数据
    struct Stack* next;  // 指向下一节点的指针(栈顶方向)
};

// 压栈操作函数
// 参数:top 栈顶指针的地址(二级指针),data 要压入的数据
void push(struct Stack** top, int data) {
    // 动态分配内存创建新节点
    struct Stack* newNode = (struct Stack*)malloc(sizeof(struct Stack));
    newNode->data = data;  // 设置新节点的数据域
    newNode->next = *top;  // 新节点指向原栈顶节点(链表头插法)
    *top = newNode;        // 更新栈顶指针指向新节点
}

// 弹栈操作函数
// 参数:top 栈顶指针的地址(二级指针)
// 返回值:弹出的数据(栈空时返回-1)
int pop(struct Stack** top) {
    if (*top == NULL) {  // 检查栈是否为空
        printf("栈为空\n");
        return -1;       // 返回-1表示错误状态
    }
    struct Stack* temp = *top;   // 临时指针指向当前栈顶
    int poppedData = temp->data; // 保存要弹出的数据
    *top = temp->next;           // 更新栈顶指针到下一个节点
    free(temp);                  // 释放原栈顶节点的内存
    return poppedData;           // 返回弹出的数据
}

// 栈遍历显示函数
// 参数:top 栈顶指针
void display(struct Stack* top) {
    struct Stack* ptr = top; // 创建遍历指针
    while (ptr != NULL) {    // 遍历直到栈底
        printf("%d -> ", ptr->data);  // 打印当前节点数据
        ptr = ptr->next;     // 移动指针到下一节点
    }
    printf("NULL\n");        // 打印栈结束标记
}

// 主函数
int main() {
    struct Stack* top = NULL; // 初始化栈顶指针为空

    // 依次压入3个数据(后进先出结构)
    push(&top, 1); // 栈状态:1 -> NULL
    push(&top, 2); // 栈状态:2 -> 1 -> NULL
    push(&top, 3); // 栈状态:3 -> 2 -> 1 -> NULL

    printf("栈内容: ");
    display(top); // 显示当前栈内容

    // 弹出栈顶元素并显示
    printf("出栈元素: %d\n", pop(&top)); // 弹出3,栈状态变为2 -> 1 -> NULL
    printf("栈内容(出栈后): ");
    display(top); // 显示弹出后的栈内容

    return 0; // 程序正常退出
}

 

问题验证: 

  1. 栈的特点是什么?
  2. 如何实现栈的入栈和出栈操作?

三、队列:先进先出的数据结构

1. 队列的基本概念

队列(Queue)是一种先进先出(FIFO)的数据结构,支持两种主要操作:入队(Enqueue)和出队(Dequeue)。队列通常用于实现任务调度、消息队列等场景。

示例验证:队列的实现

 

// 包含标准输入输出库,用于printf等函数
#include <stdio.h>
// 包含标准库函数,用于malloc动态内存分配和free内存释放
#include <stdlib.h>

// 定义队列节点结构体
struct Queue {
    int data;             // 节点存储的数据
    struct Queue* next;   // 指向下一个节点的指针
};

// 入队操作函数
// 参数:front 队头指针的地址(二级指针),rear 队尾指针的地址(二级指针),data 要入队的数据
void enqueue(struct Queue** front, struct Queue** rear, int data) {
    // 动态分配内存创建新节点
    struct Queue* newNode = (struct Queue*)malloc(sizeof(struct Queue));
    newNode->data = data;  // 设置新节点的数据域
    newNode->next = NULL;  // 新节点作为队尾,next指针置空

    if (*front == NULL) {  // 如果队列为空(首次插入)
        *front = newNode;  // 队头和队尾都指向新节点
        *rear = newNode;
    } else {               // 如果队列非空
        (*rear)->next = newNode;  // 原队尾节点指向新节点
        *rear = newNode;          // 更新队尾指针为新节点
    }
}

// 出队操作函数
// 参数:front 队头指针的地址(二级指针),rear 队尾指针的地址(二级指针)
// 返回值:出队的数据(队列空时返回-1)
int dequeue(struct Queue** front, struct Queue** rear) {
    if (*front == NULL) {  // 检查队列是否为空
        printf("队列为空\n");
        return -1;         // 返回-1表示错误状态
    }
    struct Queue* temp = *front;    // 临时指针指向当前队头
    int dequeuedData = temp->data;  // 保存要出队的数据
    *front = temp->next;            // 更新队头指针到下一个节点
    
    if (*front == NULL) {  // 如果出队后队列变为空
        *rear = NULL;      // 同时更新队尾指针为空
    }
    
    free(temp);            // 释放原队头节点的内存
    return dequeuedData;   // 返回出队的数据
}

// 队列遍历显示函数
// 参数:front 队头指针
void display(struct Queue* front) {
    struct Queue* ptr = front; // 创建遍历指针
    while (ptr != NULL) {      // 遍历直到队尾
        printf("%d -> ", ptr->data);  // 打印当前节点数据
        ptr = ptr->next;       // 移动指针到下一个节点
    }
    printf("NULL\n");          // 打印队列结束标记
}

// 主函数
int main() {
    struct Queue* front = NULL; // 初始化队头指针为空
    struct Queue* rear = NULL;  // 初始化队尾指针为空

    // 依次入队3个数据(先进先出结构)
    enqueue(&front, &rear, 1); // 队状态:1 -> NULL
    enqueue(&front, &rear, 2); // 队状态:1 -> 2 -> NULL
    enqueue(&front, &rear, 3); // 队状态:1 -> 2 -> 3 -> NULL

    printf("队列内容: ");
    display(front); // 显示当前队列内容

    // 出队队头元素并显示
    printf("出队元素: %d\n", dequeue(&front, &rear)); // 出队1,队状态变为2 -> 3 -> NULL
    printf("队列内容(出队后): ");
    display(front); // 显示出队后的队列内容

    return 0; // 程序正常退出
}

问题验证:

  1. 队列的特点是什么?
  2. 如何实现队列的入队和出队操作?

四、链表、栈、队列的比较与应用
数据结构特点适用场景
链表动态存储,灵活插入和删除实现动态数据管理(如联系人管理)
栈先进后出,适合回溯和撤销操作实现函数调用、括号匹配
队列先进先出,适合任务调度实现任务队列、消息队列

示例验证:链表、栈、队列的综合应用

// 包含标准输入输出库,用于printf等函数
#include <stdio.h>
// 包含标准库函数,用于动态内存分配和释放
#include <stdlib.h>

/******************** 链表实现部分 ********************/
// 定义链表节点结构体
struct Node {
    int data;            // 节点存储的数据
    struct Node* next;   // 指向下一个节点的指针
};

// 链表头插法插入函数
// 参数:head 链表头指针的地址(二级指针),data 要插入的数据
void insert(struct Node** head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // 创建新节点
    newNode->data = data;   // 设置新节点的数据域
    newNode->next = *head;  // 新节点指向原头节点(头插法)
    *head = newNode;        // 更新头指针指向新节点
}

// 链表遍历显示函数
// 参数:head 链表头指针
void display(struct Node* head) {
    struct Node* ptr = head; // 创建遍历指针
    while (ptr != NULL) {    // 遍历直到链表末尾
        printf("%d -> ", ptr->data);  // 打印当前节点数据
        ptr = ptr->next;     // 移动指针到下一个节点
    }
    printf("NULL\n");        // 打印链表结束标记
}

/******************** 栈实现部分 ********************/
// 定义栈节点结构体
struct Stack {
    int data;             // 节点存储的数据
    struct Stack* next;   // 指向下一节点的指针(栈顶方向)
};

// 压栈操作函数
// 参数:top 栈顶指针的地址(二级指针),data 要压入的数据
void push(struct Stack** top, int data) {
    struct Stack* newNode = (struct Stack*)malloc(sizeof(struct Stack)); // 创建新节点
    newNode->data = data;  // 设置新节点的数据域
    newNode->next = *top;  // 新节点指向原栈顶节点(LIFO结构)
    *top = newNode;        // 更新栈顶指针指向新节点
}

// 弹栈操作函数
// 参数:top 栈顶指针的地址(二级指针)
// 返回值:弹出的数据(栈空时返回-1)
int pop(struct Stack** top) {
    if (*top == NULL) {   // 检查栈是否为空
        printf("栈为空\n");
        return -1;        // 返回错误代码
    }
    struct Stack* temp = *top;    // 临时指针指向当前栈顶
    int poppedData = temp->data;  // 保存要弹出的数据
    *top = temp->next;            // 更新栈顶指针到下一节点
    free(temp);                   // 释放原栈顶节点内存
    return poppedData;            // 返回弹出的数据
}

/******************** 队列实现部分 ********************/
// 定义队列节点结构体
struct Queue {
    int data;             // 节点存储的数据
    struct Queue* next;   // 指向下一个节点的指针
};

// 入队操作函数
// 参数:front 队头指针的地址,rear 队尾指针的地址,data 要入队的数据
void enqueue(struct Queue** front, struct Queue** rear, int data) {
    struct Queue* newNode = (struct Queue*)malloc(sizeof(struct Queue)); // 创建新节点
    newNode->data = data;  // 设置新节点的数据域
    newNode->next = NULL;  // 新节点作为队尾,next指针置空

    if (*front == NULL) {  // 如果队列为空
        *front = newNode;  // 队头和队尾都指向新节点
        *rear = newNode;
    } else {               // 如果队列非空
        (*rear)->next = newNode;  // 原队尾节点指向新节点
        *rear = newNode;          // 更新队尾指针
    }
}

// 出队操作函数
// 参数:front 队头指针的地址,rear 队尾指针的地址
// 返回值:出队的数据(队列空时返回-1)
int dequeue(struct Queue** front, struct Queue** rear) {
    if (*front == NULL) {  // 检查队列是否为空
        printf("队列为空\n");
        return -1;         // 返回错误代码
    }
    struct Queue* temp = *front;    // 临时指针指向当前队头
    int dequeuedData = temp->data;  // 保存要出队的数据
    *front = temp->next;            // 更新队头指针到下一节点
    
    if (*front == NULL) {  // 如果出队后队列变空
        *rear = NULL;      // 同时更新队尾指针为空
    }
    
    free(temp);            // 释放原队头节点内存
    return dequeuedData;   // 返回出队的数据
}

/******************** 主函数 ********************/
int main() {
    // ======== 链表应用演示 ========
    struct Node* head = NULL;  // 初始化链表头指针
    insert(&head, 1);          // 插入数据1(链表:1 -> NULL)
    insert(&head, 2);          // 插入数据2(链表:2 -> 1 -> NULL)
    insert(&head, 3);          // 插入数据3(链表:3 -> 2 -> 1 -> NULL)
    printf("链表内容: ");
    display(head);             // 显示链表内容

    // ======== 栈应用演示 ========
    struct Stack* stackTop = NULL;  // 初始化栈顶指针
    push(&stackTop, 10);       // 压入10(栈:10 -> NULL)
    push(&stackTop, 20);       // 压入20(栈:20 -> 10 -> NULL)
    push(&stackTop, 30);       // 压入30(栈:30 -> 20 -> 10 -> NULL)
    printf("栈内容: ");
    display(stackTop);         // 显示栈内容
    printf("出栈元素: %d\n", pop(&stackTop));  // 弹出30
    printf("栈内容(出栈后): ");
    display(stackTop);         // 显示弹出后的栈(20 -> 10 -> NULL)

    // ======== 队列应用演示 ========
    struct Queue* queueFront = NULL;  // 初始化队头指针
    struct Queue* queueRear = NULL;   // 初始化队尾指针
    enqueue(&queueFront, &queueRear, 100);  // 入队100(队列:100 -> NULL)
    enqueue(&queueFront, &queueRear, 200);  // 入队200(队列:100 -> 200 -> NULL)
    enqueue(&queueFront, &queueRear, 300);  // 入队300(队列:100 -> 200 -> 300 -> NULL)
    printf("队列内容: ");
    display(queueFront);              // 显示队列内容
    printf("出队元素: %d\n", dequeue(&queueFront, &queueRear));  // 出队100
    printf("队列内容(出队后): ");
    display(queueFront);              // 显示出队后的队列(200 -> 300 -> NULL)

    return 0;  // 程序正常退出
}

 

问题验证:

  1. 链表、栈和队列的主要区别是什么?
  2. 如何根据需求选择合适的数据结构?

五、总结与实践建议

链表、栈和队列是C语言中非常重要的数据结构,它们各自有不同的特点和适用场景。链表适用于动态数据管理,栈适用于先进后出的场景,队列适用于先进先出的场景。

实践建议:

  1. 多编写使用链表、栈和队列的程序,尤其是实际应用中的场景。
  2. 使用调试工具(如GDB)检测和修复数据结构相关的错误。
  3. 阅读和分析优秀的C语言代码,学习数据结构的高级用法。

希望这篇博客能够帮助你深入理解C语言中的链表、栈和队列,提升编程能力。如果你有任何问题或建议,欢迎在评论区留言!

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐