文章目录
- 前言
- 1. 数据结构概论、算法设计与分析
- 1.1 数据结构三要素?
- 1.2 算法的基本概念?
- 1.3 什么是时间复杂度?
- 2. 线性表
- 2.1 链表结构和顺序存储结构的区别?
- 2.2 单链表和双链表的区别?
- 2.3 头指针和头结点的区别?
- 3. 树
- 3.1 最大堆和最小堆
- 3.2 二叉排序树?
- 3.3 平衡二叉树?
- 3.4 红黑树
- 3.4.1 平衡树和红黑树的区别
- 3.4.2 为什么红黑树的插入、删除和查找如此高效?
- 3.4.3 红黑树为什么要保证每条路径上黑色结点数目一致?
- 3.3 B树
- 3.4 B+树
- 3.5 满二叉树的定义?
- 3.6 完全二叉树的定义?
- 4. 图
- 4.1 图的相关概念?
- 4.2 邻接矩阵与邻接表的区别?
- 4.3 最小生成树的算法?(Prim,Dijkstra)
- 4.4 什么时候最小生成树唯一?
- 4.5 Dijkstra算法与Floyd算法的区别?
- 4.6 拓扑排序的概念以及实现?
- 5. 排序
- 6. 其他
- 6.1 贪心算法和动态规划已经分治法的区别?
- 6.2 栈和堆的区别?
- 6.3 迭代和递归的区别?
前言
思来想去,还是打算自己写一个总结,这样也加深一下印象。很多时候我们都是嫌麻烦,而不愿做,只要做了就能踏出舒适圈。但我想说的是,数据结构与算法这一部分,重点还是在于刷题,知识点的背诵不像OS和计网那样,所以,man,你还是要坚持刷题。不多说了,下面正式开始。
1. 数据结构概论、算法设计与分析
1.1 数据结构三要素?
-
数据的逻辑结构
逻辑结构是指数据元素之间的逻辑关系,包括集合(平等)、线性结构(一对一)、树形结构(一对多)、图形结构(多对多)。 -
数据的存储结构
存储结构是指数据结构在计算机中的表示,也称物理结构。数据的存储结构主要有顺序存储、链式存储、索引存储、散列存储。(1)顺序存储结构:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
优点:可以实现随机存储,每个元素占用最少的存储空间。
缺点:只能使用相邻的一整块存储单元,因此可能产生较多的外部碎片。(2)链式存储结构:不要求逻辑上相邻的元素在物理位置上也相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。
优点:不会出现碎片的现象,能充方利用所有存储单元。
缺点:每个元素因存储指针而占用额外的存储空间,且只能实现顺序存储。(3)索引存储结构:在存储元素信息的同时,还建立附加的索引表,索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。
优点:检索速度快。
缺点:附加的索引表额外占用存储空间。另外,增加和删除数据也要修改修改索引表,因而会花费较多的时间。(4)散列存储结构:根据元素的关键字直接计算出该元素的存储地址,又称为哈希(Hash)存储。
优点:检索、增加和删除结点的操作都很快。
缺点:若散列函数不好,则可能出现元素存储单元的冲突,而解决冲突会增加时间和空间开销。
散列和索引有点像,区别在于散列多了一个哈希计算,目的是为了节省空间,二者经常混用,只要记得散列存储结构的存储地址和关键字存在某种映射关系即可。
1.2 算法的基本概念?
五个重要特征:
(1)有穷性:有限的步骤
(2)确定性:每条指令必须有确切的含义
(3)可行性:每一步都是通过执行有限次数完成的
(4)输入:零个或多个输入
(5)输出:至少有一个或多个输出
好的算法需要考虑的:正确性、可读性、健壮性、高效率与低存储量需求。
1.3 什么是时间复杂度?
时间复杂度是用来衡量算法执行时间的一种度量方式。它表示随着输入规模的增加,算法执行时间的增长速度。常用的时间复杂度有O(1)、O(log n)、O(n)、O(n log n)、O(n2)等,一般算法的时间复杂度记为:T(n) = O(f(n))
,其中O的含义是T(n)的数量级。
举个例子,如果一个算法的时间复杂度为O(n),那么当输入规模为n时,该算法的执行时间与n成正比。如果输入规模增加一倍,那么执行时间也会增加一倍。
通过分析算法的时间复杂度,我们可以评估算法的效率和性能,并选择合适的算法来解决问题。一般来说,我们希望选择时间复杂度较低的算法,因为它们在处理大规模数据时更高效。
2. 线性表
2.1 链表结构和顺序存储结构的区别?
顺序存储在逻辑上和物理位置上是都是相邻的。
链式存储逻辑上相邻,物理位置上不相邻。
-
顺序存储结构:
读取方便O(1)
插入删除需要移动大量元素(平均需要移动半个表长的元素)
空间分配:一次性
存储密度=1 -
链式存储结构:
读取不方便,需要遍历O(n)
插入删除方便,只需要改变指针
空间分配:在需要的时候分配
存储密度<1
存储密度=(结点数据本身所占的存储量)/(结点结构所占的存储总量)
2.2 单链表和双链表的区别?
- 单链表:只能向后访问,不能向前访问。
- 双链表:可以双向遍历。
2.3 头指针和头结点的区别?
- 头指针:是指向链表第一个结点的指针,若链表有头结点,则指向头结点。头指针是必须要有的,具有标识作用,代表这个链表的开头
- 头指针:是为了方便和统一操作,方便插入和删除第一个元素,可有可不有
3. 树
3.1 最大堆和最小堆
堆是完全二叉树,根据结点和左右孩子的大小关系,分为最大堆和最小堆,最大堆的结点比左右孩子大,最小堆的结点比左右孩子小。插入和删除的复杂度为O(logn)。
3.2 二叉排序树?
二叉排序树(二叉查找树、二叉搜索树):比根节点大的放在右子树,小的放在左子树,对二叉搜索树进行中序遍历可以的得到一个递增的有序序列。
3.3 平衡二叉树?
平衡二叉树是在二叉搜索树的基础上,保证每个节点左子树和右子树的高度差小于等于1即可。适用于插入删除比较少,但是查找比较多的情况。
3.4 红黑树
主要性质:
(1)节点是红色或者黑色,没有其他的颜色
(2)根节点是黑色
(3)每个叶节点(NIL节点,空节点)是黑色的。
(4)不存在两个连续的红色节点,即父节点和子节点不能是连续的红色
(5)从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点
优点:平均查找,添加输出效果都还不错
有了五条性质,才有一个结论:通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡。
查找时间复杂度O(logn),插入和删除时间复杂度O(logn)
3.4.1 平衡树和红黑树的区别
- AVL树是高度平衡的,频繁的插入和删除,会引起频繁的调整以重新平衡,导致效率下降
- 红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转,调整时新插入的都是红色。
3.4.2 为什么红黑树的插入、删除和查找如此高效?
- 插入、删除和查找操作与树的高度成正比,因此如果平衡二叉树不会频繁的调整以重新平衡,那它肯定是最快的,但它需要频繁调整以保证平衡
- 红黑树算是一种折中,保证最长路径不超过最短路径的二倍,这种情况下插入最多两次旋转,删除最多三次旋转,因此比平衡二叉树高效。
3.4.3 红黑树为什么要保证每条路径上黑色结点数目一致?
- 为了保证红黑树保证最长路径不超过最短路径的二倍
- 假设一个红黑树T,其到叶节点的最短路径肯定全部是黑色节点(共B个),最长路径肯定有相同个黑色节点(性质5:黑色节点的数量是相等),另外会多几个红色节点。性质4(红色节点必须有两个黑色儿子节点)能保证不会再现两个连续的红色节点。所以最长的路径长度应该是2B个节点,其中B个红色,B个黑色。
3.3 B树
m阶B-Tree满足以下条件:
-
每个节点最多拥有m个子树
-
根节点至少有2个子树
-
分支节点至少拥有m/2颗子树(除根节点和叶子节点外都是分支节点)
-
所有叶子节点都在同一层、每个节点最多可以有m-1个key,并且以升序排列
如:一个3阶的B树
在本例中每一个父节点都出现在子节点中,是子节点最大或者最小的元素
每个叶子节点都有一个指针,指向下一个数据,形成一个有序链表。
3.4 B+树
B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。
为什么说B+树查找的效率要比B树更高、更稳定;我们先看看两者的区别:
- B+树的非叶子节点不保存关键字记录的指针,只进行数据索引,使得B+树每个非叶子节点所能保存的关键字大大增加;
- B+树叶子节点保存了父节点的所有关键字记录的指针,所有数据地址必须要到叶子节点才能获取到。所以每次数据查询的次数都一样;
- B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针(叶子节点之间有指针连接,MySQL是双向指针)。
- 非叶子节点的子节点数=关键字数
B+树相比于B树,在文件系统,数据库系统当中,更有优势,原因如下:
-
B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说I/O读写次数也就降低了。 -
B+树的查询效率更加稳定
由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。 -
B+树更有利于对数据库的扫描
B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以对于数据库中频繁使用的range query,B+树有着更高的性能。
3.5 满二叉树的定义?
一颗高度为h,且含有2h-1个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点。
3.6 完全二叉树的定义?
高度为h、有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,成为完全二叉树。
4. 图
4.1 图的相关概念?
图结构中结点之间的关系是任意的,图中任意两个节点都可能有关系。
图分为有向图和无向图
有向图的基本算法:拓扑排序、最短路径算法(Dijkstra算法和Floyd算法)
无向图的基本算法:最小生成树(Prim算法,Kruskal算法)、图的遍历(DFS、BFS)
4.2 邻接矩阵与邻接表的区别?
图的存储结构:
- 邻接表:(链式存储结构)由单链表的表头形成的顶点表,和单链表其余节点形成的边表两部分组成;一般顶点表存放顶点信息和指向第一个边节点的指针。适合用于稀疏图。
- 邻接矩阵:(顺序存储结构)用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。适合用于稠密图。
4.3 最小生成树的算法?(Prim,Dijkstra)
- Prim
- 将v0到其他顶点的所有边当作侯选边
- 重复以下过程,直到所有的顶点被并入树中:
2.1 从侯选边中挑选出最小的边输出,并将与该边的另一端顶点并入树中
2.2 考查所有剩余的顶点,选取与这棵树相邻的边的最短边
时间复杂度为O(n^2),适合用于稠密图
- Kruskal
- 将图中边按照权值从小到大排序
- 然后从最小边开始扫描
- 并检查当前边是否为候选边,即是否会构成回路
适用于稀疏图
4.4 什么时候最小生成树唯一?
所有权值都不相同,或者有相同的边,但是在构造最小生成树的过程中权值相等的边都被并入到最小生成树中的图,其最小生成树是唯一的。
4.5 Dijkstra算法与Floyd算法的区别?
Dijkstra
思路:
(1)集合S存放图中一找到最短路径的顶点
(2)集合U存放途中剩余顶点
算法执行过程:
(1)初始时,S只包含原点,即:S={v},v的距离为0。U包含除v以外的其他顶点。即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
(2)从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v大k的最短路径长度)。
(3)以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
(4)重复步骤(2)和(3)直到所有顶点都包含在S中。
Floyd
解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
时间复杂度O(n3),空间复杂度O(n2)
算法描述:
(1)从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权值为无穷大。
(2)对于每一对顶点u和v,看看是否存在一个顶点w,使得从u到w再到v比已知的路径更短。如果是更新它。
4.6 拓扑排序的概念以及实现?
AOV网:一种以顶点表示活动,以边表示活动的先后次序且没有回路的有向图。反映出整个工程中各个活动之间的先后关系的有向图。
(1)从有向图中选择一个没有前驱(入度为0)的顶点输出
(2)删除1中的顶点,并且删除从该顶点发出的全部边
(3)一直重复
5. 排序
排序直接看我写的另一篇吧,基本囊括了比较重要的排序算法。
Leetcode 912. 排序数组 —— 总结十大排序
6. 其他
6.1 贪心算法和动态规划已经分治法的区别?
- 贪心算法:顾名思义就是做出在当前看来是最好的结果,它不从整体上加以考虑,也就是局部最优解。贪心算法从上往下,从顶部一步一步最优,得到最后的结果,它不能保证全局最优解,与贪心策略的选择有关。
- 动态规划:把问题分解成子问题,这些子问题有可能会重复,于是就要记住过往,减少重复计算。
- 分治法:将原问题划分成n个规模较小而结构与原问题相似的子问题。递归地解决这些子问题,然后再合并其结果,就得到原问题的解。(各子问题独立)
6.2 栈和堆的区别?
- 栈:由编译器自动分配和释放,存放函数的参数值、局部变量等,函数的递归也有用到栈。
- 堆:由程序员自己分配和释放,如果程序员不自己释放,程序结束后,由操作系统释放。(现如今一些语言拥有自动垃圾回收机制)
6.3 迭代和递归的区别?
递归和迭代两者完全可以互换。不能完全决定性地说迭代的效率比递归的效率高
递归算法:
优点:代码简洁、清晰,并且容易验证正确性。
缺点:它的运行需要较多次数的函数调用,如果调用层数比较深,需要增加额外的堆栈处理(还有可能出现堆栈溢出的情况),比如参数传递需要压栈等操作,会对执行效率有一定影响。
但是,对于某些问题,如果不使用递归,将是极端难看的代码。再编译器优化后,对于多次调用的函数处理会有非常好的效率优化,效率未必低于循环。
迭代算法:
优点:速度快,结构简单。
缺点:并不能解决所有的问题。有的问题适合使用递归而不是迭代。如果使用循环并不困难的话,最好使用循环。
循环:例如,斐波那契额数列,采用循环来实现,不仅简洁,而且效率高,而采用递归,随着问题规模的增加,效率就越低。
递归:例如,汉诺塔问题,用递归来实现,不仅代码简洁,而且清晰,方便查找出问题。