news 2026/5/3 0:43:34

数据结构开篇:从问题到解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构开篇:从问题到解决方案

引言

在之前的学习中,我们学习了C语言的基础语法、指针、结构体、动态内存分配,甚至用这些知识实现了一个银行管理系统。但你可能已经发现,随着程序规模的增长,管理数据变得越来越困难。

一个简单的问题:如果让你存储100个学生的成绩,你会怎么做?

// 方案1:定义100个单独的变量? int score1, score2, score3, ...; // ❌ 不可行 // 方案2:使用数组 int scores[100]; // ✅ 可行,但功能有限

数组能解决一部分问题,但它的局限性也很明显:

  • 大小固定,无法动态扩展

  • 插入、删除元素需要移动大量数据

  • 无法方便地存储复杂的学生信息(姓名、学号、成绩等)

这就是为什么我们需要学习数据结构


第一部分:什么是数据结构?

一、数据结构的定义

数据结构是计算机存储、组织数据的方式。它是指相互之间存在一种或多种特定关系的数据元素的集合。

通俗地说,数据结构就是研究如何高效地存储和管理数据

类比理解:

场景类比数据结构
排队买票先来先服务队列(Queue)
叠盘子后进先出栈(Stack)
通讯录按顺序存储数组(Array)
地铁线路图网状连接图(Graph)
公司组织架构树状结构树(Tree)

二、数据结构的分类

三、逻辑结构与物理结构的区别

结构类型定义举例
逻辑结构数据之间的抽象关系线性、树形、图形
物理结构数据在内存中的实际存储方式顺序存储、链式存储

关键理解:逻辑结构是“思路”,物理结构是“实现”。同一种逻辑结构可以用不同的物理结构实现。


第二部分:为什么需要数据结构?

一、没有数据结构的困境

假设我们要存储一个班级的学生信息,需要支持:

  • 添加新学生

  • 删除指定学生

  • 按学号查找

  • 按成绩排序

如果只用数组,实现起来非常复杂:

// 使用数组存储学生信息 typedef struct { int id; char name[20]; int score; } Student; Student students[100]; int count = 0; // 插入学生:需要移动后面的所有元素 void insert(int pos, Student s) { for (int i = count; i > pos; i--) { students[i] = students[i - 1]; } students[pos] = s; count++; } // 删除学生:也需要移动元素 void delete(int pos) { for (int i = pos; i < count - 1; i++) { students[i] = students[i + 1]; } count--; }

这种实现方式存在明显问题:

  • 插入/删除操作效率低(O(n))

  • 数组大小固定,扩容不便

  • 代码容易出错(边界问题)

二、数据结构解决的问题

问题解决方案相关数据结构
数据存储如何组织数据以便访问数组、链表
数据插入/删除如何在中间位置操作链表
数据查找如何快速找到目标数据树、哈希表
数据排序如何按规则排列数据堆、二叉搜索树
数据顺序如何按特定规则处理栈、队列

第三部分:数据结构和算法的关系

一、程序 = 数据结构 + 算法

这是计算机科学领域的经典公式。数据结构决定了数据的组织方式,算法决定了对数据的操作方式。

二、同一个问题,不同的解法

问题:判断一个字符串是否是回文(正读反读相同)

解法1:使用数组

int isPalindrome(char str[], int len) { for (int i = 0; i < len / 2; i++) { if (str[i] != str[len - 1 - i]) return 0; } return 1; }

解法2:使用栈

int isPalindrome(char str[], int len) { Stack s; initStack(&s); // 前半部分入栈 for (int i = 0; i < len / 2; i++) { push(&s, str[i]); } // 后半部分与栈顶比较 int start = (len + 1) / 2; for (int i = start; i < len; i++) { if (pop(&s) != str[i]) return 0; } return 1; }

不同的数据结构适用于不同的场景。数组简单直接,而栈更好地体现了“后进先出”的逻辑。


第四部分:时间复杂度和空间复杂度

一、为什么要学习复杂度?

当我们有多种方案解决同一个问题时,如何选择最优的方案?复杂度分析就是答案。

复杂度含义通俗解释
时间复杂度算法执行所需时间运行快慢
空间复杂度算法执行所需内存占用多少内存

二、大O表示法

大O表示法用于描述算法在最坏情况下的时间复杂度。

复杂度名称例子数据量翻倍时
O(1)常数阶数组访问时间不变
O(log n)对数阶二分查找时间增加约1倍
O(n)线性阶遍历数组时间翻倍
O(n log n)线性对数阶快速排序时间增加略多于2倍
O(n²)平方阶冒泡排序时间变为4倍

时间复杂度对比图(直观理解):

三、复杂度计算示例

// O(1) - 常数阶 int getFirst(int arr[]) { return arr[0]; // 只执行1次操作 } // O(n) - 线性阶 int sum(int arr[], int n) { int total = 0; for (int i = 0; i < n; i++) { // 循环n次 total += arr[i]; } return total; } // O(n²) - 平方阶 void bubbleSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { // 外层n次 for (int j = 0; j < n - 1 - i; j++) { // 内层n次 if (arr[j] > arr[j + 1]) { swap(&arr[j], &arr[j + 1]); } } } }

第五部分:数据结构学习路线图

一、建议学习顺序

二、每个阶段的目标

阶段核心目标实践项目
线性结构理解顺序存储和链式存储的区别实现一个简单的图书管理系统
栈和队列理解后进先出和先进先出的应用场景实现括号匹配、任务调度
查找与排序掌握不同算法的时间和空间复杂度实现学生成绩排序
树形结构理解层次结构的数据组织实现文件目录遍历
图结构理解网络关系的数据建模实现地铁线路规划

第六部分:我们已经学过的数据结构

在学习数据结构之前,其实我们已经接触过一些数据结构了:

数据结构我们已经学过的内容待深入的内容
数组一维/多维数组的定,内存布局动态数组、变长数组
结构体自定义数据类型,成员访问结构体数组的管理
函数调用栈(运行时)自定义栈的实现(顺序栈、链式栈)
队列未正式学习顺序队列、循环队列
链表未正式学习单向链表、双向链表

栈的应用举例:函数调用栈

// 下面的函数调用过程,就是一个栈的应用 void funcC() { } // 第3个入栈,第1个出栈 void funcB() { funcC(); } // 第2个入栈,第2个出栈 void funcA() { funcB(); } // 第1个入栈,第3个出栈 int main() { funcA(); // 函数调用栈:main → funcA → funcB → funcC → 返回 return 0; }

这就是为什么数据结构如此重要——它不仅是我们显式使用的工具,更是程序运行的基础机制。


第七部分:学习数据结构的建议

一、学习方法

方法说明
理解原理先弄清楚数据结构的设计思想,再动手实现
动手编码不要只看代码,每个数据结构都要自己实现一遍
画图辅助数据结构涉及很多指针操作,画图能帮你理清思路
分析复杂度实现之后,分析时间和空间复杂度
实际应用将学到的数据结构应用到实际项目或练习题中

二、常见误区

误区正确理解
数据结构是死记硬背数据结构是解决问题的工具,重在理解
理论比实践重要理论和实践同等重要,缺一不可
越复杂的结构越好适用于场景的才是最好的
直接抄代码就行抄一遍远不如自己写一遍理解深刻

第八部分:动手预热——从一个简单的问题开始

问题:实现一个动态整数数组,支持以下操作:

学习建议:

数据结构的学习需要时间和耐心,但一旦掌握,你会发现编程世界变得更加清晰。下一篇文章,我们将从最简单的线性表开始,实现一个完整的链表。

  • init:初始化数组

  • add:在末尾添加元素

  • insert:在指定位置插入元素

  • removeAt:删除指定位置的元素

  • get:获取指定位置的元素

  • destroy:销毁数组

    #include <stdio.h> #include <stdlib.h> #include <string.h> #define INIT_CAPACITY 4 typedef struct { int* data; int size; // 当前元素个数 int capacity; // 当前容量 } DynamicArray; // 初始化 void init(DynamicArray* arr) { arr->data = (int*)malloc(INIT_CAPACITY * sizeof(int)); arr->size = 0; arr->capacity = INIT_CAPACITY; } // 扩容 void expand(DynamicArray* arr) { int newCapacity = arr->capacity * 2; int* newData = (int*)realloc(arr->data, newCapacity * sizeof(int)); if (newData == NULL) return; arr->data = newData; arr->capacity = newCapacity; printf("扩容成功: %d -> %d\n", arr->capacity / 2, arr->capacity); } // 在末尾添加 void add(DynamicArray* arr, int val) { if (arr->size >= arr->capacity) { expand(arr); } arr->data[arr->size++] = val; } // 在指定位置插入 void insert(DynamicArray* arr, int pos, int val) { if (pos < 0 || pos > arr->size) return; if (arr->size >= arr->capacity) { expand(arr); } // 移动元素 for (int i = arr->size; i > pos; i--) { arr->data[i] = arr->data[i - 1]; } arr->data[pos] = val; arr->size++; } // 删除指定位置元素 void removeAt(DynamicArray* arr, int pos) { if (pos < 0 || pos >= arr->size) return; for (int i = pos; i < arr->size - 1; i++) { arr->data[i] = arr->data[i + 1]; } arr->size--; } // 获取元素 int get(DynamicArray* arr, int pos) { if (pos < 0 || pos >= arr->size) return -1; return arr->data[pos]; } // 打印数组 void print(DynamicArray* arr) { for (int i = 0; i < arr->size; i++) { printf("%d ", arr->data[i]); } printf("\n"); } // 销毁 void destroy(DynamicArray* arr) { free(arr->data); arr->data = NULL; arr->size = 0; arr->capacity = 0; } int main() { DynamicArray arr; init(&arr); for (int i = 0; i < 10; i++) { add(&arr, i + 1); } printf("添加后: "); print(&arr); insert(&arr, 3, 100); printf("插入后: "); print(&arr); removeAt(&arr, 5); printf("删除后: "); print(&arr); destroy(&arr); return 0; }

    这是一个动态数组的简单实现,它结合了数组的连续存储特性和动态扩容能力。这个例子很好的展示了数据结构设计的核心思想——封装数据和对数据的操作

    数据结构是计算机科学的核心课程,它不仅仅是面试中的必考内容,更是编程内功的重要组成部分。

    本文作为数据结构系列的开篇,旨在为你解答"为什么需要学数据结构"这个问题。后续我们将会逐一深入讲解:

  • 线性表(链表、数组)

  • 栈和队列

  • 树和图

  • 查找与排序算法

  • 每个数据结构都要自己动手实现一遍

  • 画图理解指针操作,不要只在脑中想象

  • 分析每个操作的时间复杂度和空间复杂度

  • 思考每个数据结构适用于什么场景

    • 哈希表

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/3 0:41:36

抖音下载器技术诊断:从API失效到多策略下载的架构演进

抖音下载器技术诊断&#xff1a;从API失效到多策略下载的架构演进 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppo…

作者头像 李华
网站建设 2026/5/3 0:41:30

物联网固件安全生死线(ARM Cortex-M3实测对比:AES-128-CBC vs. Speck128/128 vs. LEA-128——吞吐量+功耗+代码体积三维碾压数据)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;物联网固件安全生死线&#xff1a;ARM Cortex-M3平台轻量级加密算法选型总览 在资源受限的ARM Cortex-M3嵌入式设备上&#xff0c;固件完整性与通信机密性直接决定物联网终端的生存边界。其典型配置仅含…

作者头像 李华
网站建设 2026/5/3 0:37:32

ctfileGet终极指南:快速获取城通网盘直连地址的完整方案

ctfileGet终极指南&#xff1a;快速获取城通网盘直连地址的完整方案 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 还在为城通网盘的复杂下载流程烦恼吗&#xff1f;ctfileGet作为一款开源工具&#x…

作者头像 李华
网站建设 2026/5/3 0:36:28

WPS-Zotero完整指南:三步实现跨平台文献管理效率提升5倍

WPS-Zotero完整指南&#xff1a;三步实现跨平台文献管理效率提升5倍 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为学术写作中的文献引用而烦恼吗&#xff1f;WPS-Zo…

作者头像 李华
网站建设 2026/5/3 0:31:45

基于语音识别的机械臂控制:从Whisper模型到任务规划实战

1. 项目概述&#xff1a;当机械臂“听懂”人话最近在折腾一个挺有意思的开源项目&#xff0c;叫openclaw-voice。简单来说&#xff0c;它让一个叫MCKRUZ的桌面级机械臂&#xff0c;能“听懂”你说的话&#xff0c;然后去执行对应的抓取任务。你不再需要去写复杂的运动学代码&am…

作者头像 李华