数据结构基础
学习内容概述 今天我开始学习数据结构,重点理解了它在编程中的重要性。数据结构是为了高效访问数据而设计的一种数据组织和存储方式。它不仅仅关注数据的存储位置,还关注数据元素之间的关系。
计算机科学家尼古拉斯·沃斯提出了著名的公式:
算法 + 数据结构 = 程序
这说明数据结构与算法是程序设计的核心。数据结构就像战场上的排兵布阵,设计良好的数据结构能让我们在处理问题时事半功倍。
内存的理解 数据结构的基础是对内存的理解。内存由许多存储单元组成,每个单元都有唯一的地址。数据可以保存在连续的内存单元中,也可以保存在分散的单元中。选择哪种方式,取决于我们想如何组织和操作这些数据。
数据结构的逻辑结构 数据的逻辑结构主要描述数据元素之间的逻辑关系,可以分为以下几类:
-
集合结构:数据元素之间只有属于同一集合的关系。
-
线性结构:数据元素之间存在一对一的关系,比如数组、链表等。
-
树形结构:数据元素之间存在一对多的关系,比如家谱、文件系统。
-
图形结构:数据元素之间存在多对多的关系,比如社交网络。
数据的存储结构 数据的存储结构是逻辑结构在计算机中的实现,可以分为:
-
顺序存储结构:相邻的数据元素在内存中也相邻,比如数组。
-
链式存储结构:相邻的数据元素可以在内存中不相邻,用指针链接,比如链表。
-
索引存储结构:在数据存储之外,有一个索引目录,比如数据库的索引。
-
散列存储结构:通过计算关键字来确定数据存储地址,比如散列表。
线性结构之数组
学习内容概述 在C语言中,数组是具有相同类型数据元素的集合。数组的特点是访问速度快,因为可以通过下标直接访问指定位置的元素。今天我学到了如何用C语言实现数组的基础操作。
代码示例:数组的定义与操作
#include <stdio.h> // 引入标准输入输出头文件
int main() { // 主函数入口
int arr[5] = {10, 20, 30, 40, 50}; // 定义一个包含5个整数的数组
// 遍历数组
for (int i = 0; i < 5; i++) { // 使用循环遍历数组的每个元素
printf("arr[%d] = %d\n", i, arr[i]); // 输出当前元素的值
}
return 0; // 返回0,表示程序正常结束
}
通俗理解 数组就像是一排连续的储物柜,每个储物柜都有一个编号(下标),你可以通过编号快速找到需要的物品(数据)。数组的长度一旦确定就不能改变,这就好比一排储物柜数量固定了,不能再增加新的储物柜。
线性结构之链表
学习内容概述 链表是由一系列结点组成的线性结构,每个结点包含一个数据域和一个指针域。链表的优点是可以动态扩展,不需要预先指定大小,适合频繁插入和删除的场景。
代码示例:链表的定义与操作
#include <stdio.h> // 引入标准输入输出头文件
#include <stdlib.h> // 引入标准库头文件,用于动态内存分配
// 定义节点结构体
typedef struct Node {
int data; // 数据域,存储节点的数据
struct Node* next; // 指针域,指向下一个节点
} Node;
// 插入节点到链表头部
void insert(Node** head, int data) {
Node* new_node = (Node*)malloc(sizeof(Node)); // 动态分配内存给新节点
new_node->data = data; // 设置新节点的数据域
new_node->next = *head; // 将新节点的next指向当前的头节点
*head = new_node; // 更新头节点指针为新节点
}
// 打印链表
void printList(Node* head) {
Node* current = head; // 定义当前节点指针,初始为头节点
while (current != NULL) { // 遍历链表直到当前节点为空
printf("%d -> ", current->data); // 打印当前节点的数据
current = current->next; // 移动到下一个节点
}
printf("NULL\n"); // 打印链表结束标志
}
// 释放链表内存
void freeList(Node* head) {
Node* current = head; // 定义当前节点指针
Node* next_node; // 定义下一个节点指针
while (current != NULL) { // 遍历链表直到当前节点为空
next_node = current->next; // 保存下一个节点的位置
free(current); // 释放当前节点的内存
current = next_node; // 移动到下一个节点
}
}
int main() {
Node* head = NULL; // 初始化链表为空
insert(&head, 10); // 插入数据10到链表头部
insert(&head, 20); // 插入数据20到链表头部
insert(&head, 30); // 插入数据30到链表头部
printList(head); // 打印链表内容
freeList(head); // 释放链表内存,防止内存泄漏
return 0; // 返回0,表示程序正常结束
}
通俗理解 链表就像是一串珍珠项链,每颗珍珠(节点)都有一根线(指针)连接到下一颗珍珠。你可以随时在项链中加入或取出一颗珍珠,而不需要重新排列所有珍珠,因此链表非常适合需要频繁添加或删除数据的场景。
线性结构之栈
学习内容概述 今天我还学习了栈这一数据结构。栈是一种只能在表的一端进行插入和删除操作的线性表,其特点是后进先出(LIFO)。栈的应用非常广泛,比如函数调用栈、表达式求值等。
代码示例:栈的实现(基于数组)
#include <stdio.h> // 引入标准输入输出头文件
#include <stdlib.h> // 引入标准库头文件
#define MAX 100 // 定义栈的最大容量为100
typedef struct {
int data[MAX]; // 用数组表示栈的数据存储
int top; // 栈顶指针,表示栈中数据的位置
} Stack;
// 初始化栈
void init(Stack* stack) {
stack->top = -1; // 初始化栈顶指针为-1,表示栈为空
}
// 判断栈是否为空
int isEmpty(Stack* stack) {
return stack->top == -1; // 如果栈顶指针为-1,则栈为空
}
// 判断栈是否为满
int isFull(Stack* stack) {
return stack->top == MAX - 1; // 如果栈顶指针等于最大容量减1,则栈为满
}
// 入栈操作
void push(Stack* stack, int value) {
if (isFull(stack)) { // 判断栈是否已满
printf("栈已满,无法入栈\n"); // 输出栈已满信息
} else {
stack->data[++stack->top] = value; // 将数据压入栈顶,并更新栈顶指针
}
}
// 出栈操作
int pop(Stack* stack) {
if (isEmpty(stack)) { // 判断栈是否为空
printf("栈为空,无法出栈\n"); // 输出栈为空信息
return -1; // 返回-1表示栈为空时无法出栈
} else {
return stack->data[stack->top--]; // 返回栈顶数据,并更新栈顶指针
}
}
// 获取栈顶元素
int peek(Stack* stack) {
if (isEmpty(stack)) { // 判断栈是否为空
printf("栈为空,无法获取栈顶元素\n"); // 输出栈为空信息
return -1; // 返回-1表示栈为空时无法获取栈顶元素
} else {
return stack->data[stack->top]; // 返回栈顶元素
}
}
int main() {
Stack stack; // 定义一个栈变量
init(&stack); // 初始化栈
push(&stack, 10); // 压入数据10到栈中
push(&stack, 20); // 压入数据20到栈中
push(&stack, 30); // 压入数据30到栈中
printf("栈顶元素: %d\n", peek(&stack)); // 获取并打印栈顶元素
printf("出栈元素: %d\n", pop(&stack)); // 弹出栈顶元素并打印
printf("出栈元素: %d\n", pop(&stack)); // 弹出栈顶元素并打印
return 0; // 返回0,表示程序正常结束
}
通俗理解 栈就像是一摞书,新的书只能放在最上面(压栈),取书也只能从最上面开始拿(弹栈)。这种“后进先出”的特点非常适合处理那些需要按相反顺序进行的操作,比如浏览器的后退功能或者函数的递归调用。
线性结构之队列
学习内容概述 今天我学习了队列这一数据结构。队列是一种只能在一端插入数据,在另一端删除数据的线性表,其特点是先进先出(FIFO)。队列的应用也非常广泛,比如任务调度、数据流处理等。
代码示例:队列的实现(基于数组)
#include <stdio.h> // 引入标准输入输出头文件
#include <stdlib.h> // 引入标准库头文件
#define MAX 100 // 定义队列的最大容量为100
typedef struct {
int data[MAX]; // 用数组表示队列的数据存储
int front; // 队列头指针,表示队列中第一个元素的位置
int rear; // 队列尾指针,表示队列中最后一个元素的位置
} Queue;
// 初始化队列
void initQueue(Queue* queue) {
queue->front = 0; // 初始化队列头指针为0
queue->rear = 0; // 初始化队列尾指针为0
}
// 判断队列是否为空
int isEmptyQueue(Queue* queue) {
return queue->front == queue->rear; // 如果队列头指针等于队列尾指针,则队列为空
}
// 判断队列是否为满
int isFullQueue(Queue* queue) {
return (queue->rear + 1) % MAX == queue->front; // 如果队列尾的下一个位置等于队列头,则队列为满
}
// 入队操作
void enqueue(Queue* queue, int value) {
if (isFullQueue(queue)) { // 判断队列是否已满
printf("队列已满,无法入队\n"); // 输出队列已满信息
} else {
queue->data[queue->rear] = value; // 将数据加入队列尾部
queue->rear = (queue->rear + 1) % MAX; // 更新队列尾指针
}
}
// 出队操作
int dequeue(Queue* queue) {
if (isEmptyQueue(queue)) { // 判断队列是否为空
printf("队列为空,无法出队\n"); // 输出队列为空信息
return -1; // 返回-1表示队列为空时无法出队
} else {
int value = queue->data[queue->front]; // 获取队列头部数据
queue->front = (queue->front + 1) % MAX; // 更新队列头指针
return value; // 返回队列头部数据
}
}
int main() {
Queue queue; // 定义一个队列变量
initQueue(&queue); // 初始化队列
enqueue(&queue, 10); // 入队数据10
enqueue(&queue, 20); // 入队数据20
enqueue(&queue, 30); // 入队数据30
printf("出队元素: %d\n", dequeue(&queue)); // 出队并打印元素
printf("出队元素: %d\n", dequeue(&queue)); // 出队并打印元素
return 0; // 返回0,表示程序正常结束
}
通俗理解 队列就像排队买票的人群,新的顾客只能站到队伍的末尾(入队),而服务总是从队伍的最前面开始(出队)。这种“先进先出”的特点非常适合任务调度的场景,比如打印机任务或者操作系统中的进程管理。
心得总结 栈的“后进先出”特性在程序设计中非常有用,尤其是处理递归调用或需要逆序操作的场景。通过实际编写代码,我更好地理解了栈的工作原理,并体验到了栈在内存管理和函数调用中的重要性。对于实现栈,我学到了基于数组的顺序栈和基于链表的链式栈的不同实现方式,分别有各自的优缺点,选择时需要根据具体场景进行权衡。通过学习队列,我理解了队列的先进先出特性,以及它在数据处理和任务调度中的重要性。通过编写队列代码,我更好地理解了如何用数组实现队列,并学会了如何判断队列的空和满情况。对于队列的实现,还可以使用链表来实现一个动态队列,这样就不再受限于数组的固定大小。
标签:链表,队列,日记,queue,int,嵌入式,数据结构,数据,stack From: https://blog.csdn.net/Find_tim/article/details/142962944