数据结构个人学习推荐


这篇博客只是分享一些个人的学习经验,推荐一些资料,带有强烈的个人主观感受在里面,仅供参考。

学好这门课的重要性

和计算机网络不一样,这门课让我去讨论它的重要性,一时间还真不知道怎么说。说它是后面搞开发要具备的基础吧,但很多编程语言其实都有封装好这些数据结构的实现,例如 C++ 的 STL 库,Java 有集合类(Collection 和 Map)。说是锻炼计算机思维之类的方面吧,但是往往是只可意会不可言传的,我好像也没什么直接的感受。
先从一些实际的角度上说:

  • 首先这门课是计算机类专业的专业必修基础课,也就是说只要是计算机类的专业都是要学习数据结构的。所以无论后面的发展方向是否需要用到这些知识,至少得先把这门课过了。
  • 对于就业而言,无论是问答、手撕还是现场敲,面试开发岗基本上都会考查算法题,很多技术的底层原理也都是基于相应的数据结构实现的。
  • 对于研究生考试而言,在科目“408”中数据结构占到 45 分,和计算机组成原理一样是占比最高的科目,大部分自命题的学校也会考查甚至是只考察数据结构。在复试中的笔试或上机环节也会大量考查数据结构的代码,面试中专业课问答的环节这门课也非常容易提问。
  • 对数据结构或者相关的研究领域,想要从事相关的科研工作肯定得有非常扎实的数据结构、算法的基础。
  • 对于程序设计类学科竞赛,例如 ICPC、CCPC、程序设计天梯赛、蓝桥杯,数据结构是必须要掌握的基础知识。

再从比较“虚”的角度上来看,编程语言和具体的技术相当于计算机专业的“招式”,而数据结构、算法之类的理论相当于“内功”。我们都说“程序 = 数据结构 + 算法”,不同情景下的程序需要处理的数据各有其特点,选用合适的数据结构才能更好地组织数据或利用其特性满足需求。同时时间复杂度、空间复杂度分析贯穿着整门课程,体现了一种优化的思想,在开发应用时除了利用主流技术和框架,在对他们进行排列组合或者编写业务流的代码时也需要很注意在时间和空间上的开销。在很多时候,某个功能的少许时空上的优化,很可能在某个量级的数据下能显著地提升效率。
在其他很多的理论课中,也都能或多或少地看到数据结构的影子,例如数据库的索引、操作系统的堆栈和队列、计算机网络中的报文格式等。很多计算机的理论和技术都是相互渗透或需要协同工作的,相信学好数据结构对学习其他计算机理论课也有很大的帮助。

学习方法

多看图例并动笔画图

这门课你可以把它当做是我给你一些数据,你通过一些抽象的数据结构来组织他们,使这些数据能够在计算机中抽象出现实中的意义,或者能够借助这些结构的特性来有效解决某些问题。所以这里面包括很多计算机的技术和理论,都包含着对现实的抽象,因此如果没法很好地理解抽象后的效果,也就很难理解这些算法。例如看如下一段代码:

typedef struct BiTNode
{
    ElemType data;    //数据域
    ChildPtr *lchild,*rchild;    //左右孩子的指针域
    //可以开个指针域指向双亲,变为三叉链表
}BiTNode, *BiTree;

在没有学习这门课之前,你可能很难想象出就这么几行代码就能将数据以“二叉树”的形式组织,如下面这张图所示。

所以这些数据结构乍一看理解不了很正常,因为它们都是抽象之后的东西,想要看懂就需要化抽象为具体。此时一个具体的例子和逐步推进的算法图例能起到很重要的作用,先理解某个例子在算法中的执行过程,就有助于你在思路上推广到通用的、抽象的算法上。例如单链表插入新结点的图例如下所示,通过画图你就能理解 2 个关键操作的作用,以及它们不能调换顺序的原因。

有时候当算法比较复杂时,可能你一时间也没法把图画好,或者是数据量稍微大一些图会变得不好画。此时可以利用一些数据结构可视化工具,例如我学这门课的时候主要用了VisuAlgo,你可以输入一组数据然后逐步查看每一步都发生了什么事。

步步为营且不断重复

数据结构和 C 语言这类技术课有着本质上的区别,我认为它是一门需要通过编程来掌握的理论课,所以在算法上的理解和编程上的实践都很重要。这门课的信息量相对是比较大的,学习时感到“消化不良”是常事。对学习这门课本身其实可以和中学的学习差不多,预习肯定是很重要的,至少要先知道这种数据结构或算法要解决一件什么事情,否则上课很容易掉线。因为知识量比较大,在书本上圈圈画画、做笔记、整理笔记本也需要看情况选择,也要通过一些例题和作业来巩固。这些肯定屏幕前的你是懂的,但是知道和是否有付出行动是两个完全不同的维度。
需要强调的一点是,如果让我挑一个最重要的章节,那一定是线性表。简单地说,线性表、树、图分别是数据的一对一、一对多、多对多的关系,树和图可以看做是线性表的推广,线性表也可以看做树和图的特例。例如图中的结构是一种特殊的二叉树——斜树,是一种所有结点都只有左(右)子树的二叉树,不难看出从表面上看它就是线性表。

线性表(尤其是单链表)的基操是极其重要的,栈和队列本身就是特殊的线性表,后面的树和图把它们的代码段的粒度切得很细时,会发现这些操作和线性表的操作是紧密联系的。所以线性表掌握得情况如何,其实基本上能决定后续这门课的学习是否能有较好的节奏。所以这门课一定要从一开始就紧紧跟着,前面放松了后续雪球只会越滚越大,很容易导致直接摆烂。
当然这门课本身内容比较多,也不用想着一两遍就全部记下来,肯定也是需要不断多次重复才能较好地掌握。每次的学习都是有效果的,在后期重复复习的时候,过同一个知识点肯定效率会更高。在脑中提取的速度越快,复习时捡起来的速度越快,也能从侧面证明掌握的情况是比较好的。

Show me your code!

弄懂了这些算法的原理,做对了纸质的作业就算是完全掌握了吗?如果是由人的肉身去执行这些算法,不难看出这是一件啰嗦且费劲的事情,我们的最终目的是要让计算机来执行这些代码,这肯定比人有效率得太多太多。所以真正要看看自己掌握的如何,一定要有足够的编程量,看看你的所学能不能解决问题。因为题目难度有别,对知识的掌握也是循序渐进的,所以对于一道具体的题目,可以按照下面的流程来要求完成。

借鉴学习他人的优秀代码是没问题的,毕竟一道超出现在能力的题目很难不靠外力憋出来。不过切记,这和直接复制粘贴交作业完全是两码事。同时看懂算法和动手写代码也存在明显的相互作用,尤其是在懵懂和看懂之间的“暧昧”状态时,放下书本敲点代码能帮你理顺算法的思路和流程。我刚学链表的时候也有这么一段时间,我就找了个题集把链表的基操写出肌肉记忆,回头再看链表的基操就能比较顺畅地理解了。
所以数据结构学不进去的原因,也有可能是学习时根本没有动手写过代码,而不是所谓的“基础差”、“记不下”和“看不懂”的问题。

尝试输出所学知识

我们学习很多的理论和技术的最终目的,往往都是为了能在工作或生活中利用它们满足需求或者发现、解决新的问题。尤其是对于计算机类这门较为“务实”的学科,学习这些知识都是为了让我们能更好地利用计算机。所以你可能理解了这门课的很多知识,写了很多作业也刷了一些题目,但是如果不能进行输出,其实就结果而言和没有学习这门课是一样的。
在理论方面也可以用知识输出的方式帮助自己检验学习效果,巩固对知识的理解。如果你要把某个知识点或题目 100% 地教会别人,你对它的理解一定是需要超过 200% 的。因此在学习过程中你可以有意识地寻找和他人讨论或讲解的机会,或者制作一些复习提纲或者知识笔记之类的,这些都是知识输出的方式。我之前学这门课是在疫情居家学习的时候,当时我写博客就写得非常上头,由于需要翻阅很多资料来回阅读,所以写完博客就对知识点比较熟悉了。在后期进行应用或者考试时,也可以通过读这些博客的方式快速复习或检索出需要的内容。
在写代码方面就更不用说了,学习数据结构的一大目的就是为了更好地组织数据。平时写的编程题确实能帮助你学好这门课,但是毕竟题目只是一个小的、特定的情景,工作或生活中的情景远比题目大得多或复杂得多。因此学完这些知识,更重要的是结合自己现有的技术做一个较为完整的应用,至少是一个综合度和工作量较大的课程设计。送分级别的课程设计题目,或做出的成品只能勉强达到及格的程度,肯定是不能达到学以致用的要求。

常见的问题

C 都还给老师了,还需要返工吗?

可以但不推荐,现在再去返工 C 语言的价值已经很小了。虽然学习时需要用 C 语言实现数据结构(先开 C 后学数据结构的情况),但是只要把握好学习绪论、时空复杂度和线性表的时间,数据结构的内容可以反过来帮助你熟练 C 语言。
一种数据结构的实现可链式存储也可以顺序存储,顺序结构在 C 语言一般使用数组实现,例如:

//顺序表按照元素查找
Position LocateElem(List L, ElementType e)
{
    int i = 0;
    for (Position i = 0; i <= L->Last; i++)   
    {
        if (L->Data[i] == e)
        {
             return i;
        }
    }
    return 0;
}

链式结构需要用到很多指针,例如:

//头插法建链表
void CreateListF(LinkList& L, int n)
{
    LinkList head, ptr;
    head = new(LNode);    //创建头结点
    head->next = NULL;    //初始化头结点的后继为NULL

    for (int i = 0; i < n; i++)
    {
        ptr = new(LNode);    //创建新结点
	cin >> ptr->data;
	ptr->next = head->next;    //连接表身
	head->next = ptr;    //将新结点插到表头
    }
    L = head;
}

定义数据结构时需要会写结构体,例如:

//定义单链表结点类型
typedf struct LNode
{
    ElemType data;    //数据域,存放数据
    struct LNode *next;    //指针域,指向后继结点
}LinkList,*List;

同时也不难看出,实现这些结构的功能也肯定不会缺少分支和循环。因此我认为认真把线性表吃透,代码模仿着多写一写,自然就会熟悉学习这门课需要的 C 语言语法。一开始也不用太苛求自己能写出线性表的各种操作,而是先看懂每组代码段都是些什么功能,具体的语法怎么用再回去查即可。
当然这样做肯定一开始还是会很痛苦的,这也意味着在学完线性表之前你需要挤出更多的时间,花费更多的心血才能把必要的基础补上来。其实很多的方法技巧起到的效果都是少走弯路、省些力气,但不意味着过程会很轻松。但我相信如果回头去学这门上学期因为种种原因掌握得不是很好的课,可能重新看完教材半新个学期都过去了,也耽误了新课程的学习。学习新的知识,换一种新的学习思路,可能你的编程之路会就此开启一个新的起点。

需不需要先学某门语言再开始学?

不需要,只是说你掌握了更多编程语言的时候,可选的学习资料也会变多而已。算法可以理解为解决问题的具体步骤或策略,这个步骤不会因为你换了一种实现方式就失效了。例如定义单链表,用 C 写是这样的:

//定义单链表结点类型
typedf struct LNode
{
    ElemType data;    //数据域,存放数据
    struct LNode *next;    //指针域,指向后继结点
}LinkList,*List;

用 Python 写是这样的:

class ListNode:
    def __init__(self, val, next):
        self.val = val
        self.next = next

class LinkedList:
    def __init__(self):
        self.head = None

而且他们分别是基于面向过程和面向对象实现的,但它们都实现了链表结构。所以学习这门课并没有编程语言的限制,用 Java、Pascal、JavaScript 和世界上最好的语言 PHP 都可以。在具体用代码实现之前,首先要把算法的原理和一些典型的例子搞清楚。
如果是先开 C 后学数据结构的情况,掌握 C、C++ 面向过程和 STL 库就完全够用,可以参考我之前写过的博客《C++面向过程编程》《C++ STL 库快速上手》。如果你学了其他编程语言,也可以选择其他资料来进行学习,例如学过 C++ 可以选择邓俊辉的《数据结构(C++语言版)》,学过 Java 可以选择《算法(第4版)》。

代码都看不懂,无从下手咋办?

这个问题通常有两种情况,第一种是算法本身没弄懂,第二种是可能代码比较长被吓坏了。如果你没有先理清对应的算法原理,自然也无法知道不同的代码段需要实现什么功能。有的算法的实现思想是比较巧妙的,用代码实现起来不一定会很长,就很容易看的云里雾里。所以这种情况就需要回去再看看算法原理和一些例子,一个方法是可以先回到一组具体的测试数据上,甚至是特例也行。先假设此时算法的输入只有这一种情况,按照这个例子的步骤强行硬编码复现算法,接着在尝试第二个特例。我相信这样搞个两三组数据,你再把这些版本的代码改成通用的即可。
不过我觉得在课堂学习时,更常见的情况是后者,尤其是到了图章节的一些算法更是吓人。首先需要认识到复杂的功能都是用简单的功能排列组合而成,因此可以先把课本上的代码划分为很多代码段。例如合并两个线性表,你可以先实现获取表的长度、读取线性表第 i 个元素、判断元素 e 是否存在、插入链表这 4 个函数,然后通过排列组合得到代码:

void unionList(List &L1,List L2)
{
    int len_a,len_b;
    int i;
    ElemType e;
    
    len_a = ListLength(L1);    //获取表长
    len_b = ListLength(L2);
    for (i = 1; i <= len_b; i++)
    {
        GetElem(L2,i,e);    //获取线性表L2中的第i个元素
        if(!LocatElem(L1,e))    //判断元素e是否已经包含于线性表L1
        {
            ListInsert(La, ++len_a, e);    //若还未包含,执行插入插入
        }
    }
}

所以此时读懂这段代码之后,你再去读这 4 个函数的代码,我相信每一段代码肯定会比不单独封装成函数的版本更好读懂。我们再以一个看上去可能比较吓人的代码来举例,例如图章节中的 Dijkstra 算法,乍一眼可能很吓人,其实本质上是通过修改 3 个一维数组而实现的。我们把这段代码划分成多个代码段,就会发现其实并没有想象中的那么难:

中间加上一些判断条件和循环,就把这些代码段串起来了。然而如果你去看一个具体的 Dijkstra 算法的例子可能会觉得很啰嗦,但其实每一步的操作是一样的,用代码实现起来非常优雅,这也是这门课的魅力所在。

资料推荐

书籍推荐

书本的话我细读过《数据结构(C语言版)》(非“严版”)、《大话数据结构》、《数据结构高分笔记》(天勤),《剑指 Offer》读过部分章节,在魏梦舒出书之前看过部分博客版的《漫画算法:小灰的算法之旅》。另外提到的 3 本书我没读过,介绍《数据结构(C++语言版)》的原因是它的口碑很好,而《算法(第4版)》和《算法导论》则象征着权威。我按照我阅读或者翻过去时的主观感受,对这些书做的概括如下:

当然书不在多在于精,把这些书都买下来吃灰还不如挑一两本读得下去的书吃透。

《数据结构(C语言版)》——严蔚敏、李冬梅

我曾经以为这本书和《数据结构(C语言版)》(也就是“严版”数据结构)是同一本书的不同版本,但是当我某天翻了一下“严版”之后我发现这两本书差别很大。我个人认为这本书作为一本教材是合格的,编写的逻辑比较清晰完整,该有的要素都有,该有定义、图、表、例子也都不缺。我还是觉得需要一本教材来串起一学期的课程,所以这本书总体也是比较推荐的,这本书的第 2 版应该就是双色版那本了。
缺点的话我罗列几个我感受到的,首先是代码都是伪代码,很多都是无法运行的,如果无法自行实现算法还得自己找能运行的代码。其次是叙述的逻辑是完整,但是一些知识点的讲解也是比较模糊,例如 KMP 和外排序之类的一些章节,不过这些章节大部分书都讲的不好也情有可原。

顺便这边提一下经典的“严版”数据结构,这本教材被吐槽的非常严重,原因包括不限于堆砌概念、晦涩难懂、缺乏例子、伪代码难度且无法运行等等,和上面那本是完全不同的两本书。我个人推测这本书成为经典主要是年代问题,可能这本书被写于国内计算机教材较为缺乏的时候,所以现在看来槽点很多。感觉和谭先生的《C 语言程序设计》、王珊的《数据库系统概论》情况类似,当然这个只是我的个人推测,不一定对。

《大话数据结构》——程杰

这本书是我看的第一本讲数据结构的书,相比教材这本书语言非常生动,比较有趣能引人入胜。无论是样例和图例都很多,也有对代码的注解,尤其是本书的溢彩加强版增强了书中的很多图和排版的质量。总体而言我推荐绪论、算法、线性表、栈和队列、树这 5 个章节,在预习、教材看不下去、编程基础较差的情况下可以先看这本书,能够帮你打好基础、建立好对不同数据结构的认识。
我认为这本书的主要缺点在于,首先是能看出作者很尽力地在举例子让这些算法好理解,但是部分例子显得生硬起到了反效果,在图章节之后非常明显。接着是这本书在讲解某些算法的时候直接跳到了代码部分,没有对算法本身的介绍。这种讲法其实是有点问题的,因为算法可以用不同的语言、不同的代码来实现,而不能只立足于某一份代码本身。作者也有说这本书受到了“严版”的影响,内容上也确实有“严版”的影子。还有内容错误较多和代码是伪代码的问题,不过有勘误文档和配套的样例代码,可以自行下载作为补足。

《剑指 Offer》——何海涛

读过部分章节,非常经典且高质量的面试算法题集,很多的企业的面试题都出自这本书里面,还有一本《专项突破版》和本书内容互补。关于题目本身可以看书,因为书中给的讲解非常详细,一题多解能开拓思路,还有很多对异常数据的讨论,配套的代码也很符合规范。

推荐在学习的时候去 leetcode 上刷题,可以尝试用自己擅长的编程语言先尝试做一遍。最重要的是,写完后一定要去评论区和题解区,因为这几个地方会有题目的多解和巧解,很多都是脑洞大开的绝活。

《数据结构高分笔记》——天勤论坛

这本身是准备计算机考研专业课的复习用书,相比于其他书这本更倾向于解题。可以看出作者没有无聊地堆砌概念和代码,对于知识点的讲解是很细腻的,不同难度的内容有相应的讲解侧重点。选用的题目也非常经典,且能在解题的过程中渗透作者的思维方式,我在读这本书的时候也弄懂了一些之前没理解的问题。针对没有学过编程的读者还有入门章节,用较短的篇幅交代好考试需要的 C 语言基础。
这本书每年都会对内容进行更新,以融入新的大纲和去年的考题。我手上有 21 版和 23 版的,对比之下大部分内容都是差不多的,只是题目和大纲变化会产生一些小的变化。如果以学习数据结构本身为目的的话,近几年的版本都可以使用,等到了需要备考 408 时再来一本王道出的书即可。

谈到这个就不得不提一嘴王道论坛的书,我个人认为王道的特点是知识点讲解非常简单有限,但是题目量大、题型较广,B 站能直接搜到免费的配套课程。我觉得讲解看天勤的书,然后刷题用王道的书是一个不错的搭配。

《漫画算法:小灰的算法之旅》——魏梦舒

程序员小灰是 CSDN 上的博主,写了很多用漫画讲解算法疑难点的博客,我在看不懂一些算法时有参考学习过对应的内容。在他的漫画中的主角是一只仓鼠,每篇博客开头都会以某种奇怪的理由向老板请假,实则是参加其他公司的面试,每次都是被面试官问倒后引出博客的主题。通过漫画讲解的算法化抽象为具体,使得很多疑难问题变得好理解,因此可以用作者的博客或这本书对具体的一些算法进行学习。

《数据结构(C++语言版)》——邓俊辉

这本书我只是翻过,没有详细读,书本和对应的课程口碑都很好,具体可以参考豆瓣读书中的一些评论。这本书技巧性比较高,看上去读起来会比较过瘾,讲解和代码都是紧密贴合 C++ 的语法和编程技巧。因此这本书也不适合初学使用,熟悉 C++ 的语法之后再看效果更佳。

这本书感觉还是很有意思的,如果我后面有细读会回来更新这部分内容。

《算法(第4版)》

经典且权威的数据结构与算法书籍,无非就是《算法(第4版)》和《算法导论》。这本书我买来了在吃灰,是 600+ 页的大块头,书中的算法使用 Java 实现。就我翻过感受而言,内容上基础而全面,例子和图片非常多,代码实现得很优雅。不过我没有细看过,更加具体的评价可以参考豆瓣读书

《算法导论》

这本书绝对是数据结构与算法方面“圣经”级别的存在,里面充满了各种算法的数学证明,阅读难度很大,搞定这本书就能完全掌握数据结构了。

课程推荐

我大一在学数据结构的时候就试听过很多网课,但是很难找到让我比较好接受的,所以就直接啃书学了。下面推荐的这几门课我或多或少都有听一些,也都是大家公认的好课。

数据结构-浙江大学

中国大学 MOOC 上数据结构的经典慕课,由浙大的陈越教授和何钦铭教授主讲。课程体系很完善,是公认的好课,难度也比较大一些。课程链接——数据结构

数据结构与算法基础-青岛大学王卓

这门课是王卓老师自己录制后发布在 B 站上的,讲解得非常清晰易于理解。由于王老师没有讲解代码,可以用这些视频预习复习,结合课本的代码样例进行学习。课程目录——《数据结构与算法基础》教学视频目录

数据结构-王道计算机考研

王道论坛在 B 站上发布了的旧版《数据结构考研复习指导》配套课程,是公认的考研好课。相对于其他以教学为目的的课程,专为考试制作的课会显得更加直接而高效,因为考试的核心目的就是拿分。课程链接——王道计算机考研-数据结构

我的博客笔记

我自己在学习的时候也有根据这些资料的内容总结了一些博客,连接是——数据结构博客清单

posted @ 2023-02-17 03:51  乌漆WhiteMoon  阅读(1610)  评论(5编辑  收藏  举报