算法笔记
C1-2
算法的五个重要特性:
- 输入
- 输出
- 有穷性
- 确定性
- 可行性
算法时间复杂度的含义:算法执行过程中所需要的基本运算次数
插入排序O(n^(1-2)):从第二个元素开始,每次将当前元素和这个元素之前的元素一一比较,小则交换,大则停止。最后得到一个升序
C3
三种记号的定义:
g(n)与机器和语言无关
- O(g(n)) = { f(n): 存在正常量 c 和 n0 , 使得对所有 n ≥ n0 有0 ≤ f(n) ≤ cg(n) }
- Θ(g(n)) = { f(n): 存在正常量 c1 , c2 , 和 n0 , 使得对所有 n ≥ n0 , 有0 ≤ c1g(n) ≤ f(n) ≤ c2g(n) }
- Ω(g(n)) = { f(n): 存在正常量 c 和 n0 , 使得对所有 n ≥ n0, 有0 ≤ cg(n) ≤ f(n) }
当渐进记号出现在等式左边,则表示无论等号左边的匿名函数如何选择,总有办法选取等号右边的匿名 函 数 使 等 式 成 立
限界函数的性质:
- 传递性:f(n)=O(g(n)) and g(n)=O(h(n)) ------->f(n)=O(h(n))
- 自反性:f(n)=O(f(n))
- 对称性:只有Θ
- 转置对称性(对称性是Θ,转置是O和Ω)
相关定理:
- 若 是一个 n 的 m 次项式,其中 ai 为常量,i = 0,…,m, 且 am > 0,则有 A(n) = Θ(n ^m)
- 估算复杂性(函数阶的大小)的定理:
- (lgn)^x < n
- n^x < 2^n
- O(2^n ) < O(n!) < O(n^n )
- 如果 d(n) 是 O(f(n)),e(n)是 O(g(n)),那么 d(n)+e(n) 是 O(f(n)+g(n)) —— 加法法则
- 如 果 d(n) 是 O(f(n)) , e(n) 是 O(g(n)) , 那 么 d(n)e(n) 是 O(f(n)g(n)) —— 乘法法则
C4
分治法基本思想:当问题规模比较大而无法直接求解时,将原始问题分解 为几个规模较小、但类似于原始问题的子问题,然后递归地求解这 些子问题,最后合并子问题的解以得到原始问题的解。
分治策略遵循三个步骤:
- 分解
- 解决:递归地解各个子问题
- 合并:将子问题的结果合并成原问题的解
因为分治算法的计算时间表达式往往是递归式,所以要化简递归式,已得到形式简单的限界函数。
化简递归式的方法:之前先做合理简化,通常忽略上下取整函数
T(n)=2T(⌊n/2⌋)+n,猜测其解为 T(n)=O(nlgn)
- 代入法:
- 猜测解的形式
- 用数学归纳法找出使解真正有效的常数,证明正确
- 递归树法
- 主方法:T(n)=aT(n/b)+f(n),三种情况见书
C5
概率分析:使用关于输入分布的知识或者对其做的假设,然后分析算法,计算出一个平均情况运行时间(有不同输入,对每一个输入不管运行多少次算法有个固定的结果,确定型算法)
- 平均运行时间是算法对所有可能情况的期望运行时间,与输入数据的概率分布有关
随机算法:如果一个算法的行为不仅由输入决定,而且也由 一个随机数生成器产生的数值决定,则称这个算法是随机化 的
- 我们将一个随机算法的运行时间称为期望运行时间
均匀随机排列就是 n!个排列
指示器随机变量
- 定义:给定一个样本空间S和一个事件A,那么事件A对应 的指示器随机变量 I{A} 定义为:I{A}=1(如果A发生),0(如果A不发生)
- 目的:为 了 建 立 概 率 和期望 之间的联系,用于 实现概率与期望之间的转换
C6-9
堆排序算法:
- O(nlgn)
- 原址排序算法
最大堆:除 根 节 点 之 外 的 每 个 节 点 i , 有 A[PARENT(i)] ≥ A[i],即某结点的值不大于其父结点的值,故 堆中的最大元素存放在根结点中
最小堆:除 根 节 点 之 外 的 每 个 节 点 i , 有 A[PARENT(i)] ≤ A[i],即某结点的值不小于其父结点的值,故 堆中的最小元素存放在根结点中
维护最大堆操作:
-
传入参数数组A和下表i,假定 以LEFT(i)和RIGHT(i)为根的两棵二叉树都是最大堆
-
运行时间为 O(lgn),维护最大堆性质的关键
-
设一个变量largest,将A[i]和A[LEFT(i)]比较,大者下标存入largest,将A[largest]和A[RIGHT[i]]比较,大者存入largest;如果largest不等于i,交换A[i]和A[largest]
建堆操作:
- for i = ⌊A.length / 2⌋ downto 1:MAX-HEAPIFY(A, i)
- 对最后一个叶节点的根结点之前的根节点都进行一次维护操作
- O(n)
堆排序算法:
-
O(nlgn)
-
先将A建成一个最大堆
-
将A[1]和A[n]互换位置,
-
去掉结点n
-
维护堆
-
重复
快速排序:
- 原址排序算法
- 最坏情况运行时间为Θ(n ^2 ),期望运行时间为O(nlgn)
- 对排好序的数组,复杂度为O(nlgn)
过程:选最后一个元素为基准值,按基准值把数组分为小于基准值和大于基准值两个数组,分别对这两个数组继续进行快速排序,i,j,当j遇见比基准值小的,将其和i+1交换
计数排序:
- 如果所有待排序元素均为0到k之间的整数,则当k=O(n), 时间复杂度:Θ(n+k)
- 基本思想:对每一个输入元素 x,统计出小于 x 的元素的个 数。然后,根据这一信息直接把元素 x 放到它在最终输出数 组中的位置上
- 熟练掌握伪代码[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c6ULW5X1-1616301930287)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20210111091653273.png)]
- 伪代码步骤:
- 创建C数组,用来保存等于i的元素的个数,将C数组元素都初始化为0
- 遍历A数组,保存等于i的元素的个数
- 遍历C数组,保存小于或等于i的元素的个数
- 把每个元素 A[j] 放到输出数组B中正确的 位置,决定了计数排序的稳定性
桶排序:
- 熟练掌握伪代码 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cyCrTA4d-1616301930288)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20210111094740863.png)]
- O(n),最坏0(n^2)
- 步骤:
- 准备 n 个桶(bucket),B[1…n],将元素 x 依照 x 所在的区间放进 对应的桶中:即第 ⌈nx⌉ 个桶;
- 元素放进桶时,使用链表来存储,并利用插入排序法排序
- 只要依序将链表串接起來,即得到已排序的 n 个元素
C10-14
栈和队列:
栈实现的是一种后进先出(LIFO)策略的动态集合:S.top
队列实现的是一种先进先出(FIFO)策略的动态集合:Q.head,Q.tail
链表:
- 实现的是一种线性顺序排列的对象集合
- 用一组任意的存储单元存储线性表的数据元素(关键字),同时 还存储下一个结点地址(指针),这两部分信息组成为结点
双向链表的每个元素包含关键字和两个指针:next 和 prev
链表的搜索:从head开始,往后搜索,最坏O(n)
双向链表的插入:O(1)
- x.next=L.head
- 如果原先链表有元素,则将L.head.prev=x
- L.head=x
- x.prev=NIL
双向链表的删除:
- 如果删除结点prev!=NIL,x.prev.next = x.next
- 否则说明是头结点,则head=x.next
- 如果x.next不是空,x.next.prev = x.prev
散列表:
- 在元素的关键字与其存储位 置间建立直接联系,以实现直接快速查找
- 使用散列方法进行查找时,对元素的关键字进行散列函数的计算,把 求得的函数值当做元素存储位置,不必进行多次关键字的比较,查找 速度比较快
解决冲突:
- 链接法:将同一个槽的所有元素放在一个链表中。
- 开放寻址法: 通过探查确定序列,依次遍历所有表项,直到有空位插入,满了就不能插入了。序列即是1-n的一个排列
线性探查:ℎ(𝑘, 𝑖) = (ℎ′(𝑘)+𝑖) mod m 弄清除k和i是什么,k是要插入的值
二次探查:ℎ(𝑘, 𝑖) = (ℎ′(𝑘)+c1 * 𝑖+c2 * i ^2 ) mod m
上两个方法都有 群集现象:如果两个关键字的初始探 查位置相同,那么它们的探查序列也是相同的
双重散列:ℎ(𝑘, 𝑖) = (ℎ1 (𝑘)+𝑖h2 (k)) mod m
每个后面都有mod m
二叉搜索树:
性质:
- 若其左子树非空,则左子树上所有结点的关键字都小于根结 点的关键字
- 若其右子树非空,则右子树上所有结点的关键字都大于根结 点的关键字
- 左子树和右子树也是二叉搜索树
查询操作:O(h),h是树的高度
返回最大元素操作:O(h)
返回最小元素操作:O(h)
后继:O(h)
- 有右子树,则后继节点是右子树中关键字最小的结点
- 若一个结点没有右子树:
- 若该结点是其父结点的左边孩子,那么该结点的后继结点即 为其父结点
- 若该结点是其父结点的右边孩子,那么需要沿着其父亲结点 一直向树的顶端寻找,直到找到一个结点 P,P 结点是其父 结点 Q 的左边孩子,那么 Q 就是该结点的后继结点
插入操作:不断比较直到找到一个合适的叶节点,再次比较决定插入到哪一个,O(h)
删除操作:见书
红黑树:
平衡树:左右两个子树高度差不超过一
红黑树性质:
- 根节点是black
- 叶节点是black
- 如果一个节点是red,则两个子节点是black
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含 相同数目的 Black 结点-------------------->得出这条路径上的black结点个数称为黑高
- 一棵有 n 个内部结点红黑树的高度至多为 2lg(n+1)。
- 一般操作都可以在O(lgn)内完成
旋转:看代码
插入删除操作:可能会破坏的性质是
- 根节点为黑色
- 如果一个节点是red,则两个子节点是black
C15
思想:分解成子问题,然后合并,形成解。与分治法不同,适用于有子问题重叠的情况,即不同的子问题 具有公共的子子问题。
相同点:都是将待求解问题分解成 若干个独立子问题,然后将子问题的解进行合并,形成原问题的解
不同点:分治法要求将问题划分为一些独立不重叠子问题,子问题之间 不包含公共的子子问题,而动态规划适用于有子问题重叠的情况,即不同的子问题 具有公共的子子问题,对相同子问题只求解一次
用来求解的问题:最优化问题——通常有多个可行解,我们要求其最优解
步骤:
- 刻画一个最优解的结构特征—>划分子问题
- 递归地定义最优解的值 ---->给出最优解的递归式
- 按自底向上的方式计算最优解的值
- 由计算出的结果构造一个最优解
掌握递归求解公式
排序
稳定:
不稳定:
原址排序:
选择排序O(n^2):每次都在数组选出最大或最小元素(python直接列表pop,插入新列表
c++每次和arr[0]、arr[1]、arr[2]……交换)
插入排序O(n^(1-2)):从第二个元素开始,每次将当前元素和这个元素之前的元素一一比较,小则交换,大则停止。最后得到一个升序
归并排序(O(nlgn)):先对半分组,分到不能再分之后,再合并成有序数组(两组从头开始遍历,小的数就进合并数组,继续一个个比较,最后一个值设置为很大的数,作为哨兵)
快速排序:选第一个元素为基准值,按基准值把数组分为小于基准值和大于基准值两个数组,分别对这两个数组继续进行快速排序
复习卷答案:
分治模式在每层递归时都有三个步骤:分解、解决、合并
堆排序的平均时间复杂度为O(nlgn),存储结构是:数组
矩阵乘法Strassen算法应用了 分治(递归?)算法设计方案,渐进复杂性为Θ(n^lg7)
大则停止。最后得到一个升序
归并排序(O(nlgn)):先对半分组,分到不能再分之后,再合并成有序数组(两组从头开始遍历,小的数就进合并数组,继续一个个比较,最后一个值设置为很大的数,作为哨兵)
快速排序:选第一个元素为基准值,按基准值把数组分为小于基准值和大于基准值两个数组,分别对这两个数组继续进行快速排序
复习卷答案:
分治模式在每层递归时都有三个步骤:分解、解决、合并
堆排序的平均时间复杂度为O(nlgn),存储结构是:数组
矩阵乘法Strassen算法应用了 分治(递归?)算法设计方案,渐进复杂性为Θ(n^lg7)