主要内容:
1.算法引论
2.递归算法与分治算法
3.贪心算法
4.动态规划
-----------------------------
1.算法引论
基本内容:
一、算法:算法是一组有穷的规则,是问题求解方案的准确描述。
二、算法的特性
确定性 组成算法的每条指令是清晰的、无歧义的。 有限性 算法中每条指令的执行次数有限,每条指令执行的时间有限。 输入 算法有0个或多个外部量作为输入。 输出 算法产生1个或多个值作为输出。 能行性 每种运算都是可以由人用纸和笔在有限时间内完成。
三、算法分析
“通用”计算机模型:图灵机
算法的复杂性是算法运行所需要的计算机资源的量,需要的时间资源的量称为 时间复杂度,需要的空间资源为 空间复杂度。 算法分析可分为事前分析与事后测试,事前分析可以求出该算法的一个时间限界函数,事后测试则是收集算法的执行时间和占用空间的统计资料。
四、算法的分类
从计算时间上可将算法分为两大类: 1、凡可用多项式对其计算时间限界的算法,通常称为多项式时间算法; 2、计算时间只能用指数函数限界的算法称为指数时间算法。
6种使用计算时间限界的多项式时间算法以及根据它们计算时间的长短形成的关系: Ο(1) < Ο(logn) < Ο(n) < Ο(nlogn) < Ο(n2) < Ο(n3) 指数时间算法一般有Ο(2n)、Ο(n!)和Ο(nn)等,其关系为 Ο(2n) < Ο(n!) < Ο(nn)
五、计算时间的渐近表示
定义1 若存在两个正常数c和n0,使得对于任意的n>=n0,都有|f(n)|<=c|g(n)|,则记作f(n)=O(g(n))。 通常称g(n)是f(n)的上界函数。 定义2 若存在两个正常数c和n0,使得对于任意的n>=n0,都有|f(n)|>=c|g(n)|,则记作f(n)=Ω(g(n))。 定义3 若存在正常数c1,c2和n0,使得对于任意的n>=n0,都有c1|g(n)|<=|f(n)|<=c2|g(n)|,则记作f(n)=Θ(g(n))。
六、常用的整数求和公式
在算法分析中,当确定语句的频率时,通常会遇到以下形式的表达式:
2.递归算法与分治算法
基本内容:
一、递归的定义
在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。
二、一般地,可以使用递归算法求解的问题应满足以下三个条件: 问题P的描述涉及问题的规模,即P(size); 问题的规模发生变化后,解决问题的方法完全相同,并且原问题(通常是大规模的问题)的解由小规模问题的解构成; 小规模的问题是可以求解的(在有限步内可以停机)。
三、递归算法的设计步骤
/分解过程,即用递归体将“大问题”分解成“小问题”,直到递归出口为止
/ 求值过程,即已知“小问题”,计算“大问题”。
四、递归算法的特点
优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。
五、基于递归算法的插入排序
/对于n个元素的数组A用递归插入排序算法对其按不减的次序排序,其思想即:A 中的n个元素a1,a2,......,an的排序依赖于a1,a2,......,an的排序。
/进行排序时,总是将当前数组的最后一个元素an与它前面的元素,an-1进行比较,若an<an-1,则交换an与an-1的位置,然后,an再与an-2进行比较,若an<an-2,则交换an与an-2的位置,......,直到有ai(i<=n-1)使得ai<=an 时,这种比较和交换的过程终止。
时间复杂度:当待排序数组是有序的,最优情况,只需当前数跟前一个数比较一下就可以了,这时需要比较n-1次,时间复杂度为O(n)。最坏的情况是待排序数组是逆序的,此时需要比较次数较多,时间复杂度是O(n2)。
空间复杂度:算法用于递归栈的工作单元数与n为同一数量级,即为 。
例:n=6,3,1,6,9,2,8
1,2,3,4,5,6
6,5,4,3,2,1
分析递归插入排序算法在最好、最坏情形下的时间复杂度。
A={3,1,6,9,2,8 }
解:①输入A={3},直接返回{3}
②输入A={3,1},比较1次,交换1次,返回A={1,3}
③输入A={1,3,6},比较1次,交换0次,返回A={1,3,6}
④输入A={1,3,6,9},比较1次,交换0次,返回A={1,3,6,9}
⑤输入A={1,3,6,9,2},比较4次,交换3次,返回A={1,2,3,6,9}
⑥输入A={1,2,3,6,9,8},比较2次,交换1次,返回A={1,2,3,6,8,9}
结束运算 共比较9次,交换5次。
A={1,2,3,4,5,6 }
解:①输入A={1},直接返回{1}
②输入A={1,2},比较1次,交换0次,返回A={1,2}
③输入A={1,2,3},比较1次,交换0次,返回A={1,2,3}
④输入A={1,2,3,4},比较1次,交换0次,返回A={1,2,3,4}
⑤输入A={1,2,3,4,5},比较1次,交换0次,返回A={1,2,3,4,5}
⑥输入A={1,2,3,4,5,6},比较1次,交换0次,返回A={1,2,3,4,5,6}
结束运算 共比较5次,交换0次。
A={6,5,4,3,2,1 }
解:①输入A={6},直接返回{6}
②输入A={6,5},比较1次,交换1次,返回A={5,6}
③输入A={5,6,4},比较2次,交换2次,返回A={4,5,6}
④输入A={4,5,6,3},比较3次,交换3次,返回A={3,4,5,6}
⑤输入A={3,4,5,6,2},比较4次,交换4次,返回A={2,3,4,5,6}
⑥输入A={2,3,4,5,6,1},比较5次,交换5次,返回A={1,2,3,4,5,6}
结束运算 共比较15次,交换15次。
六、递归关系式的计算
例:求解以下递归方程: T(1)=1 n=1;T(n)=T(n-1)+n
当 n>1 求 T(n)的过程如下:
T(n)=T(n-1)+n
=[T(n-2)+n-1)]+n
=T(n-2)+n+(n-1)
=T(n-3)+n+(n-1)+(n-2)
=…
=T(1)+n+(n-1)+…+2
=n+(n-1)+ +…+2+1
=n(n+1)/2=O(n2)。
六、分治法
分治法的基本思想是将一个规模为n的问题分解为k个规模为的子问题,这些子问题互相独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
七、分治算法的设计由以下三个步骤构成:
划分步:在这一步,将输入的问题实例划分为k个子问题。
治理步:当原问题的数据规模大于某个预定义的阈值n0时,治理步由k个递归调用组成。
组合步:组合步将各个子问题的解组合起来,它对分治算法的实际性能至关重要,算法的有效性在很大程度上依赖于组合步的实现。
八. 二分搜索法
已知一个按非降次序排列的元素表a1,a2,...,an,判定某元素x是否在该表中,若在,则找出x在该表中的位置,并将此位置的下标值赋给变量j,若不在,则将j的值置为0。
九. 归并排序
1. template<class Type>
2. void MergeSort(Type a[ ],int left,int right)
3. {
4. if(left<right) /*至少有2个元素*/
5. {
6. int i=(left+right)/2; /*取中点*/
7. MergeSort(a,left,i);
8. MergeSort(a, i+1, right);
9. Merge(a,b,left,i,right);/*归并到辅助数组b[ ]*/
10.Copy(a,b,left,right); /*将数组b[ ]中已排好序的元素复制回原数组a[ ]中*/
11.}
12.}
十、快速排序
快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素ak,利用ak将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列。
int partition(float A[ ],int low, int high)
1.{
2. int k, i=low;
3. float x=A[low];
4. for (k=low+1;k<=high;k++) {
5. if(A[k]<=x) {
6. i+=1;
7. if(i!=k)
8. swap(A[i],A[k]);
9. }
10. }
11. swap(A[low],A[i]);
12. return i;
13.}
void quicksort(Type A[ ], int low, int high)
1. {
2. Int k;
3. if(low<high) {
4. k= partition(A, low, high);
5. quicksort(A, low,k-1);
6. quicksort(A, k+1,high);
7. }
8. }
3.贪心算法
3.1 贪心算法设计思想
贪心算法是一种改进了的分级处理方法。
它的基本思想是:首先根据优化问题的要求,选取一种量度标准,然后按照这种量度标准对这n个输入进行排序,并且按照排好的顺序依次输入每一个量。如果这个输入与当前已经构成在这种量度意义下的部分最优解组成在一起不能产生一个可行解,那么我们就不把该输入纳入到这一部分最优解中。这种能够得到某种量度意义下的最优解的分级处理方法称为贪心算法。
适合于用贪心算法求解的最优化问题,通常具有以下两个重要的性质,即贪心选择性质与最优子结构性质。
所谓贪心选择性质,就是指待求解最优化问题的全局最优解,可以通过一连串的局部最优选择来实现。
所谓最优子结构性质,就是指一个待求解的最优化问题的最优解中包含它的子问题的最优解。
3.2背包问题
背包问题:给定n种物品(每种物品仅有一件)和一个背包。物品i的重量是wi ,其价值为vi ,背包的容量为c。问应如何选择物品装入背包,使得装入背包中的物品的总价值最大?
0-1背包问题: 在选择物品装入时要么不装,要么全装入。
连续背包问题:在选择物品时,可以选择物品的一部分而不一定要全部。
定理3.1 当物品的效益重量比值按照非递增次序排好序以后,knapsack_greedy算法可以求得背包问题的最优解。
采用Dijkstra发明的贪婪算法可以解决最短路径问题。
经典Dijkstra算法的基本思路:
首先设置一个顶点集合S,并不断地作贪心选择来扩充这个集合。即每次从V-S中取出最短路径长度的顶点u,将u添加到S中,一旦S包含了所有V中的顶点,算法结束。
假设每个点都有一对标号 (dist[u], prev[u]),其中dist[u] 是从源点v到点u的最短路径的长度 (从顶点到其本身的最短路径是零路(没有弧的路),其长度等于零); prev[u]则是从v到u的最短路径中u点的前一点。
Dijkstra算法通过分步方法求出最短路径,每一步产生一个到达新的目的顶点的最短路径。下一步所能达到的目的顶点通过如下贪婪准则选取:在还未产生最短路径的顶点中,选取路径长度最短的目的顶点。
Dijkstra算法按路径长度顺序产生最短路径,下一条路径总是由一条已产生的最短路径再扩充一条最短的边形成。
3.4最小成本生成树
设T是无向图G的子图并且为树,则称T为G的树。若T是G的树且为生成子图,则称T是G的生成树。 设无向连通带权图 G = <V,E>,T是G的一棵生成树. T的各边权之和称为T的权,记作W(T)。 G的所有生成树中权最小的生成树称为G的最小生成树 。
最小生成树问题的两种贪心策略:
1.最近顶点策略(Prim)
2.最短边策略(Kruskal)
Prim算法(最近顶点策略)
任选一个顶点,并以此建立起生成树,每一步的贪心选择是简单地把不在生成树中的最近顶点添加到生成树中。 设最小生成树T=(U,TE),初始时U={u0}, u0为任意顶点,TE={}。
Kruskal算法(最短边策略)
设无向连通带权图 G = <V,E>,最小生成树T=(U,TE),最短边策略从TE={}开始,每一步的贪心选择都是在边集E中选取最短边(u,v),如果边(u,v)加入集合TE中不产生回路,则将边(u,v)加入集合TE中,并将它在集合E中删去。 用Kruskal算法构造最小生成树的过程:
4.动态规划算法
基本内容:
一、采用动态规划求解的问题的一般性质:
最优子结构性质 如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构。
无后效性 指第 i+1 个阶段的决策只与第个i阶段的决策有直接关系,而与第 i 个阶段之前的决策没有直接关系。