算法 Algorithm

算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制

只有在后启示录世界,所有连接到互联网的计算机的硬盘都被炸了,所有基础学术论文和计算机科学教科书都化为灰烬,你才真正需要记住算法。

尝试理解并思考算法的本质,尝试去实现每一个你不论多糟糕的想法,尝试去向更为优秀的算法

算法基础概念

  • 算法的特征
    (以下是高德纳在他的著作《计算机程序设计艺术》里对算法的特征归纳)
    输入:一个算法必须有零个或以上输入量。
    输出:一个算法应有一个或以上输出量,输出量是算法计算的结果。
    明确性:算法的描述必须无歧义,以保证算法的实际执行结果是精确地匹配要求或期望,通常要求实际运行结果是确定的。
    有限性:依据图灵的定义,一个算法是能够被任何图灵完备?(https://zh.wikipedia.org/wiki/%E5%9B%BE%E7%81%B5%E5%AE%8C%E5%85%A8)系统模拟的一串运算,而图灵机?(https://zh.wikipedia.org/wiki/%E5%9C%96%E9%9D%88%E6%A9%9F)只有有限个状态、有限个输入符号和有限个转移函数(指令)。而一些定义更规定算法必须在有限个步骤内完成任务。
    有效性:又称可行性。能够实现,算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现。

  • 算法描述
    自然语言描述
    程序流程图/NS流程图
    程序设计语言
    伪代码

  • 1.变量的声明
    算法中出现的数组、变量可以是以下类型:整数、实数、字符、位串或指针。
    定义变量的语句不用写出来,但必须在注释中给出
  • 2.指令的表示
    指令:在算法中的某些指令或子任务可以用文字来叙述
    例如,”设x是A中的最大项”,这里A是一个数组;或者”将x插入L中”,这里L是一个链表。
    这样做的目的是为了避免因那些与主要问题无关的细节使算法本身杂乱无章。
  • 3.表达式
    算术表达式 可以使用通常的算术运算符(+,-,*,/,以及表示幂的^。
    逻辑表达式 可以使用关系运算符=,≠,<,>,≤和≥,
    逻辑运算符 与(and),或(or),非(not)。
  • 4.语句
    赋值语句
a←b  \\ 语句的含义是将b的值赋给a。 

这里a是变量、数组项,b是算术表达式、逻辑表达式或指针表达式
变量交换

a<->b

若a和b都是变量、数组项,那么记号a<->b 表示a和b的内容进行交换。
* 5.goto语句

  • 6 分支结构
    条件语句:
if i=10
      then xxxx
      else xxxx                 //else 和 then 要对齐
if i=10
      then xxxx                   //if 后面必定跟上then,else后面不用跟then
      elseif i=9                  //elseif 要连在一起写
      then xxxx
        yyyy
    else  xxxx             //else 跟在elseif 的 then 对齐
  • 8.循环结构
    while
while time<10
      do  xxxxx                  //while后面必定要紧跟缩进的do
      xxxxx
      end

for

for var init to limit by incr do
      xxxx
end
for i←0 to 10        //for、while、if 后面的条件语句都不用加括号
    do ...                 //for后面必定要紧跟缩进的do
    ...

这里var是变量,init、limit和incr都是算术表达式,
而xxxx是由一个或多个语句组成的语句串。
初始时,var被赋予init的值。假若incr≥0,则只要var≤limit,就执行s并且将incr加到var上。
(假若incr<0,则只要var≥limit,就执行s并且将incr加到var上)。incr的符号不能由xxxx来该改变。

* 9.程序的结束
exit 可以在通常的结束条件满足之前,被用来结束while循环或者for循环的执行。exit导致转向到紧接在包含exit的(最内层)while或者for循环后面的一个语句。
return 用来指出一个算法执行的终点;如果算法在最后一条指令之后结束,它通常是被省略的;它被用得最多的场合是检测到不合需要的条件时。return的后面可以紧接被括在引号的信息。
* 10.注释风格
算法中的注释被括在/* */之中。诸如read和output之类的各种输入或者输出也在需要时被用到。
* 11.函数的编写
函数的伪代码格式例子为

search(A,name) //参数类型可以不给出,但必须在注释中说明
  • 算法设计过程
    1.问题分析
    2.算法策略/建立计算模型
    3.算法设计与描述
    4.算法分析[算法选择]
    5.算法实现
    6.测试与结果分析
    7.文档编制

  • 算法设计工具

循环
* 循环设计思路
自底向上的设计(Down - Top Design)
先找出某个问题的子问题或若干特殊问题,以定性、定量的方式去描述和解决这些子问题,然后,逐步合并子问题的解,最后得到大问题的解。
核心本质:合并
自顶向下的设计(Top-Down Design)
将复杂的大问题分解为相对简单的小问题,找出每个问题的关键、重点所在,然后用精确的思维定性、定量地去描述问题和解决问题。
核心本质:分解

递归:一个过程或函数在定义中直接或间接调用自身的一种方法。
设计关键:找出递归关系(方程)和递归终止(边界)条件。递归关系就是使问题向边界条件转化的规则。

  • 递归设计的步骤:
    (1) 分析问题找到递归关系:找出大规模问题与小规模问题的关系,以便通过递归使问题规模变小。
    (2) 设置终止条件控制递归:通过停止条件的设置,找出可解的最小规模问题。
    (3) 设计函数确定数据传递方式
  • 基本的数据结构
    线性表
    栈 FILO
    队列 FIFO
    树 (二叉树/B树/B+树)

算法基础概念

  • 算法效率评价的指标 : 算法计算机资源的占用
    时间复杂度 O 计算资源(时间)
    空间复杂度 O 存储资源(内存)

数学基础

函数的渐近的界
利用极限求函数渐近的界
有用的求和级数及推导方法
基本效率类型

算法分析实例

非递归形式算法分析
递归形式算法分析(难点)

  • 分析递归算法时,可遵循以下步骤
    决定用哪些参数作为输入规模的度量标准
    找出算法的核心操作,它通常是递推公式
    检查一下,对于相同规模的不同输入,核心操作的执行次数是否可能不同。如果有这种可能,则必须对最差效率平均效率以及最优效率做单独研究;
    对于算法核心操作的执行次数,建立一个递推关系以及相应的边界条件
    解这个递推式,或者至少确定它的解的增长次数。

递推法分析时间复杂度 求n!

  • 计算模型

  • 算法分析
    n为规模,也是计算规模
    核心操作为n*F(n-1),是一次乘法操作
    依据递推公式,每递推一次,执行一次乘法操作,因此有如下推导过程:
    T(n) =1+T(n-1) =1+1+T(n-2)……=1+1+…+1+T(1)=1+1+…+1=n=Θ(n)

递推法分析时间复杂度 汉诺塔问题

  • 计算模型

    n为规模,也是计算规模
    核心操作为移动盘子
    依据递推公式,两次递推之间,执行一次移动操作,因此有如下推导过程:
    T(n) =2T(n-1)+1
    =2[2T(n-2)+1]+1=22T(n-2)+2+1
    =22[2T(n-3)+1]+2+1=23T(n-2)+22+2+1
    ……
    =2i[2T(n-i)+1]+2i-1+2i-2…+20=2iT(n-i)+ 2i-1
    ……
    =2n-1T(n-(n-1))+ 2n-1-1=2n-1T(1)+ 2n-1-1=2n-1

主定理

主定理(Master Theorem) 设a≥1, b>1为常数,f(n)为函数,T(n)为非负整数,且 T(n)=aT(n/b)+f(n) 则有以下结果

迭代法

是一种不断用变量的旧值递推新值的过程

  • 分类
  • 精确迭代 (杨辉三角,内存移动算法)
  • 近似迭代 (方程求解)
  • 设计关键
  • 确定迭代模型/迭代方程
  • 控制迭代过程

杨辉三角

杨辉三角

斐波那契数列

f(n) = f(n-1)+f(n-2)

内存移动问题

数组中有n个数据,要将它们顺序循环向后移k位,即前面的元素向后(右)移k位,后面的元素则循环向前移k位 后面的元素则循环向前移k位,例:0、1、2、3、4、5循环移3位后为: 3、4、5、0、1、2。考虑到n会很大,不允许用2n及以上个空间来完成此题*
解题思路 : 利用移动长度与数据个数的关系约简运算
内存移动

求解方程的近似算法 (以9x2-sin x-1=0为例)

  • 迭代方程 求解序列必须收敛
    (1)选取适当的初值x0
    (2)建立迭代方程,将方程f(x)=0转换成x=φ(x)的等价形式。
    (3)运用迭代方程x=φ(x),反复计算,如x1=φ(x0), x2=φ(x1),…,xn=φ(xn-1),得到x的序列,若该数列收敛,则最终可以得到满足一定精度ε的解,即有|xn-xn-1|<ε。有时候也会用f(xn)<= ε或f(xn)=0来判断

    迭代方程
  • 二分法
    零点定理
    零点定理
  • 牛顿法
    使用泰勒展开近似求解
    牛顿法

线性代数方程组

线性代数方程组有如下特征

  • Jacobi算法
    Jacobi

  • Gauss-Seidel算法
    Gauss-Seidel
    两种方法实现

线性规划问题

研究线性约束条件线性目标函数的极值问题的数学理论和方法
* 单纯型表法求解
1 根据约束方程建立模型 (通过松弛变量等方法)
2 选择入基变量与离基变量
3 进行转轴变换,求最值

蛮力法/暴力法

直接基于问题的描述和所涉及的概念定义的进行算法设计,简单而直接

枚举法

利用枚举所有的情况,或者其它大量运算又不用技巧的方式,来求解问题的方法。
* 猜数字问题

穷举查找

  • 单词搜索问题
  • 穷举求解背包问题 (注意更高效的生成一个全排列矩阵/树
  • 穷举求解旅行商问题 (traveling salesman problem,TSP)
    旅行商由一个旅行商由某市出发,经过所有给定的n个城市后,再回到出发的城市。除了出发的城市外,其它城市只经过一回。这样的回路可能有多个,求其中路径成本最小的回路。
    • 问题分析与描述
      旅行商问题实例
      旅行商问题解空间
    • 计算模型
      (1) 存储 图G(V, E)。以邻接矩阵的方式存储,设计如下
      计算模型-矩阵存储
      (2)计算 设起始点下标为0
      生成排列树。设解空间为a,则其解空间的计算过程可描述为:
      排列树
      (3)求回路代价 设sumi是并入第i个结点的代价
      求回路代价
    • 伪代码描述
      TSP 穷举求解伪码描述

图的搜索

  • 深度优先遍历 DFS
  • 广度优先遍历 BFS
  • 矩阵迷宫求解

分治法

把一个较大的问题分解成几个与原问题相似的子问题,找到求出这几个子问题的解法后,再以适当的方法组织,把它们合成求整个问题的解法。

  • 分治法适用情景
    该问题的规模缩小到一定的程度就可以容易地解决
  • (前提) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
  • (关键) 利用该问题分解出的子问题的解可以合并为该问题的解;
  • (效率) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题

二分查找

思路:将按顺序排列元素拆分成更小的集合来查找
将n个元素分成个数大致相同的两半,取a[n/2]与x作比较。如果x=a[n/2],则找到x,算法终止。如果x<a[n/2],则在集合的前半部分继续查找,否则在后半部分查找。

大数乘法

思路:将大数X分解成登场的两部分 X0 X1 例如
分解
计算模型
将两个大数字的乘法分解成了四个较小数字的乘法
计算模型公式
此思路下的时间复杂度为
迭代时间复杂度
根据主定理求得时间复杂度
此时,对计算模型公式进行进一步优化可以将四个乘法操作简化到三个
新计算模型公式
继续求得时间复杂度
新计算模型公式时间复杂度
伪代码描述
伪代码描述

Strassen矩阵乘法

将两个矩阵的乘法转化为两个矩阵四个区域相乘的结果,如果区域内只剩下一个数字,此时矩阵乘法就变成了简单的数字相乘,见下图
矩阵分解
或者根据Strassen方程 进行更快速的运算
Strassen方程?(http://blog.csdn.net/winnerlyn/article/details/51327030)算法
(1)划分(Divide):把矩阵A和B划分为(n/2)*(n/2)的子矩阵,完成加减运算,求解Mi(递归)
(2)运算(Conquer):按Strassen方程,执行7个(n/2)*(n/2)的子矩阵乘法运算
(3)合并(Combine):按Strassen方程,通过加减运算求C

棋盘覆盖问题

  • 问题描述
    残缺棋盘是一个有2k×2k (k≥1)个方格的棋盘,其中恰有一个方格残缺。如图给出k=1时各种可能的残缺棋盘,其中残缺的方格用阴影表示。
    残缺棋盘
    这样的棋盘称作“三格板”,残缺棋盘问题就是要用这
    四种三格板
    覆盖更大的残缺棋盘。在此覆盖中要求:
    1)两个三格板不能重叠
    2)三格板不能覆盖残缺方格,但必须覆盖其他所有的方格
    在这种限制条件下,所需要的三格板总数为(2k×2k -1 )/3。

  • 问题分析

    分4种情况(子解集求解合并):
    若坏格子在左上角,则把图5-2中①号放置在二分后的中间位置,如图(2)
    若坏格子在右上角,则把图5-2中②号放置在二分后的中间位置,如图(3)
    若坏格子在左下角,则把图5-2中③号放置在二分后的中间位置,如图 (4)
    若坏格子在右下角,则把图5-2中④号置在二分后的中间位置,如图(5)

  • 伪代码描述
    残缺棋盘问题 伪代码描述

选择性问题 (选最大值、最小值、选中位数、选第二大值)

  • 问题描述
    设A是含有n个元素的集合,从A中选出第k小的元素,其中1≤k≤n。这里的第k小是指当A中元素从小到大排序之后,第k个位置的元素,当k=1时,选出的是A中的最小值,当k=n时,选出的就是最大值。

  • 问题分析
    (1)排序 找第k小的元素,时间渐近复杂度为O(nlogn) 快速排序
    (2)蛮力算法 当k=1或k=n时,一趟即可找到解,时间渐近复杂度只有O(n)。
    (3)二分治策略 当k=2时,将原数列分为两个子集,每个子集各选出一个最小值和第二小值,从这个4个数字里可找到当前的最小值

  • 计算模型
    选择pivot=a[nleft]值,j=right对集合进行二分
    (1) j-left=k-1,则分界数据就是选择问题的答案
    (2) j-left>k-1,则选择问题的答案继续在左子集中找,问题规模变小了。
    (3) j-left<k-1,则选择问题的答案继续在右子集中找,问题变为选择第k-left-1小的数,问题的规模也变小。

  • 算法分析
    (1)最坏情况下的复杂性是O(n2),left 总是为空,第k个元素总是位于 right子集中。
    (2)设n=2k,算法的平均复杂性是O (logn)。若仔细地选择分界元素,则最坏情况下的时间开销也可以变成O(n)。
    (3)它与本章第一个例子非常相似,只对一半子集进行搜索,所不同的时,由于pivot点选择的不同,多数情况下它是非等分的。

  • 伪码描述
    选择性问题 伪码描述

贪心算法

是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。
贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推最终问题的最优解
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
贪心算法的难度是对选择正确性的证明

教室分配问题

  • 问题描述
    学生有n项活动申请使用某一个会议室,每项活动都有一个开始时间和一个结束时间。任何两个活动都不能同时使用这个会议室。问如何安排这些活动,使得被安排活动的数量达到最多

  • 问题分析
    设活动编号的集合为A={1,2,…,n},第i项活动的起始时间为si,结束时间为ei。满足si≥ej或sj≥ei,i≠j,称为活动相容。求A的两两相容的最大活动子集S

  • 思路
    1 更早的开始活动,也许能安排更多的活动,例A={1,2,3},s1=0, e1=17, s2=3,e2=5,s3=8,e3=15
    2 按占用时间由短到长来选择,也许可能安排更多的活动,例S={1,2,3},s1=0, e1=8, s2=7,e2=10,s3=9,se=15
    3 按结束时间从小到大选择,也许可以安排更多的活动。

  • 正确性证明
    贪心算法选择到第k步,有k项活动被选择,生成一个选择序是按结束时间从小到大进行排序的,即有e1≤e2≤e3…≤en,贪列i1,i2, …,ik(按策略必有i1=1),那么,最优解S必然包含i1,i2, …,ik。
    证明:设A中的活动都是按照结束的时间递增的顺序排列的,S是A的最优解且S={i1,i2, …,im}。
    (1) 当k=1时,需证明活动1被包含在最优解S中,即i1=1。假设i1≠1,那么用活动1去替换i1,得到S’,有S’=(S-{i1})∪{1},因为活动1结束时间比i1活动结束得更早,因此,其和i2, …,im活动均相容,又由于S与S’数量相同,所以,S’也是A的最优解。命题成立。(反证法)
    (2)假设对于任意整数k,命题正确。若前k步顺序选择的活动为i1,i2, …,ik,那么存在一个最优解S={i1,i2, …,ik}∪B
    对比(1)的证明,算法第一步选择结时间最早的活动总是导致一个最优解,故对子问题S’存在一个最优解B={ ik+1, …}。由于B与B都是S’的最优解,因B=B。于是
    S’={ i1,i2, …,ik }∪B*={ i1,i2, …,ik, ik+1}∪(B-{ ik+1})
    S’与S的活动数目一样多,也是一个最优解,而且恰好包含了算法前k+1步选择的活动。则命题得证。
    如果令S’是S中剩下的与{i1,i2, …,ik}相容的活动,S’⊂𝑆即有
    𝑆^′={𝑗|𝑠𝑗≥𝑒(𝑖_𝑘 ),𝑗∈𝑆}
    那么B是S’的一个最优解。若不然,假如S’有解B’,B’>B,那么用B’替换B以后得到解{i1,i2, …,ik}∪B’,将比S的活动更多,这与S是最优解矛盾。*

  • 计算模型
    (1)存储结构:

struct Active{
              	startTime	s; 	//活动开始时间
              	endTime 	e; 	//活动结束时间
              	selectflag	f;	//选标识 
          	        }A[n];

(2)计算:活动i与活动j相容A[i].s≥A[j].e或A[j].s≥A[i].e
用归并排序或其他任何高效的排序算法完成以
a[i].e的从小到大的排序,形成序列
A[1].e≤A[2].e ≤…≤A[n].e

  • 伪码描述与分析

数列极差问题

给定含有n个正整数的数列a,做如下两步操作:
(1)每一次删除其中的两个数ai和aj;
(2)在数列中加入一个数ai×aj+1,
循环执行步骤(1)(2)直到集合中只剩下一个元素为止。设计算法求得数列中剩余的数为最大值max和最小值min,则该数列的极差为M=max-min。
* 贪心思路

当一个数列中含有n(n>2)数时,该数列按数列极差法求最大数值的方法为:每次选择数列中最小的两个数进行相乘加1。求最小数值为:每次从数列中选择两个最大的数相乘加1
* 正确性证明 (数学归纳法

(1) 当k=3时,数列a={a1, a2, a3},不妨令a1< a2< a3,这样可以设a2= a1+k1 (k1>0);a3= a1+k1+k2(k1,k2>0)。那么这三个数的三种组合方式为:
(a1* a2+1)* a3+1
= a1* a1* a1+(2k1+k2) a1 a1 +(k1(k1+k2)+1)a1+k1+k2+1
(a1
a3+1)* a2+1
= a1* a1* a1+(2k1+k2) a1 a1 +(k1(k1+k2)+1)a1+k1 +1
(a2
a3+1)* a1+1
= a1* a1* a1+(2k1+k2) a1 a1 +(k1(k1+k2)+1)a1 +1
类比上述三式的同类项,可知命题成立
(2)假设k=n时命题成立。为了方便运算,不妨设{a1,a2,…,ak}为任意顺序的组合,[ai aj]= ai
aj +1,则数列a的最大极值
{an}max=[ a1 a2…an], a1<a2…<an 。
证明k=n+1成立,也就是证明{an+1}max=[ a1 a2…an an+1]。还可以表示为
{an+1}max=[[ a1 a2…an], an+1]= [ a1 a2…an]* an+1+1。
为了证明k=n+1成立,我们需要先证明一个引理。(
* 计算模型
(1)存储 用数组a[n]来数列
(2)计算 取最小值和第二小值:
vmin=min{a[n]}, vmins=min{{a[n]}-{vmin}}
取最大值和第二大值:
vmax=max{a[n]}, vmaxs=max{{a[n]}-{vmax}}
极大值计算: a[vmin]=a[ vmin]*a[ vmins]+1
极小值计算:a[vmax]=a[vmax]*a[vmaxs]+1
用最后一个元素覆盖a[ vmins]或a[vmax]

  • 伪码描述与分析

最优装载问题

有一批集装箱准备装上轮船,编号依次为1, 2, …, n,其中集装箱i的重量是wi,i=1, 2,…, n。已知轮船最多装载量是C,每个集装箱的重量wi≤C,且对集装箱无体积限制,设计算法求如何选择能够使得装上的集装箱个数最多。
* 贪心策略:轻者优先

  • 伪代码描述

  • 算法分析

数字串删除问题

键盘输入一个高精度的正整数N,去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数。对给于定的N和S,寻找一种方案使得剩下的数字组成的新数最小
输出:包括所去掉数字的位置和组成的新正整数

  • 问题分析
    1,高位的数字尽量小
    2,删除策略(贪心):删除高位较大的数字,相邻两位比较若高位比低位大则删除高位。
    枚举归纳,如当s=3时,输入如下符串
    n1=“1 2 4 3 5 8 6 3” → 12353 符合条件
    n2=“2 3 1 1 8 3 1” → 2111 * 最优解是 1131*
    n3=“1 2 3 4 5 6 7” → 1234567 此策略无法删除任意一个字符
    n4=“1 2 0 0 8 3” → 1003 删除的尾数不够

  • 计算模型
    (1)存储 a[]存储字数N,data[]记录删除的元素在原数字中位置;存在一位回溯的操作,设置变量j1来记住上一次删除的位置。
    (2)计算 删除a[i]=a[i+1],n=n-1。
    记录删除位置 data[i]=j+i, j1=j, if j≥j1
    data[i] =data[i-1]-1, if j<j1

  • 源码描述与分析

#### 近似贪心问题 
* 找零问题
(1)币值系统为:100元、50元、20元、10元、5元、1元
(2)客户购买了20元商品,递给收银员100元现钞,用最少的钱币数量进行找零,使用贪心算法的计算结果为1张50元、1张20元和1张10元。
(3)币值系统新增一40元钱币,用贪心算法仍为3张钱币,但是容易看出最优解是2张40元货币。我们把这类问题称为近似贪心问题

动态规划

与分治算法类似,动态规划算法也是将待求解问题分解成若干个子问题,分阶段求解子问题前一阶段子问题的解成为求后续阶段子问题的解的计算信息,最后用这些子问题的最优解构造出原问题的最优解

与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是互相独立的,若用分治法来解这类问题,则分解得到的子问题数目太多,且相互重叠,以至于解决原问题时间渐近复杂度为指数级。然而,不同子问题的数目常常只有多项式量级,且有些子问题被重复计算了许多次。如果能够保存已解决子问题的解,需要时直接使用,从而减少重复计算,就要可以得到多项式时间的算法。

子问题重叠
问题本身是可分解的,而且分解出来的子问题之间并不是独立的,存在重叠的部分。这个性质表现在两个方面:
①一个子问题的解可能与另一个子问题的解存在重复的部分;
②多个子问题的解在下一阶段决策中,可能被多个延续子问题多次使用

子问题的解为下一阶段的求解提供帮助

最优子结构
当一个问题的最优解包含着它的子问题的最优解时,称此问题具有最优子结构性质。问题所具有的这个性质是该问题可用动态规划算法或贪心算法求解的一个关键特征。

动态规划设计的基本步骤 **
1 找出
最优解的性质,并刻画其结构特征。
2 按最优解的性质,划分
子问题及演算的阶段**,建立递推方程
3 以自底向上自顶向下的记忆化方法(备忘录法)计算出最优值。
4 根据每阶段推算出的局部最优解,构造出全局最优解

数塔问题

  • 问题描述
    有一个数塔,结点中数字为其权值,按自顶向下(或自底向上)方式行走,经过的每一个结点,可以选择向左或向右走,到达底(或顶部),求一条自顶向下(或自底向上)的路径,所经过结点权值和最大
    问题描述与贪心求解

  • 动态规范问题分析
    第一阶段
    3+18>3+7,选择结点18,最优值21;
    17+7<17+12,选择结点12,最优值29;
    9+12>9+6,选择结点12,最优值21;
    4+6<4+16,选择结点16,最优值20
    第2阶段
    10+21<10+29,选择结点17;
    5+29>5+21,选择结点17;
    8+21>8+20,选择结点9。
    第3阶段
    11+39<11+34,选择结点10;
    14+34>14+29,选择结点5。
    第4阶段
    8+50>8+48,选择结点11。
    树塔问题 阶段求解

  • 计算模型
    存储结构:

data[n][n]     \\存储原始数据信息;
r[n][n]        \\存储每一阶段的路径的计算结果;
path[n][n]     \\存储下一步最优结点列坐标。

计算:阶段性最优
r[i][j]=max{r[i+1][j], r[i+1][j+1]}+data[i][j] i=n-2,…,1; j≤I
下一最优子结点的列坐标

最优解 data[1][1]data[2][j]data[i+1][j]……,i= j=1,j=j+path

  • 源码描述

  • 算法分析

投资分配问题

  • 问题描述
    设有n万元钱,投资m个项目,将xi万元钱投入第i个项产生的效益为f(xi),i=1,2,…,m。请问如何分配这n万元钱,使得投资的总效益最高
    投资分配问 例子

  • 问题分析

    第一阶段,只给项目1投资(只考虑项目1),列出项目1投资的最优投资方案,其获利方程可表示为g1(x)=f1(xi),xi=0,1,2,3,4,5
    阶段1
    第二阶段加入项目2,在第一阶段的基础上求出两个项目的分配方案,获利方程可表示


    阶段2
    第三阶段加入项目3,在第2阶段的基础上求出三个项目的分配本方案,获利方程可表示
    阶段3

  • 计算模型

  • 递推方程
    设k为阶段变量,0≤k≤m由问题分析可得递推方程和边界条件:
  • 存储模型
    a[][]表示某阶段最大获利情况下分配给某个项目资金。a[i][j]?
    f[]存储某项目初始投资所获得利润。f[i]表示投资i万元给某项目所获得的利润(数组值)。
    t[]存储当前投资额的最大利润
    g[]存储每一阶段的最优方案。每一阶段运算完毕后,更新g[k]=t[k]。
    max[]存储整个投资的最优分配方案。
  • 算法描述
    a[m][n]就表示投资n万元,得到最大获利后,分配项目m的资金。
    设rest=n,令max[m]=a[m][rest]表示最后一个项目在最优分配方案中的最大配额。
    rest=rest – a[m][n]
    a[m-1][rest]就表示给第m-1个项目分配rest取得最大利润后,分配给第m-1个项目资金。
    rest=rest – a[m-1][rest]表示减去最后两个项目后的投资,则a[m-2][rest]就表示给第m-2个项目分配rest取得最大利润后,分配给第m-2个项目资金。
    依此类推,……就可以找到最优解

概率算法

利用数据序列的随机性概率分布等特点,设计解决问题的算法或提高已有算法的效率

随机性(randomness)
某一事件集合中的各个事件所表现出来的不确定性。可能遵循某个概率分布

随机序列(random sequence)/随机变量序列
如果用X1,X2……Xn代表随机变量,这些随机变量如果按照顺序出现,就形成了随机序列。
(1) 序列中的每个变量都是随机的;
(2) 序列本身就是随机的。

随机数

  • 对产生随机数的数学方法要求:
    产生的数列具有均匀总体随机样本的统计学性质
    产生的数列要有足够的周期
    产生数列要速度快,占用计算机内存少

  • 线性同余
    同余 : 设a, b, m为整数,m>0,若a – b为m的整数倍,则称a与b关于模m同余;记为a≡b(mod m)

  • 线性同余发生器(Linear Congruence Generator, LCG)
    LCG 公式
    其中,b≥0, c ≥0, m>0, d≤m。m, c互质,m为机器所能取得的大数

  • 用线性同余法生成一个随机序列

  • 计算模型
    选择系统时间作为种子,令b=1194211693L,c=12345L,m=2147483647(2^31-1),则有
  • 源码描述

蒙特卡洛算法

蒙特卡洛方法(Monte Carlo method),也称统计模拟方法,随机化算法的种类之一是1940年代中期由于科学技术的发展和电子计算机的发明,而提出的一种以概率统计理论为指导的数值计算方法。是指使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。
与它对应的是确定性算法?(https://zh.wikipedia.org/wiki/%E7%A1%AE%E5%AE%9A%E6%80%A7%E7%AE%97%E6%B3%95)
蒙特卡洛方法在金融工程学,宏观经济学,生物医学,计算物理学(如粒子输运计算、量子热力学计算、空气动力学计算)等领域应用广泛。

蒙特卡洛算法的特点

蒙特卡罗算法在一般情况下可以保证对问题的所有实例都以高概率给出正确解,但是通常无法判定一个具体的解是否正确

  • p正确(p-correct)
    设如果一个MC算法对于问题的任一实例得到正确解释的概率不小于p,p是一个实数,且1/2≤p<1,称MC算法是p正确的,且称p - 1/2是该算法的优势。

  • 一致性(consistent)
    如果对于同一实例,蒙特卡罗算法不会给出2个不同的正确解答。

  • 偏真/偏假性
    设MC(x)是解某个判定问题D的蒙特卡罗算法,当MC(x)返回true时解总是正确的,仅当它返回false时有可能产生错误的解,称为偏真,反之为偏假。

蒙特卡洛算法进行数值计算

计算圆周率π

  • 数学模型
    数学模型:将n根飞镖随机投向一正方形的靶子,计算落入此正方形的内切圆中的飞镖数目k。假定飞镖击中方形靶子任一点的概率相等

    设圆的半径为r,圆的面积s1=πr2, 正方形面积s2=4r2
    由随机序列均匀分布的假设可知落入圆中的飞镖和正方形内的飞镖平均比为:k:n=πr2:4r2
    由此可知:π=4k/n
    取单位圆,取第一象限,则有长方形:0<=x<=1,0<=y<=1;圆形:x2+y2<=1

  • 算法描述:
    Step 1: 设定总点数
    Step 2: 取随机数x, y
    Step 3: 判断x2+y2<=1,是则圆点数加1
    Step 4: 判断所取点数达到总点数,是,计算π值,否,重复执行step 2.

  • 算法分析:O(n) 时间复杂度取决于点数
  • 算法实现

  • 结果分析:随机数需要足够多,才能计算出有效的π

蒙特卡洛算法进行素数判定

  • 线性筛法
  • 算法描述
    用筛法求素数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。
  • 代码实现
#define range 2000
bool IsPrime[range+1];
//set函数确定i是否为素数,结果储存在IsPrime[i]中 
void set(bool IsPrime[])
{
      int i,j;
      for(i=0;i<=range;++i)
      IsPrime[i]=true;
      IsPrime[0]=IsPrime[1]=false;
      for(i=2;i<=range;++i)
      {
            if(IsPrime[i])
            {
                  for(j=2*i;j<=range;j+=i)
                  IsPrime[j]=false;
            }
        }
}
  • 穷举法

  • 使用随机是判断是否是判定数字的因子 (偏假算法

  • 改进型蒙特卡洛算法判定素数

  • 相关数学定理


  • 计算模型
    a=Random(2, n-1)
    令m=n-1
    d=a2 mod n if m mod 2==0
    d=(a*(a2 mod n)) mod n if m mod 2==1
    若d==1且 a<>1 and a<>n-1,则n是合数

  • 源码实现

  • 算法分析

舍伍德算法

舍伍德算法是概率算法的一种,该文在比较线性表的顺序存储与链式存储的特点之后,提出了一种较优的数据结构——用数组模拟链表。理论上证明了采用舍伍德算法进行查找运算的时间复杂度为0(n),并在计算机上给出相应数据的模拟。

舍伍德算法总能求得问题的一个解,且所求得的解总是正确的。当一个确定性算法在最坏情况下的计算复杂性与其在平均情况下的计算复杂性有较大差别时,可以在这个确定算法中引入随机性将它改造成一个舍伍德算法,消除或减少问题的好坏实例间的这种差别。舍伍德算法精髓不是避免算法的最坏情况行为,而是设法消除这种最坏行为与特定实例之间的关联性

以快速排序为例分析舍伍德算法

快速排序即是一种最坏情况与平均情况差距较大的算法

  • 问题描述
    设A是含有n个元素的集合,从A中选出第k小的元素,其中1≤k≤n。

  • 问题分析
    快速排序分析
    最坏情况:T(1)=θ(1),T(n)=T(n-1)+T(1)+θ(n) =** O(n^2);有序且每次都取第一个元素,每次都分为1元素和n-1个元素
    最好情况:T(1)=θ(1), T(n)=2T(n/2)+θ(n) =
    O(nlogn)**,每次都恰好二分
    平均情况O(nlogn)
    查找第k小元素的时间复杂度至少:O(n)

  • 计算模型
    (1)数列存储于数组a中,下标从0开始;
    (2)pivot=a[left + rand() % (right - left)], j=right, 一趟快排后,则有以下三个结论。
    j-left=k-1,则分界数据就是选择问题的答案。
    j-left>k-1,则选择问题的答案继续在左子集中找,问题规模变小了。
    j-left<k-1,则选择问题的答案继续在右子集中找,问题变为选择第k-left-1小的数,问题的规模也变小。

  • 源码描述

拉斯维加斯算法

随机生成答案并检测

算法思想用随机序列代替有规律的枚举,判断枚举随机结果是否正确。
特征:得到的解都是正确的,但是有时找不到解。求得正确解的概率也依赖于算法所用的时间。
原理:通常采用bool型方法来表示拉斯维加斯算法。当算法找到一个解时返回true,否则false。当返回false时,说明未得到解,那么可再次独立调用该算法,在时间允许的情况一直运算到出解为止

n皇后问题

  • 问题分析
    对于n皇后问题的任何一个解而言,每一个皇后在棋盘上的位置无任可规律,不具有系统性,像是随机放置的。适合Las Vegas。

  • 模型
    (1)数据结构:try[1]~try[8]存放摆放列坐标,(i, try[i])表示存放位置;
    (2)为第i行选取位置:try[i]=Random(1,8);
    (3) 检测:当try[i]≠try[k] and abs(try[i]- try[k])≠abs(i-k)时,位置正确

  • 算法设计
    (1) 选取位置try[i]=random(1,8)
    (2) 检测位置是否正确check(try, i),正确转步骤(3),不正确且未足1000次,转(1);
    (3) i=8输出正确结果,i ≠8且大于1000次,无解。

  • 时间复杂度
    O(n2)

  • 算法改进
    先在棋盘中随机放置若干行,后继算法用回溯随机放置皇后越多,回溯时间越少



处身寒夜,把握星光。