C语言中的数据结构:链表、栈、队列的实现与应用
·
数据结构是计算机科学中的核心概念,它为程序提供了高效的数据管理和操作方式。在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. 栈的基本概念
栈(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. 队列的基本概念
队列(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; // 程序正常退出
}
问题验证:
- 队列的特点是什么?
- 如何实现队列的入队和出队操作?
四、链表、栈、队列的比较与应用
| 数据结构 | 特点 | 适用场景 |
|---|---|---|
| 链表 | 动态存储,灵活插入和删除 | 实现动态数据管理(如联系人管理) |
| 栈 | 先进后出,适合回溯和撤销操作 | 实现函数调用、括号匹配 |
| 队列 | 先进先出,适合任务调度 | 实现任务队列、消息队列 |
示例验证:链表、栈、队列的综合应用
// 包含标准输入输出库,用于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; // 程序正常退出
}
问题验证:
- 链表、栈和队列的主要区别是什么?
- 如何根据需求选择合适的数据结构?
五、总结与实践建议
链表、栈和队列是C语言中非常重要的数据结构,它们各自有不同的特点和适用场景。链表适用于动态数据管理,栈适用于先进后出的场景,队列适用于先进先出的场景。
实践建议:
- 多编写使用链表、栈和队列的程序,尤其是实际应用中的场景。
- 使用调试工具(如GDB)检测和修复数据结构相关的错误。
- 阅读和分析优秀的C语言代码,学习数据结构的高级用法。
希望这篇博客能够帮助你深入理解C语言中的链表、栈和队列,提升编程能力。如果你有任何问题或建议,欢迎在评论区留言!
更多推荐
所有评论(0)