偶然的机会,在bilibli上看到了郝斌老师教的《数据结构入门》,课程录制时间是2009年,也就是10年前。虽然如此久远,但是我从听第一节课开始就深深被郝斌老师所折服,从未见过谁可以将这门枯燥的课教授地如此生动有趣(想当年我的数据结构只考了61分......)。于是花了几个星期的晚上,把这门课给听完了,相关的代码也跟着老师敲了一遍,笔记也整理了一下,并自己绘制了一些精美的示意图来辅助理解。代码部分不完全跟老师课堂上一致,但思路基本一致。这里分享给大家。
另外在cpp文件中,添加了大量的注释,可以直接当做笔记阅读。
全部笔记和代码见我的github或gitee:
https://github.com/beyondguo/data_structure_learning https://gitee.com/guobiyang/data_structure_learning
课堂笔记如下:
数据结构定义: 我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上为实现某个功能(比如查找、删除、排序)而执行的相应操作,这个操作就是算法。 说白了,就是如何把一个个数据,以及数据之间的关系存储到内存中。
衡量算法的标准:
狭义的算法跟数据存储有关; 广义的算法跟数据存储无关;
何谓泛型: 即某种技术,可以使得不同的存储方式,执行的操作是一样的。
同一种逻辑结构,无论其内部的存储结构是什么样子,都可以对它执行相同的操作。
预备知识:
把所有的节点用一根线连起来的结构。 连续存储→数组 优点:存取速度快 缺点:插入删除慢,事先必须知道数组长度,需要大块连续内存 离散存储→链表 优点:插入删除快,不用事先定义长度 缺点:存取速度慢
线性结构的两种常见应用:栈,队列。
笔记见程序。
首节点:第一个有效节点 尾结点:最后一个有效节点 头结点 :数据类型和首节点一样。首节点之前的一个节点,不存放有效数据,其作用是为了方便链表的操作。 头指针:指向头结点的指针变量,是头结点的地址 尾指针:指向尾结点的指针变量
要对一个链表进行处理,需要知道的参数:头指针。 只需要这么一个玩意儿就够了,就可以知道所有的数据,以及链表的长度等信息。
链表的分类: 单链表 双链表:每一个节点有两个指针域 循环链表:能通过任何一个节点,找到所有其他节点 非循环链表
定义:一种可以实现先进后出的存储结构,类似于箱子。
分类: 静态栈:以数组为基本内核 动态栈:以链表为基本内核
动态栈的算法: 就是把链表的一些功能给砍掉,例如不允许在头部插入,不允许删除中间的元素等等。 出栈 压栈(入栈)
栈的应用:
定义:一种够可以实现“先进先出”的数据结构 分类: 链式队列 静态队列 静态队列通常都必须是循环队列
静态队列需要搞清楚的几个点:
两个,front和rear,front(前台)掌管出对,rear掌管入对,在不同场合中:1). 队列初始化:两者都为零
2). 队列非空:front代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个位置
3). 队列空:front和rear的值相等,但不一定为零
1.把新增元素添加到当前r的位置
2.r = (r+1)%maxlen
出队:
f = (f+1)%maxlen
空:front与rear的值相等,就一定为空
满:这个有点难判断,因为满的时候,似乎f也跟r相同了。
这个时候有两种办法处理:
一种是添加一个参数,记录元素个数。
第二种就是,不让队列的所有位置都可以放值,而是始终空出一个位置,让r的永远是一个空位置。
这样,满的状态就是r==(f-1)%maxlen
或者f==(r+1)%maxlen
。队列的应用: 所有和时间有关的操作都有队列的影子。
递归的定义: 一个函数自己调用自己
递归要满足的三个条件:
循环和递归的对比:
递归:
循环:
递归的例子:
递归的应用:
深度:根节点到最底层节点的层数
度:子结点的个数,就是某节点的度
如图,红色点为转化成完全二叉树需要增加的点,黄色框框里的点可以删除。 但是这样还是会额外添加很多很多垃圾节点。 优点:查找某节点的父节点、子节点很快。 缺点:十分耗内存
可以发现,转过来的二叉树一定没有第一个右分支。
遍历:(可以发现,这些都是递归)
!!已知两种遍历序列推出原始二叉树: 已知一个遍历序列,是无法推出原始二叉树是样子的。 但是,已知先序和中序,或者已知中序和后序,是可以推出来的。 (但是,已知先序和后序,也是无法推出来的)
实例自查: 先序:ABCDEFGH 中序:BDCEAFHG >> 后序:DECBHGFA
先序:ABDGHCEFI 中序:GDHBAECIF >> 后序:GHDBEIFCA
根据后序、中序求二叉树和先序,也是类似的。
主要的思想就是: 通过先序或者后序确定每个树的根节点,然后就可以利用中序把左右树分开,得到新树之后,就又可以通过先序或者后序确定根节点。以此类推。
查找:
折半查找
排序:
快速排序: 总体思想: 每次确定第一个数的排序后的位置,把列表一分为二,大的在右边,小的在左边; 然后将两边的数列进行上面同样的操作。
例如,初试状态:我们给头尾各设立一个指针,L和H; 和一个临时变量val保存第一个元素的值。 然后我们设定一个规则来移动H和L,以达到我们期望的效果。
怎么移动呢? 我们要保证,L指向的位置的左边,一定都小于9; H指向的位置的右边,一定都大于9; 如果可以这样,那么当H和L移动到同一位置时,那个位置就是9应该的位置。
具体如下: 先看H从右到左扫描,如果指向的值小于9,那么就让这个元素赋值到L所指元素; 然后再移动L,现在L所指元素为7,7小于9,fine,接着移动,发现0,8都小于9,所以还要移动,移动到10发现终于有一个比9大了,那么这个10就应该被转移到H所指位置; 然后H再次出动,从右到左寻找比9小的值,于是就移动到了2,把2赋值给L位置; 然后L再移动,经过-5,碰到了H。 终于,本轮游戏结束,9到了目标位置,同时左右边分得清清楚楚(虽然还是无序的,但是整体是有序的)。
以上就是全部的笔记了。
很可惜,郝斌老师后面没有继续录制“图”的内容。
最后再次向大家墙裂推荐如今“不知去向”的郝老师当年在网上免费分享的视频,正一年一年造福着考研、期末、找工作的同学们。
郝斌的视频bilibili上很多,参考链接: https://www.bilibili.com/video/av6159200