本文是通过观看尚硅谷的数据结构和算法视频总结的笔记,如有错误地方,欢迎指正。
代码部分使用的语言是C++
稀疏数组
1. 使用情况:
当一个数组中的大部分元素为 0 或者同一值时,可采用稀疏数组进行压缩。
2. 操作方法:
(1)先记录数组中一共有多少行、多少列和多少个不同的值;
(2)将不同值的元素的行数、列数和值用一个小数组记录;
(3)小数组,即稀疏数组行数随原数组的值进行变化,列数共三列,分别表示行、列、对应的值;
(4)如果原数组共有 n 个不同的值,则小数组的行数为 n+1,因为小数组的第一行存储总的行数、列数和不同值个数,第二行开始记录每个在原数组不同值对应的行和列;
(5)将稀疏数组保存到磁盘中;
3. 代码部分
- 稀疏数组实现代码
栈(stack)
1.特点
(1)先入后出的有序列表
(2)栈底固定不变,栈顶随着数据输入输出而变化
(3)入栈图
-
(4)出栈图
-
2.使用场景
(1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
(2)处理递归调用:除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
(3)表达式的转换
(4)二叉树的遍历
(5)图形的深度优先搜索法(dfs)
3. 表达式
(1)前缀表达式(波兰表达式)
- 特点:运算符均位于操作数之前。
- 例如:- × + 3 4 5 6
- 求值过程:从右向左扫描表达式,遇到数字,压入数字栈,遇到运算符,弹出栈顶两个数字进行运算,将结果压入数字栈;重复操作。
(2)中缀表达式
- 即常见的运算表达式
- 例如:(3+4)×5-6
(3)后缀表达式(逆波兰表达式)
- 特点:运算符位于操作数之后
- 例如:3 4 + 5 × 6 –
正常表达式 | 逆波兰表达式 |
---|---|
a+b | a b + |
a+(b-c) | a b c - + |
a+(b-c)*d | a b c – d * + |
- 求值过程:从左至右扫描表达式,遇到数字,压入数字栈,遇到运算符,弹出栈顶的两个数字,计算结果,并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果;
- 【逆波兰计算器】中缀表达式转后缀表达式
队列(queue)
1. 特点
(1)先入先出的有序列表
(2)可以用数组或链表实现
(3)队列的顶端、低端下标随着数据输入、输出而变化
-
2. 模拟队列代码
链表
1.特点
(1)以节点的方式进行存储,属于链式存储
(2)每个节点含有 data 域和 next 域,next 用于指向下一个节点
2.单链表
(1)带头节点链表示意图
-
(2)单链表代码
- 【面试题】查找单链表中的倒数第k个结点
- 【面试题】单链表的反转
3.单向环形链表
(1)约瑟夫环问题(Josephu)
问题描述:
设编号为1,2,...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
-
- 约瑟夫环代码
4.双向链表
(1)不需要辅助节点就可以实现自我删除
(2)用双向链表实现单向链表
- 双向链表代码
递归
1.解决问题:
(1)数学问题如:八皇后,汉诺塔,阶乘,迷宫,球和篮子等;
(2)各种算法:快速排序,二分查找,分治算法等;
(3)将用栈解决的问题变成用递归,使代码简洁。
2.注意事项
(1)每次执行一个方法时,就创建一个新的受保护的独立空间(栈空间);
(2)归必须向退出递归的条件逼近,否则就是无限递归;
(3)当一个方法执行完毕,或者遇到return,就会返回,遵守:谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
3.走迷宫问题
(1)示意图如下
-
(2)【递归】走迷宫,求最短路径
4.八皇后问题
(1)示意图如下
-
(2)【递归】八皇后问题(回溯算法)
排序算法
1.内部排序
(1)特点:将需要处理的所有数据都加载到内部存储器中进行排序。
(2)冒泡排序
- 基本思想:对待排序序列从前向后,依次比较相邻元素的值,若发现不符合条件则交换,使值大/小的元素逐渐从前移向后部,就像水底下的气泡一样逐渐向上冒出。
- 优化:在排序过程中设置一个标志flag,判断元素是否进行过交换,如果一趟比较下来没有进行过交换,就说明序列是有序的,减少时间。
(3)选择排序
- 基本思想:每次从未排序区间中选择出一个最小/大的数与未排序区间的第一个位置进行交换,重复上述过程。
-
(4)插入排序
- 基本思想:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
-
(5)希尔排序(插入改进版)
- 基本思想:记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止;
-
- 【希尔排序】使用交换法
- 【希尔排序】使用移动法
(6)快速排序(冒泡改进版本)
- 基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列;
-
- 【快速排序】代码部分
(7)归并排序
- 基本思想:采用分治策略
-
- 【归并排序】采用分治,代码实现
(8)基数排序(桶排序扩展)
- 基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
- 是稳定性排序,即不改变元素的相对位置,需要借助二维数组,属于空间换时间
-
- 【基数排序】代码实现
2.外部排序
使用情况:数据量过大,需要借助外部存储
3.常用排序复杂度对比
查找算法
1.线性查找:逐一对比
2.二分查找:前提数组有序
3.插值查找
(1)mid 是自适应的,即根据需要查找的值和左右边界而确定mid(公式)
-
- 【插值查找】代码实现
(2)适应情况:数据量大,值分布均匀
4.黄金分割法(斐波拉契查找法)
(1)黄金分割值:0.618
-
(2)mid是位于黄金分割点附近,即 mid=low+F(k-1)-1
哈希表(散列)
1.特点
通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数称为散列函数,存放记录的数组称为散列表。
2.模拟哈希表代码
- 哈希表代码实现
树
1.存储方式分析
(1)数组存储
- 优点:通过下标方式访问元素,速度快。
- 缺点:如果要查找或插入具体某个值,(按一定顺序)会整体移动,效率较低。
(2)链式存储
- 优点:在一定程度上对数组存储方式有优化,比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好。
- 缺点:在进行检索时,效率仍然较低,比如检索某个值,需要从头节点开始遍历。
(3)树存储
- 优点:能提高数据存储,读取的效率,比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
2.二叉树
(1)树示意图如下:
-
(2)二叉树:每个节点最多只能有两个节点;
(3)满二叉树:二叉树的所有叶子节点都在最后一层,并且结点总数为 2^n -1(n为层数);
-
(4)完全二叉树:二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续;
-
3.二叉树的遍历
(1)前序遍历:先输出父节点,再遍历左子树和右子树
(2)中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
(3)后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
(4)二叉树遍历代码实现
图(graph)
1. 概念
(1)由顶点(vertex)集合V、边(edge)集合E组成;
(2)有向图:点到点之间是有方向的图;
(3)无向图:点到点之间是没有方向的图;
(4)带权图:图的边带有权/值的图;
(5)环:图中含有一条从一个顶点到它自身的边;
(6)圈:只在有向图中有意义,且路径长至少为1;
(7)路径:一个顶点序列,路径的长是路径上的边数;
(8)连通性:在一个无向图中从每一个顶点到每一个其他顶点都存在一条路径,称该无向图是连通的,有向图是强连通的;若一个有向图不是强连通的,但是其基础图(去掉方向的图)是连通的,称该有向图为弱连通的;
(9)完全图:每对顶点间都存在着一条边的图;
2. 表示方式
(1) 二维数组 —— 邻接矩阵
- 表示图形中顶点之间相邻关系的矩阵,对于每一条边,值为 1,否则为 0;
- 邻接矩阵需要为每一个顶点分配 n 个边的空间,造成一定的空间损失,所以适用于稠密的图;
(2)链表 —— 邻接表
- 只存放存在的边,由数组+链表组成;
- 如图,标号为 0 的结点的相关联结点为 1,2,3,4;
-
- 在实际应用中顶点是名字而不是数字,但是这些名字在编译时是未知的,因此需要提供从名字到数字的映射,可以适用散列表;
- 在散列表中对每个顶点存储一个名字和一个范围在 1~|V| 的内部编号,编号在图被读入的时候指定;在每条边被输入时,通过查看其两个顶点是否在散列表中的方法,检查两个顶点是否都指定了一个数;
- 如果在,使用这个内部编号,否则将下一个可用的编号分配给该顶点,并将顶点的名字和对应的编号插入散列表中。
3. 深度优先搜索(dfs)
(1)从初始访问结点出发,首先访问第一个邻接结点,然后以这个被访问的结点作为初始结点,访问其第一个邻接结点,重复操作;
(2)访问策略:优先纵向挖掘深入,是一个递归过程;
(3)搜索步骤(如下图):
-
- 1 先访问初始结点,假设为 A,并且标记A为"已访问";
- 2 查找结点 A 的第一个邻接结点 B (指链表中存放的邻接结点);
- 3 若 B 存在,继续执行下一步;若 B 不存在,回到第一步,从 A 的下一个结点继续查找;
- 4 若 B 未被访问,对 B 进行深度优先搜索(即将 B 作为初始结点,再次从第一步开始执行,这时候的初始结点为 B );
4. 最短路径 —— 广度优先搜索(bfs)
(1)按层搜索,需要使用队列保持访问过的结点顺序;
(2)搜索步骤:
- 1 访问初始结点 A,并且标记 A 为已经访问;
- 2 结点 A 入队列;
- 3 当队列非空时,继续执行,否则对 A 结点的搜索结束;
- 4 出队列,得到队头结点 U;
- 5 查找头结点 U 的第一个邻接结点 B;
- 6 若 B 不存在,返回步骤 3;否则循环执行下面三个步骤;
- 6.1 若结点 B 未被访问,则标记为已访问;
- 6.2 结点 B 入队列;
- 6.3 查找结点 U 的在邻接结点 B 之后的下一个邻接结点,转到步骤 6;
(3)【bfs】相关题目代码
5. 最短路径 —— Dijkstra 算法
(1)按阶段进行,在每个阶段,Dijkstra 算法选择一个顶点 v,