1.MIT网易公开课《算法导论》,授课老师也是《算法导论》书的作者之一。讲解的内容几乎围绕书籍,不过其中跳跃表和自组织表书中没有,并行算法和缓存参数无关算法还没看。
2.课程配套的笔记,csdn博客:MIT算法导论公开课之课程笔记,可提前看一遍熟悉一下课程内容,这样看视频有重点。
3.看《算法导论》书籍,主要看了前半部分,其中高级数据结构和算法问题选编还没看。
====================================================================
第一部分 基础知识
1.算法基础
插入排序:
思路:从2开始向后循环,判断A[j]是否比前面大,则将其调到合适位置(将前一值赋给后一值,最前那个用最后赋值)。
运行时间:
最坏:无序,theta(n^2)
最好:有序,theta(n)
归并排序:
思路:分成两个子数组、分别排序、合并排序
运行时间:T(n)=2T(n/2)+theta(n),递归树求解,T(n)=theta(nlogn)。
分治策略:
分解、解决、合并
递归算法:递归情况和基本情况
2.渐进符号
theta():渐进紧确解
O():渐进上界
Omega():渐进下界
o():非渐进上界
w():非渐进下界
3.运行时间函数的递归式求解
代入法;
归纳证明
递归树法:
确定树高,所有层次之和
主方法;
T(n)=aT(n/b)+f(n)
case1:若f(n)=O(n^logba),则T(n)=theta(n ^ logba)
case2:若f(n)=theta(n^logba),则T(n)=theta(n ^ logba * logn)
case3:若f(n)=omega(n^logba),则T(n)=theta(f(n))
第二部分 排序算法
4.快速排序
思路:选主元、原址分区、前半部分排序、后半部分排序(分治策略)
性能:取决于分区的平衡度
最坏:(0:n-1),运行时间:T(n)=T(n-1)+T(0)+theta(n),T(n)=theta(n^2)
最好:比例划分,运行时间:T(n)=2T(n/2)+theta(n),T(n)=theta(nlogn)
lucky与unlucky交替:运行时间:theta(nlogn)
改进:随机快速排序
在原有的快速排序基础上,加一个随机选择数i,A[i]与主元交换,然后分区、排序
期望运行时间:theta(n*logn)
5.线性时间排序
决策树:
之前的排序均属于比较排序,可以抽象成一颗决策树。
决策树的高度至少为nlogn => 比较排序的运行时间为Omega(nlogn)
而堆排序和归并排序时间上界为nlogn,因而为渐进最优。
计数排序:
思路:利用元素个数确定输出位置
前提:n个元素的范围[0,k],若k=O(n),则T(n)=theta(n)。
运行时间:theta(n+k)
基数排序:
思路:
最低位计数排序、次地位计数排序、往前推
运行时间:
n个d位数,T(n)=theta(d*(n+k))
n个d位数=>d/r个r位数,T(n)=theta((d/r)*(n+2^r))
6.顺序统计量
问题:
n个数中输出第i小的数
随机选择算法:
思路:分区、判断第i个数落在哪个区、区内寻找
最坏:T(n)=theta(n^2)
期望时间:E[T(n)]=theta(n),与快速排序的区别在于,快排递归处理两边,而随机选择递归处理其中一边,所以快排的期望时间nlogn,而随机选择的期望时间n。
改进选择算法:
思路:分组/每组5个元素/每组插入排序、对n/5个中位数选择中位数、以此中位数分区、递归选择
运行时间:最坏情况下线性
第三部分 数据结构
7.基本数据结构
栈S:
S.top栈顶、压栈操作、出栈操作
队列Q:
Q.head队头、Q.tail队尾、入队操作、出队操作
链表L:
L.head表头、L.key关键字、L.next下指针、L.prev上指针
双向链表、单链表、循环链表
搜索、插入、删除
8.散列表
直接寻指表:
直接把关键字作为数组下标
散列表:
利用散列函数计算关键字对应的槽位置
若两个关键字映射到同一个槽,发生冲突。
冲突解决:链接法、开放寻址法
链接法解决冲突:
把散列到同一槽的所有元素放在一个链表
n个元素m个槽,装载因子a=n/m
在简单均匀散列下,一次成功查找和不成功查找的平均时间theta(1+a)
散列函数:
h(k)
每个关键字等可能散列到m槽中,与其他关键字散列情况无关
全域散列:
设计一组散列函数H,随机选择其中一个h,以防止恶意设置关键字。
从H中选择一个h,两个关键字发生冲突的概率1/m。
使两个关键字发生冲突的散列函数最多有|H|/m。
开放寻址法:
所有元素都放在散列表中,只不过要多次探查。
散列函数:h(k,i)
线性探查、二次探查、双重探查
开放寻址表中,一次不成功的查找,探查次数最多为1/(a-1)
完全散列:
两级散列结构,一级散列表存放n个关键字,散列到槽j的所有关键字放在一个二级散列表Sj中而不是链表。
Sj的槽数mj=ni^2,确保不发生冲突。
n个关键字存储在m=n^2的散列表中,表中冲突概率小于1/2。
使用总体存储空间的期望数为O(n)。
9.二叉搜索树
二叉搜索树:
以二叉树组织,同时满足x.left.key<=x.key,x.right.key>=x,key。
利用中序遍历算法(输出左子树、中间值、输出右子树),有序输出关键字,运行时间为theta(n)。
查询二叉搜索树:
查找:k与x.key比较,决定x=x.left或者x=x.right,while循环直到x=Nil或者k=x.key。
最大值:while循环到x.right=nil
最小值:while循环到x.left=nil
后继:大于x.key的最小关键字的结点,两种情况:右子树存在时右子树最小值;右子树不存在时沿树上升直到遇到一个结点是双亲的左孩子。
前驱:小于x.key的最大关键字的结点
运行时间:均为O(h)。
插入与删除:
插入:比较z.key与x.key值,决定x=x.left还是x.right,直到x=nil,修改z和z.p属性。
删除:分情况:一是没有孩子,直接删,修改父节点属性;二是只有一个孩子,删除后孩子提升到原有位置,修改父节点的属性;三是有两个孩子,用后继代替,又分后继是否为其右孩子。
运行时间:均为O(h)。
随机二叉搜索树:
随机选取一种排列,插入关键字到一颗初始的空树中而形成的树。
随机二叉树的期望高度:O(lgn)。
10.红黑树
红黑树性质:
在二叉搜索树的基础上每个结点增加一个颜色属性,x.color。
1.每个结点是红色或者黑色。
2.根结点和叶结点是黑色。
3.每个红结点的父节点都是黑色。
4.对于每个结点,从该结点到叶结点的路径上,均包含相同数目的黑色结点个数,用黑高表示,bh(x)。
一颗有n个内部结点的红黑树的高度至多为2log(n+1)。
旋转:
考虑到插入和删除操作后会红黑树难以维持属性,需要改色和旋转,维护红黑树的性质。
左旋、右旋
插入:
在二叉搜索树的插入操作基础上,调用辅助程序对结点改色和旋转。
辅助程序:当父节点为红色时循环操作直到为黑色,分三种情况:case1为叔结点为红色,只要修改父结点和叔结点为黑色,爷结点为红色,指针指向爷结点;case2为结点为右孩子,指针指向父结点,左旋,继续进行第三种处理;case3为结点为左孩子,改父结点和爷结点颜色,指针指向爷结点,右旋。
旋转不会超过两次。
运行时间:O(logn)。
11.数据结构扩张
顺序统计树:
1.一颗红黑树,每个结点附加以其为根的子树结点数,x.size=x.left.size+x.right.size+1。
2.一个元素的秩为在中序遍历树时输出的位置。
3.支持的新操作:
查找给定秩的元素:确定x的位置(x.left.size+1),与i比较,决定左子树还是右子树找。
确定一个元素的秩:向上while循环到根节点,更新r,取决于x为右孩子还是左孩子。
4.红黑树的操作能维护size属性,以插入为例,沿插入路径改size属性,时间O(logn),而改色不需要更改size属性,旋转只需要改两个结点的size,所以总时间仍是O(logn)。
如何扩张数据结构:
1.选择一种基础数据结构;
2.确定附加信息;
3.检验基础操作能否维护附加信息;
4.设计新操作。
区间树:
维护一系列区间数x.int,以x.int.low为次序形成红黑树,每个结点附加x.max属性,表示以x为根结点的子树中端点最大值。
新操作:找出树T中与区间i重叠的结点:当x.left!=nil and x.left.max>=i.low时,x=x.left,否则x=x.right,while循环到x=nil或者i与x,int重叠。
第四部分 高级设计和分析技术
12.动态规划
动态规划与分治法的区别:
都是通过组合子问题的解来求解原问题
分治法将问题划分为互不相交的子问题
动态规划应用于子问题重叠的情况
动态规划实现的方法:
第一种方法为带备忘的由顶向下法,递归算法,当解决子问题解时首先检查是否保存过此解,如果有直接调用。
第二种方法为由底向上法,将子问题从小至大的顺序求解。
动态规划问题具备的两个要素:
最优子结构:原问题的最优解包括其子问题的最优解
子问题重叠:递归算法反复求解相同的子问题
最长公共子序列:
求解两个序列中最长的公共子序列,c[i,j]表示X[1,i]与Y[1,j]的最长公共子序列长度
递归解:
c[i,j]=0 (i=0 or j=0)
c[i,j]=c[i-1,j-1]+1 (i,j>0 and xi=yj)
c[i,j]=max(c[i-1,j],c[i,j-1]) (i,j>0 and xi!=yj)
由底向上求解,子问题规模theta(m*n)
13.摊还分析
内容:
求数据结构的一个操作序列所执行的所有操作的平均时间
聚合分析:
确定一个n个操作的总代价T(n),因为每个操作的平均代价为T(n)/n。
核算法:
确定每个操作的摊还代价,超出实际代价时存储,小于实际代价时取出。
要求总的摊还代价大于总的实际代价。
势能法:
定义每个数据结构对应的势能函数
确定每个操作的摊还代价,证明总摊还代价大于总实际代价
以表扩张为例研究摊还分析
第五部分 图算法
14.最小生成树
问题描述:
已知无向图G(V,E)和权重函数w(u,v),求解一棵树,边权重之和最小。
切割 (S,V-S)是集合V的一个划分
如果一条边(u,v)的一个端点位于集合S,另一个端点位于集合V-S,则边横跨切割。
若集合A不存在横跨切割的边,则该切割尊重集合A。
在横跨切割的所有边中权重最小的边为轻量级边。
定理:T为图G(V,E)的最小生成树,设S为V的一个子集,且有(u,v)为连通S和V-S的最小权值边,那么(u,v)属于T。
Prim算法:
这棵树从一个任意根结点r开始,一直长大到覆盖V中的所有结点为止。
算法每一步在连接集合A和A之外的结点的所有边中选择一条轻量级边加入A,贪心算法。
思路:优先队列弹出key最小的点,利用邻接表更新相邻点的key值,以及父结点。
最小生成树:A={(v,v.p),v=V-{r}}
15.单源最短路径
问题描述:
已知带权重的有向图G(V,E)和权重函数w(u,v),边权重之和最小的路径为最短路径,找出从结点s到每个结点的最短路径。
u,v之间最短路径定义为delta(u,v)。
边(u,v)的松弛操作:比较v.d和u.d+w(u,v),更新最短路径估计值v.d。
Dijkstra算法:
要求所有边的权重为非负值。
算法重复从结点集V-S中选择最短路径估计最小的结点u,将u加入集合S中,然后对所有从u发出的边进行松弛。
算法终止时,对于所有结点u,u.d=delta(s,u)。
Bellman-Ford算法:
适用于一般情况。
思路:通过对边进行松弛操作来渐进降低从源结点s到每一个结点v的最短路径估计值v.d,直到该估计值与实际最短路径权重值delta(s,v)相同。
算法对图的每一条边进行|V|-1次处理,每一次对图每条边进行松弛操作。
运行时间:O(VE)。
差分约束系统:
线性约束:Ax<=b
在一个差分约束系统中,线性规划矩阵A的每一行包括一个1和一个-1,其他所有项皆为0。
对应的图论:Am*n看做是一张由n个结点和m条边构成的图,每一个结点vi对应n个未知量xi的一个,图中有向边则对应m个不等式的一个。
约束图中包含一个额外的结点v0,用来保证图中至少存在一个结点,从其出发可以到达所有其他结点,其中边w(v0,vi)=0。
如果xj-xi<=bk是一个差分约束条件,则边(vi,vj)的权重w(vi,vj)=bk。
如果G不包含权重为负值的环路,则x=(delta(v0,v1),delta(v0,v2),…delta(v0,vn))是该系统的一个可行解。如果图G包含权重为负值的环路,则该系统没有可行解。
16.所有结点对的最短路径问题
问题描述:
对于每对结点u和v,找到结点u到结点v的最短路径。
可以运行|V|次单源最短路径算法来解决所有结点对之间的最短路径问题,每一次使用一个不同的结点作为源结点。
与单源最短路径算法中使用邻接链表表示图不同,本章使用邻接矩阵来表示图。
算法的输入是n阶的W,代表有向图的权重,输出是n阶的D,代表结点间的最短路径权重。
Floyd-Warshall算法:
简单路径p=
考虑到结点i到结点j的所有中间结点均取自于集合{1,2,…k}的路径,设p为其中权重最小的路径。通过分析,可得到递归解:
dij(0)=wij
dij(k)=min(dij(k-1),dik(k-1)+dkj(k-1)) (k!=0)
由底向上计算最短路径权重,计算D(0),D(1),…D(n)。
注意点:
熟悉书中伪码
====================================================================
第二部分 排序算法
堆排序
线性时间排序中的桶排序
第四部分 高级设计和分析技术
贪心算法
第五部分 高级数据结构
B树
斐波那契堆
van Emde Boas树
用于不相交集合的数据结构
第七部分 算法问题选编
多线程算法
矩阵运算
线性规划
傅里叶变换
数论算法
NP完全性
近似算法