第一章 导论
伪码:介于自然语言和机器语言之间的一种,特点:通俗易懂。
大概语法如下:第一行:算法 算法名(数据范围)
第二行:输入描述
第三行:输出描述
写算法大致执行过程例如:
- 算法(Algorithm):对特定问题求解步骤的一种描述,是指令的有限序列。
- 算法被公认为是计算机科学的基石
- 算法理论的两大论题:
(1)算法设计:面对一个问题,如何设计一个有效的算法。
(2)算法分析:对已设计的算法,如何评价或判断其优劣。 - 算法—程序的灵魂:问题的求解过程:
分析问题→设计算法→编写程序→整理结果
- 算法的五大特性:
⑴ 输入:一个算法有零个或多个输入。
⑵ 输出:一个算法有一个或多个输出。
⑶ 有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
⑷ 确定性:算法中的每一条指令必须有确切的含义,对于相同的输入只能得到相同的输出。
⑸ 可行性:算法描述的操作可以通过已经实现的基本操作执行有限次来实现。 - 程序 = 算法 + 数据结构
- 时空效率:运行算法所需要耗费的空间和时间。(时间换空间,空间换时间)
- 伪代码:例如求最大公约数的欧几里得算法
1. r = m % n;
2. 循环直到r等于0
2.1 m = n;
2.2 n = r;
2.3 r = m%n;
3. 输出n;
- 一个优秀的算法应该选择合适的数据结构,数据结构决定了算法的设计难度和执行效率。
- 一个优秀的算法应该简单,普遍适用。算法只有更好没有最好。
- 典型算法问题:排序问题、查找问题、串处理问题、图问题、组合问题、几何问题、数值问题。
- 排序问题的两大特性:稳定性(等值元素应当在排序前后保持相对位置不变)、在位性(不需要额外的存储空间)
- 图问题:基本算法有图遍历、最短路径、拓扑排序。高阶算法有旅行商、图着色、图相似。
- 常见具体问题:最大子段和问题、找钱问题、背包问题、TSP问题、n皇后问题、假币问题、凸包问题、平面上的最近点问题、排序问题、多段最短路径问题、任务分配问题。
- 逻辑结构:线性结构、树形结构、图形结构、集合结构。
- 存储结构:顺序存储、链式存储、索引存储、散列存储。
- 完全图:任意两个顶点之间都有边相连。边数:无向完全图:n(n-1)/2。有向完全图:n(n-1)。
- 稠密图:相对完全图,所缺边较少的图。
- 系数图:相对完全图,所缺边较多的图。
- 连通图:任意顶点之间都有一条路径相连。
- 简单路径:路径中的顶点仅通过一次,无重复顶点。
- 路径长度:顶点数减一等于边数。
- 树的特点:边数=顶点数-1
- 树中顶点的深度:等于从根到顶点的路径长度。
第二章 算法效率分析基础
- P类问题和NP问题
(1)P类问题:将所有可以在多项式时间内求解的问题称为P问题。采用的是确定性算法。
(2)NP问题:在多项式时间内可以验证的问题称为NP问题。采用的是非确定性算法。 - 算法分析:对算法所需的空间复杂性和时间复杂性(*)进行估算。
- 算法分析的目的:设计算法-设计复杂性尽可能低的算法。选择算法-在多种算法中选择复杂性最低的。
算法中循环体之外的操作,其复杂度都是常数阶。因此,若算法中没有循环,即使做了再多的计算,耗时再长(有这样的复杂计算),其时间复杂度仍然为常数阶。而相对一些简单重复的操作,其复杂度却高于常数阶。所以,不能单以一个算法的运行时间来衡量算法的复杂度高低
2.1 分析框架
- 主要研究当算法的输入规模趋向无穷大时,其运行时间函数的增长次数(增长率)。
- 算法的复杂性:C = F(N,I,A)。N:问题规模。I:输入。A:算法本身。一般把时间复杂性和空间复杂性分开。
- 时间复杂度:T = F(N,I)。用基本操作的执行次数来作为时间复杂度的量读。
- 空间复杂度:S = F(N,I)。用算法消耗的额外存储单元的数量来量度空间复杂度。
问题:若某算法运行在一台比选现在机器快10倍的机器上,此算法运行有多快?
答案:快10倍。
- 执行次数的增长次数(增长率):基本操作数(时间耗费)是输入规模的函数,记为T(n)。T(n)随着n次数的增加而增加。函数值T(n)的增长次数决定了算法的时间复杂度。增长次数约高,算法时间复杂度约高。
- 算法时间复杂度的排序:O(1) < O(log n) < O(n)< o(nlog2n) <O(n2) <O(nk)< O(2n) < O(kn) <O(n!)
- 算法的最优、最差、平均效率:
(1)最优:在最好的情况下的时间复杂度。例如:顺序表查找,要查找的元素在表头。
(2)最差*:在最坏的情况下的时间复杂度。例如:顺序表查找,要查找的元素在表尾。最为关注。它保证算法运行时间不会超过最坏输入情况下的运行时间。
(3)平均效率:平均效率并不等于最优效率和最差效率的均值。
2.2 渐进类型和基本效率类型
- 上界符号:O。最坏情况下的算法时间复杂度。
(1)加法规格:针对并列的程序段m和n。 T(n,m)=T1(n)+T2(m) = O(max((f(n),g(m))))。当算法由两个连续执行部分组成时,该算法的整体效率由具有较大增长次数的那部分所决定。
(2)乘法规则:针对嵌套的程序段m和n。T(n,m)=T1(n)*T2(m) = O ((f(n)*g(m)) - 下界符号:Ω。最好情况下的算法时间复杂度。可以用来描述问题的时间复杂性,即:解决这个问题最起码的时间复杂度是多少。
- 增长次数小,即算法时间复杂度低。
- 使用洛必达法则(分别求导)和史特林公式(把n!化简)。
洛必达法则使用条件:一:分子分母的极限都等于0或无穷大。二:分子分母在指定区域内可导。
- n!的时间复杂度比2^n高
2.3 非递归效率分析
- 非递归算法效率分析的通用方案
(1)确定输入规模;
(2)确定基本操作;(一般情况:它总是位于算法的最内层循环里)
(3)考虑基本操作的执行次数是否仅仅与输入规模有关。若还与其他因数有关(比如顺序查找算法中基本操作执行次数还与查找键在列表中的位置有关),则按需要对最差、最佳、平均效率作出分析。
(4)建立基本操作执行次数与输入规模 n 的求和表达式,即建立增长函数。
(5)利用运算公式(法则),确定该增长函数的增长率。
复习:几个常用求和公式
2.4 递归算法分析
- 递归:在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。若调用自身,称之为直接递归。若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归。
- 何时使用递归:
(1)定义是递归的。比如:斐波那契数列的定义。
(2)数据结构是递归的。比如:单链表的数据结构,struct Node *next;
。
(3)问题的求解方法是递归的。比如:汉诺塔的求解过程。 - 递归算法效率分析的通用方案:
(1)决定哪些参数作为输入规模的度量。
(2)找出算法的基本操作。
(3)检查对于相同规模的不同输入,基本操作的执 行次数是否不同。
(4)求出基本操作的递归关系表达式及初始条件。
(5)解递归关系,得基本操作执行次数的增长次数。(难点) - 解递归关系:
(1)反向替换法:从n开始。(这里的n和n-1是下标),适用n和n-1的关系。
(2)公式法:解二阶常系数线性递推方程。
例如:
2.5 数学基础
2.5.1 计算复杂性函数的阶
2.5.2 和式的估计与界限
2.5.3递归方程
- 如何描述算法的效率:增长率
- 忽略低阶项,忽略常系数,保留最高阶项
第三章 蛮力法
概述:
查找问题
排序问题
组合问题
图问题
几何问题
-
蛮力法的设计思想:直接基于问题的描述。
-
蛮力法所赖的基本技术:扫描技术, 关键是依次处理所有元素
-
基本的扫描技术——遍历
(1)集合的遍历
(2)线性表的遍历
(3)树的遍历
(4)图的遍历 -
排序问题:
(1)选择排序:时间复杂度O(n^2)
(2)冒泡排序:时间复杂度O(n^2) -
组合问题:
(1)生成排列对象:时间复杂度O(n!)
(2)生成子集:时间复杂度O(2^n)
(3)0/1背包问题:- 问题描述:0/1背包问题是给定n个重量为{w1, w2, … ,wn}、价值为{v1, v2, … ,vn}的物品和一个容量为C的背包,求这些物品中的一个最有价值的子集,并且要能够装到背包中。
- 解决:用蛮力法解决0/1背包问题,需要考虑给定n个物品集合的所有子集,找出所有可能的子集(总重量不超过背包容量的子集),计算每个子集的总价值,然后在他们中找到价值最大的子集。
- 时间复杂度:时间复杂度O(2^n)
(4)任务分配问题:
- 问题描述:假设有n个任务需要分配给n个人执行,每个任务只分配给一个人,每个人只分配一个任务,且第j个任务分配给第i个人的成本是C[i, j](1≤i , j≤n),任务分配问题要求找出总成本最小的分配方案。 形成一个任务分配问题的成本矩阵,矩阵元素C[i, j]代表将任务j分配给人员i的成本。
- 解决: 任务分配问题就是在分配成本矩阵中的每一行选取一个元素,这些元素分别属于不同的列,并且元素之和最小。
- 时间复杂度:时间复杂度O(n!)
-
图问题:
(1)哈密顿回路:- 问题描述:著名的爱尔兰数学家哈密顿(William Hamilton,1805—1865)提出了著名的周游世界问题。他用正十二面体的20个顶点代表20个城市,要求从一个城市出发,经过每个城市恰好一次,然后回到出发城市。
- 时间复杂度:O(n!)
(2)TSP问题:
- 问题描述: TSP问题是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。该问题又称为货郎担问题、邮递员问题、售货员问题,是图问题中最广为人知的问题。
- 时间复杂度:O(n!)
-
几何问题:
(1)最近对问题:- 问题描述:最近对问题要求找出一个包含n个点的集合中距离最近的两个点。
- 解决: 蛮力法求解最近对问题的过程是:分别计算每一对点之间的距离,然后找出距离最小的那一对,
- 时间复杂度:O(n^2)
(2)凸包问题:
- 问题描述:
【1】对于平面上的一个点的有限集合,如果以集合中任意两点P和Q为端点的线段上的点都属于该集合,则称该集合是凸集合。
【2】一个点集S的凸包是包含S的最小凸集合,其中,最小是指S的凸包一定是所有包含S的凸集合的子集。 为了解决凸包问题,需要找出凸多边形的顶点,这样的点称为极点。 - 性质:如果点集中的两个点的连线属于凸多边形的边,当且仅当点集中其余的点都在这两个点连线的同一侧
- 解决: 在平面上,穿过两个点(x1, y1)和(x2, y2)的直线是由下面的方程定义的:
ax + by = c (其中,a=y2-y1, b=x1-x2, c=x1y2-y1x2)
这样一条直线把平面分成两个半平面:其中一个半平面中的点都满足ax + by>c,另一个半平面中的点都满足ax + by<c,因此,为了检验这些点是否位于这条直线的同一边,可以简单地把每个点代入方程ax + by = c,检验这些表达式的符号是否相同。 - 时间复杂度:O(n^3)
第四章 分治法
-
【算法思想】:分治法是指将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,一直这样循环下去,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
-
启发式规则:
(1)平衡子问题:最好使子问题的规模大致相同。也就是将一个问题划分成大小相等的k个子问题(通常k=2),这种使子问题规模大致相等的做法是出自一种平衡(Balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。
(2)独立子问题:各子问题之间相互独立,这涉及到分治法的效率,如果各子问题不是独立的,则分治法需要重复地解公共的子问题。
-
分治法所能解决的问题一般具有以下几个特征:
(1)该问题的规模缩小到一定的程度就可以解决;
(2)该问题可以分解为若干个规模较小的相同问题;
(3)利用该问题分解出的子问题的解可以合并为该问题的解;
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题
-
分治法的求解过程:
(1)划分:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
(2)求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
(3)合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。
注意:不是所有的分治法都比简单的蛮力法更有效。