ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

数据结构核心知识梳理:从线性表到二叉树与排序算法

数据结构核心知识梳理:从线性表到二叉树与排序算法 1. 为什么要系统学数据结构先搞清楚这门课在解决什么问题先说个我自己的经历。当年准备考研的时候我最开始复习数据结构是直接拿着严蔚敏那本C语言版的书从第一章开始啃。啃了大概一个星期链表、栈、队列的代码都能照着敲出来但一做题就懵。后来我才发现问题不在于我代码写得少而在于我根本不清楚数据结构这门课到底在解决什么问题。数据结构说白了就是研究“数据怎么组织、怎么存储、怎么操作”的一门课。你写任何程序本质上都是在处理数据用户注册的信息是数据电商平台的商品列表是数据地图导航的路径规划也离不开数据。同样的数据你用不同的方式组织起来程序的性能可以是天壤之别。举个最简单的例子在十万个已经排好序的数里找一个目标值用顺序查找平均要比较五万次用二分查找最多比较十七次。数据组织方式对程序效率的影响就是这么直接。很多人分不清数据结构和算法的关系这里我用自己的理解说清楚数据结构是“怎么把数据摆好”算法是“怎么把这些数据用起来”。两者是配套的你选了一种数据结构就基本决定了你能用什么算法、不能用什么算法。比如你用的是无序数组那就没法用二分查找你用的是二叉搜索树才能高效地做范围查询。所以很多经典教材叫《数据结构与算法》这两个东西确实分不开。这篇内容适合谁看我总结下来大概是三类人。第一类是正在准备考研、软考或者期末考试的在校生数据结构是计算机专业绕不开的核心课也是408统考里占分最重的一门。第二类是准备找工作面试的开发者面试官问数据结构表面上看是在考你链表反转怎么写实际上是在判断你有没有基本的程序功底和逻辑思维。第三类是刚入行、写业务代码写得有点迷茫的程序员你可能会发现那些经典的框架、中间件底层全是用数据结构堆出来的。不管你是哪一类这篇文章不是要把教材复述一遍而是把我从复习、考试到实际写代码这些年积累下来的思路和教训整理出来。我会告诉你每个模块到底该怎么学、哪些地方最容易踩坑、代码应该怎么写才算真正掌握而不是仅仅背下来。2. 知识框架与复习路径设计别一上来就啃代码我见过太多人复习数据结构第一件事就是打开编译器开始敲链表。这个做法不是错但效率真的很低。数据结构的体系性很强你脑子里没有整体框架敲再多的代码也是散的今天会了明天忘。2.1 从“逻辑结构—存储结构—操作”三个维度理解一切我后来总结出一个方法所有数据结构都可以从三个维度去理解你只要把这三个维度抓住任何新遇到的数据结构都能快速上手。第一个维度是逻辑结构也就是数据元素之间是什么关系。线性结构是一对一的像排队打饭每个人只有前一个和后一个树形结构是一对多的像公司组织架构一个领导管多个下属图形结构是多对多的像地铁线路图一个站连着好几个站。集合结构则是元素之间没有明确关系属于最松散的。第二个维度是存储结构也就是逻辑结构在计算机内存里怎么落地。两个最基础的方式是顺序存储和链式存储。顺序存储就是开一块连续的内存元素一个一个紧挨着放数组就是典型代表链式存储则是每个元素单独存再用指针把各个元素串起来链表就是典型代表。同一个逻辑结构可以用不同的存储结构来实现比如线性表既可以用顺序表实现也可以用链表实现两种实现各有优劣。第三个维度是操作也就是这个结构支持哪些基本动作。最常见的操作是增、删、改、查再加上一些结构特有的操作比如栈的入栈、出栈队列的入队、出队树的遍历图的遍历。我建议你每学一个数据结构都拿一张纸把这三个维度写下来。比如学二叉树逻辑结构层面是一对多的层次关系存储结构层面可以用顺序存储数组存完全二叉树也可以链式存储二叉链表操作层面有先序、中序、后序、层序遍历还有插入、删除节点。等你把每个结构都这样拆解过一遍整本书的脉络就清晰了。2.2 复习路线建议先线性再非线性先结构再算法再来说复习顺序。我看过很多人的复习计划也踩过一些坑比较合理的路径是这么走的。第一步先把线性表吃透包括顺序表和链表。线性表是基础中的基础后面的栈、队列都是受限的线性表图的邻接表存储也用到了链表所以这个模块学不扎实后面全受影响。第二步学栈和队列。这两个结构本身不难但它们的应用场景非常广函数调用栈、表达式求值、括号匹配、广度优先搜索全都离不开它们。学栈和队列的时候重点不是会写入栈出栈的代码而是能识别出“这个场景本质上是栈还是队列”。第三步学树。树是第一个非线性结构也是递归思维的主战场。二叉树的各种遍历、线索二叉树、哈夫曼树、二叉搜索树、平衡二叉树每一块都很重要。这一章是整个数据结构的第一个分水岭很多人就是在这里开始掉队的。第四步学图。图的存储邻接矩阵、邻接表、遍历深度优先、广度优先、最小生成树、最短路径、拓扑排序知识点多而杂但每一个都不算难。学图的时候要善于把现实问题映射到图模型上比如选课的先修关系就是拓扑排序的典型场景。第五步学查找。从顺序查找、二分查找到二叉搜索树、平衡二叉树再到散列表哈希表这一章的核心是“怎么把数据存成方便查找的形式”。各种查找结构的平均查找长度ASL是考试和面试的高频考点。最后学排序。排序算法是数据结构里最“卷”的一章要记的东西非常多。建议你按“插入排序、交换排序、选择排序、归并排序、基数排序”这个分类去整理把每个算法的思想、代码、时间复杂度、空间复杂度、稳定性做成一张大表反复记忆。还有一个建议学完一遍之后一定要把知识串联起来。比如你学完树和排序可以想想堆排序是怎么用完全二叉树实现的学完图和队列可以想想广度优先搜索是怎么用队列实现的。数据结构这本书的章节之间是有内在联系的串联得越好你理解得越深。3. 核心知识点拆解与实操要点每个模块该怎么学这一章我按模块讲一些核心知识点的学习方法重点放在“怎么理解”和“怎么考”上而不是把教材内容复制过来。3.1 线性表顺序表与链表的取舍线性表是整个数据结构的敲门砖它要解决的核心问题是一串有先后顺序的数据怎么存、怎么操作。顺序表用数组实现优点是随机访问特别快想拿第i个元素直接下标取就行时间复杂度O(1)缺点是插入和删除需要移动大量元素比如在长度为n的顺序表头部插入一个元素需要把后面n个元素全部后移一位时间复杂度O(n)。另外顺序表要预先分配内存分配太大浪费空间分配太小又不够用。链表用节点加指针实现每个节点包含数据域和指针域。它跟顺序表刚好相反插入和删除只需要修改指针不需要移动元素所以频繁插入删除的场景更适合链表但链表没办法随机访问想找第i个节点必须从头往后一个个数过去时间复杂度O(n)。很多初学者会问那到底哪个更好答案是看场景。你要是存一个“只会被遍历、不会被插入删除”的数据集合顺序表就够了你要是做一个“频繁在中间插入删除”的队列或者LRU缓存链表更合适。面试和考试里经常出现这类选择题或简答题你要能把两种结构的优缺点和应用场景说清楚。链表的代码实现有几个细节特别容易出错。一个是头结点的设计很多教材用带头结点的链表好处是空表和非空表的处理逻辑保持一致不用特判另一个是删除节点时要记得用一个临时指针先把待删节点存下来等修改完前驱节点的指针之后再free掉这个临时指针顺序不能反。还有单向链表反转这个高频题核心就是三个指针翻转pre指向已反转部分的前驱cur指向当前要处理的节点next保存cur的后继循环里先把next存下来再让cur指向pre然后整体后移。3.2 栈和队列两种受限制的线性表为什么重要栈和队列都是线性表只是操作受限。栈只能在栈顶插入和删除所以是后进先出LIFO队列只能在队尾插入、队头删除所以是先进先出FIFO。这两个结构看起来简单但它们在实际系统和算法里的出场率极高。函数调用的执行过程就是栈后调用的函数先返回浏览器的后退按钮也是栈每访问一个页面就入栈一个URL点后退就出栈文本编辑器里的撤销操作同样是栈每撤销一步就弹出最近一次的操作记录。队列的应用更广操作系统的进程调度、消息队列、打印机任务排队全是队列树的层序遍历、图的广度优先搜索底层也是队列。学栈和队列的时候我有一个建议不要只盯着数组实现和链表实现重点去把它们应用到具体题目里。比如括号匹配问题遍历字符串遇到左括号就入栈遇到右括号就出栈并检查是否匹配这就是用栈的典型场景。再比如中缀表达式转后缀表达式、用两个栈实现队列、用两个队列实现栈这些都是面试高频题做几道你就会发现栈和队列的本质就那回事。还有一个容易混淆的点是“循环队列”。用数组实现队列的时候如果出队后队头指针不断后移前面的空间就浪费了所以要用循环队列把数组首尾相连。判断循环队列空和满有两种方式一种是牺牲一个存储单元队空条件是front rear队满条件是(rear1) % MaxSize front另一种是增设一个size成员记录元素个数。考试里这个点的计算题很多比如给定MaxSize、队头和队尾指针问队列中有几个元素公式是(rear - front MaxSize) % MaxSize这个公式一定要记熟推导过程是索引差为负时加上MaxSize取模回到正确的偏移量。3.3 树与二叉树递归思维的主战场树这一章是整个数据结构里最容易让人“学会但不会用”的章节因为它涉及的题目太灵活了。我见过的考研真题和面试算法题有一大半跟树有关尤其是二叉树。先建立概念框架。树是n个节点的有限集合有且仅有一个根节点其余节点分成m个互不相交的子树。二叉树是每个节点最多有两个子树的树度最大为2。二叉树有很多性质比如第i层最多有2^(i-1)个节点深度为k的二叉树最多有2^k - 1个节点n0 n2 1叶子节点数等于度为2的节点数加1。这些性质看起来抽象其实都是数学推导出来的复习的时候建议自己推一遍不要死记。二叉树的遍历是重中之重。先序遍历根左右、中序遍历左根右、后序遍历左右根、层序遍历一层一层从上到下前三种用递归实现非常简洁几行代码就搞定。但我强烈建议你也要掌握非递归写法因为考试和面试经常要求你写出非递归版本而且非递归版本能帮你更深刻地理解栈的作用。层序遍历用队列实现每次从队头取出一个节点先访问它再把它的左孩子和右孩子依次入队直到队列为空。线索二叉树是很多人的噩梦。它的核心是解决“二叉树遍历时需要栈或递归来记录前驱后继”的问题。办法是让节点的空指针域派上用场如果左孩子为空就把左指针指向它的前驱节点如果右孩子为空就把右指针指向它的后继节点。为此需要增设ltag和rtag两个标志位0表示孩子指针1表示线索指针。这个知识点考试经常出选择题考察某个节点的前驱或后继是谁。二叉搜索树BST则是后续查找章节的基础。它的性质很简单左子树上所有节点都小于根节点右子树上所有节点都大于根节点且左右子树也分别是二叉搜索树。因为有了这个有序性查找、插入、删除的平均时间复杂度都是O(log n)。但BST有一个致命问题就是如果插入的数据本身有序BST会退化成一条链操作复杂度退化成O(n)所以后面才有了平衡二叉树AVL和红黑树。3.4 图两种遍历就够了图这一章的知识点很多但核心其实就两块遍历和经典算法而遍历是理解后面所有算法的基础。图的遍历有两种深度优先搜索DFS和广度优先搜索BFS。DFS用栈或者递归一条路走到黑走不通就回头BFS用队列像水波一样逐层扩散。DFS的思想和应用特别广。判断两个节点是否连通、求连通分量个数、检测图中是否有环、拓扑排序都可以用DFS实现。BFS的优势在于求最短路径无权图因为它逐层扩散的特性决定了第一次到达某个节点时走的路径一定是最短的。你如果理解了BFS后面学最短路径算法Dijkstra就会轻松很多因为Dijkstra本质上是带权图的BFS变种。最小生成树有两个经典算法Prim和Kruskal。Prim从顶点出发每次选择连接已选集合和未选集合的最短边Kruskal从边出发每次选择权值最小的边只要不构成环就加入。判断是否构成环要用到并查集这个数据结构这也是面试里很常考的一个点建议单独把并查集的路径压缩和按秩合并学会。最短路径要分清单源和多源。单源最短路径用Dijkstra要求边权非负Bellman-Ford和SPFA可以处理负权边但会慢一些。多源最短路径用Floyd三层循环代码极短但时间复杂度是O(V^3)只适合顶点数不多的场景。拓扑排序用BFS实现最直观每次取出入度为0的节点把它从图中删掉更新邻接点的入度循环直到所有节点处理完。如果最后还有节点没被处理说明图里有环。图这章我的学习建议是算法思想一定要配合图示去理解不要只看文字。你自己画一个小图用笔把每一步算法过程走一遍比看十遍课件都管用。等步骤走顺了再去对照代码写一遍你会发现代码其实就是在模拟你刚才手算的过程。3.5 查找从顺序查找到平衡二叉树查找这一章的核心关键词是效率和对比。你学完这一章要能回答一个问题在不同的场景下用哪种查找方式最合适顺序查找最简单从头到尾挨个比平均查找长度是(n1)/2适合无序表但只适合数据量很小的场景。二分查找要求数据有序每次把搜索区间对半砍时间复杂度O(log n)但底层必须是顺序表因为链表没办法用下标快速定位中间元素。这里有个常见考点二分查找的判定树是一棵平衡二叉树查找任意元素所需的比较次数不超过树的深度。二叉搜索树上的查找其实就是把二分查找从顺序表搬到了树上它的优点是插入和删除不需要移动大量数据缺点是树可能会退化。为了解决退化问题就有了平衡二叉树AVL树它保证任意节点的左右子树高度差不超过1。AVL树插入后可能需要做旋转调整有LL型、RR型、LR型、RL型四种情况LR和RL型需要先旋转成LL或RR再继续。考试里AVL的构建过程是高频大题一定要自己多画几遍。红黑树是AVL的“放宽版”不追求绝对平衡只保证最长路径不超过最短路径的两倍所以插入删除时调整开销更小在工程上应用更广比如TreeMap、HashMap的红黑树化。散列表哈希表是另一条查找路线它是通过一个散列函数把关键字直接映射到存储位置理想情况下查找时间是O(1)。但散列函数可能会把不同的关键字映射到同一个位置这就是冲突。处理冲突常用两种方法开放定址法和链地址法。开放定址法出现冲突就找下一个空位链地址法把同义词都挂在同一个链上。这个章节最常考的计算是求平均查找长度ASL你必须学会画出散列表的最终形态然后按查找成功的ASL和查找失败的ASL分别计算细节很多但只要画表时按规则一步步来不会太难。3.6 排序稳定性、复杂度和代码的平衡排序是数据结构里知识点最密集的一章也是很多复习到一半的人最容易放弃的一章。我建议你用“每个算法的核心思想一句话 一张对比表”的方式来掌握而不是死记硬背代码。先按类别整理一下。插入类排序直接插入排序和希尔排序核心思想是把新元素插入到已排序的序列中。交换类排序冒泡排序和快速排序核心思想是通过交换逆序元素来逐步有序。选择类排序简单选择排序和堆排序核心思想是每次从剩余元素中选出最小或最大的放到最终位置。归并排序则是“分而治之”把序列分成两半分别排序再合并起来。基数排序不走比较路线按位分配和收集。每个算法你都必须关注三个指标时间复杂度、空间复杂度、稳定性。考试和面试非常爱问这些对比。时间复杂度的记忆有个小技巧直接插入排序、冒泡排序、简单选择排序这三个“简单排序”的平均时间复杂度都是O(n^2)但直接插入排序在数据基本有序时表现好是O(n)快速排序平均O(n log n)但最坏情况下退化成O(n^2)不过它的常数很小实际表现通常最好堆排序和归并排序的时间复杂度稳定在O(n log n)但堆排序不稳定归并排序稳定但需要O(n)的辅助空间。关于稳定性一句话记法是不稳定的排序算法有四个——快快速些希尔选简单选择堆堆排序剩下的一般都是稳定的。你还可以补充一个细节像“简单选择排序”为什么不稳定举个例子序列[5, 5*, 3]第一轮选出最小元素3和第一个5交换两个5的相对位置就变了所以不稳定。这个细节理解了面试中被追问就不会慌。代码实现方面快速排序和归并排序是重点因为它们最能体现递归和分治思想。快速排序的关键是partition操作选一个基准值把小于等于基准值的元素放到左边大于基准值的放到右边然后递归处理左右两个子区间。边界条件非常容易出错建议你将区间定义为左闭右闭的写法固定下来比如快排的递归函数写void quickSort(int arr[], int low, int high)在low high的条件下递归partition返回基准位置pivotIndex后再分两段调用。4. 实操C语言版核心代码到底该怎么写很多同学学到代码实现这一步就卡住了觉得教材上的代码不完整、变量名太抽象、边界条件看不懂。这一章我挑几个最常写的核心结构把实现思路和容易犯的错讲清楚代码风格以严蔚敏版教材为基础做简化。4.1 顺序表的动态扩容实现顺序表用定长数组实现最大的问题是容量不够。实际情况中我们更常用动态扩容的方式这也是Java里ArrayList、C里vector的底层思路。核心结构体可以这样定义#define INIT_CAPACITY 8 typedef struct { int *data; int size; // 当前元素个数 int capacity; // 当前容量 } SeqList;初始化时分配一块初始内存void initList(SeqList *list) { list-data (int *)malloc(INIT_CAPACITY * sizeof(int)); if (list-data NULL) { exit(EXIT_FAILURE); } list-size 0; list-capacity INIT_CAPACITY; }插入元素时先检查容量是否满了满了就扩容扩容的倍速一般取1.5倍或2倍void expandCapacity(SeqList *list) { int newCapacity list-capacity * 2; int *newData (int *)realloc(list-data, newCapacity * sizeof(int)); if (newData NULL) { exit(EXIT_FAILURE); } list-data newData; list-capacity newCapacity; } void insertElement(SeqList *list, int index, int value) { if (index 0 || index list-size) { printf(index out of range\n); return; } if (list-size list-capacity) { expandCapacity(list); } for (int i list-size; i index; i--) { list-data[i] list-data[i - 1]; } list-data[index] value; list-size; }注意插入时循环从后往前移动元素如果从前往后移后面的元素会被前面的覆盖掉。这个细节在考试和实际编码里都很要命。4.2 单链表的头插法为什么经常用链表节点的定义很简单typedef struct Node { int data; struct Node *next; } Node;链表的插入方式有头插和尾插两种。头插法代码短而且在某些场景里有特殊价值比如逆置一个链表时遍历原链表用头插法逐个插入新链表就能得到逆序结果。头插法实现如下void insertAtHead(Node **head, int value) { Node *newNode (Node *)malloc(sizeof(Node)); newNode-data value; newNode-next *head; *head newNode; }关键点是函数参数要传头指针的地址也就是二级指针Node **head。如果你只传Node *head函数内部对head的修改不会影响外部调用者的头指针。很多初学者在这个地方卡很久记住一句话如果你想在函数里修改一个指针变量的值就必须传这个指针变量的地址也就是二级指针或者在函数里返回新的头指针再赋值回去。尾插法需要先找到当前链表的最后一个节点再在后面接上新节点。如果每次都从头遍历找尾节点插入n个元素的时间复杂度是O(n^2)这时候可以额外维护一个tail指针在插入时同时更新tail时间复杂度降为O(1)。这个尾指针技巧在做队列的链表实现时也会用到。4.3 二叉树遍历递归与非递归的对应二叉树节点的定义typedef struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; } TreeNode;先序遍历的递归实现非常好写void preorder(TreeNode *root) { if (root NULL) { return; } printf(%d , root-val); preorder(root-left); preorder(root-right); }中序和后序只是调整printf的位置这里不再重复。很多题目要求非递归实现理解起来其实很简单递归的本质是系统栈我们手动用栈模拟这个过程就行。先序非递归的核心逻辑是遇到节点就访问并把它的右孩子先压栈、左孩子后压栈因为栈是后进先出左孩子会后压栈但先弹出才符合先左后右的顺序void preorderNonRecursive(TreeNode *root) { if (root NULL) return; Stack *stack createStack(); push(stack, root); while (!isEmpty(stack)) { TreeNode *node pop(stack); printf(%d , node-val); if (node-right ! NULL) { push(stack, node-right); } if (node-left ! NULL) { push(stack, node-left); } } }后序非递归相对复杂一个常用的技巧是使用两个栈第一个栈按“根右左”的顺序入栈出栈再把出栈节点压入第二个栈最后统一弹出第二个栈得到的就是“左右根”的后序遍历。这个技巧在面试中如果写不出来很遗憾但理解了第一个栈的“根右左”来自先序遍历的变形就好记多了。4.4 快速排序的边界条件最容易错快速排序是面试手写代码的最高频题目之一也是很多人写得时对时错的一个。问题基本都出在边界条件的处理上。我给出一个比较稳妥的写法int partition(int arr[], int low, int high) { int pivot arr[low]; while (low high) { while (low high arr[high] pivot) { high--; } arr[low] arr[high]; while (low high arr[low] pivot) { low; } arr[high] arr[low]; } arr[low] pivot; return low; } void quickSort(int arr[], int low, int high) { if (low high) { int pivotIndex partition(arr, low, high); quickSort(arr, low, pivotIndex - 1); quickSort(arr, pivotIndex 1, high); } }这个写法的key point有两个。第一外层while的条件是low high内层两个while也都要加上low high否则元素移动时可能越界第二内层循环比较时用和这样可以跳过与基准值相等的元素避免两个相等元素无限交换导致死循环。我见过不少人在这个问题上栽跟头测试数据一大就崩溃或者排序结果不对基本都是这个原因。5. 常见问题与排查技巧实录代码写多了就会发现问题我把实际写数据结构代码时最常遇到的几类问题和排查思路整理一下这些经验教材里不会写但非常实用。5.1 程序崩溃和内存错误排查第一个常见问题是程序一运行就崩溃最常见的两个原因是空指针解引用和数组越界。空指针解引用典型的场景是链表操作时没有判断节点是否为NULL。比如删除链表节点时你直接访问了node-next-data但node-next可能是NULL程序就会崩溃。解决办法就是养成习惯每次访问指针成员前先判断指针是否为空。很多老手写代码时会刻意写“防御性判断”不是啰嗦是保命。数组越界的典型场景是顺序表插入删除时循环边界没控制好。比如插入时从isize开始循环到iindex如果你写成iindex就会多移动一个元素最后把数组越界写坏了。这种错误用调试器很难一眼看出来因为崩溃的位置往往不在出错的那一行而是在下一次分配内存或者释放内存的时候才暴露。我的排查技巧是在关键的循环前后打印size、index等关键变量的值一旦发现size比预期大1马上就能定位到循环边界有问题。还有一个特别隐蔽的内存问题是返回了局部变量的地址。比如你在函数里定义了一个局部数组然后返回它这个数组在函数结束后就被销毁了调用者拿到的是一个悬空指针。正确的做法是用malloc在堆上分配内存或者把结果通过参数传出去。这种问题在实现树的遍历、把结果集存入数组时特别容易出现。5.2 死循环和逻辑错误排查死循环在链表和递归算法里很常见。链表遍历写成死循环往往是循环条件写错比如应该判断while(p ! NULL)写成了while(p-next ! NULL)导致最后一个节点不被处理或者某个节点的next没有置为NULL导致链表根本走不完。排查方法是用一个计数器限制循环次数比如最多循环100次就退出然后打印当前节点的值看它停在哪里。递归算法的死循环通常是你没有处理好递归的递推关系和终止条件。比如二分查找如果区间更新写成了low mid而不是low mid 1当区间缩小到两个元素时会陷入无限递归。排查递归死循环的思路是“缩小输入规模”用一个很小的测试用例在纸上手动执行几层看看参数是怎么变化的很快就能找到问题。逻辑错误比崩溃更隐蔽因为程序能跑但结果不对。最典型的例子是排序结果不完整或部分有序。这时候我的做法是准备一组很小的、带有重复元素的测试数据比如[3, 1, 4, 1, 5, 9, 2, 6]然后追踪第一轮或第一次递归的过程手动核对每一步交换或者插入的结果。用小组数据能帮你快速定位是partition写错了还是合并逻辑漏了元素。5.3 复习备考常见问题速查考试复习这件事也有几个高频问题一并放在这里。问严蔚敏C语言版教材风格偏老代码里用了很多C引用看不懂怎么办答严蔚敏教材的C版代码里确实用了引用传参符号这是很多自学读者最容易困惑的地方。我的建议是先跳过语法层面的困惑把注意力放在算法的逻辑流程上。教材代码的核心是算法思想的表达而不是一门语言的最佳实践。如果你用纯C只需要把引用改成指针传参比如InitList(SqList L)改成InitList(SqList *L)。如果你用C直接保留引用就行。千万不要因为语法细节卡住然后放弃整本书。问王道和严蔚敏教材如何配合要不要都看答如果你是考研我的建议是严蔚敏教材作为“字典”王道单科书作为“主线”。王道胜在把考点归纳得很清楚适合按章节快速过知识点、刷题严蔚敏教材胜在原理推导和代码完整性遇到王道没讲透的细节再去翻严书。不要试图两本都从头到尾精读时间不够效率也低。问数据结构代码题怎么练才能对答两个原则第一是“手写”第二是“调试”。手写能帮你发现哪些地方你以为会但实际写不出来调试能帮你验证思路。我自己的节奏是每学完一个数据结构就把它最基本的增删改查和遍历手写一遍不看任何参考资料写完再对着教材自查。等到面试或考试前再把手写过的重点代码重新默写一遍这样上考场或者面试时才稳。问软考和408考数据结构有什么区别答软考初级、中级里面的数据结构题考察面更广但深度不深很多是概念判断和简单计算408统考对数据结构和算法的考察更深入不仅有选择题还有算法设计大题要求你能写出可运行的代码。408里数据结构的算法题一般不难但很看重边界条件的处理和基本思想所以408复习一定要动手写代码光看光背是不够的。最后再分享一个我自己反复用的实用技巧。无论是考试复习还是准备面试我都喜欢在纸上画“数据结构知识思维导图”但不是按教材目录画而是按“存储方式”和“操作复杂度”来画。比如把所有插入操作时间复杂度为O(1)的结构列在一起把所有支持有序遍历的结构列在一起。这样跨章节地重新组织知识能帮你从更高的角度理解整个数据结构体系。等你能做到看到一个问题就能判断它适合用什么结构解决同时又知道这个结构的增删改查时间复杂度各是多少的时候说明这门的底子已经打得差不多了。
返回列表