unity3d教程:游戏开发算法(四)

来源:互联网 发布:中国互联网金融数据 编辑:程序博客网 时间:2024/05/16 17:25
上述二进制大整数乘法同样可应用于十进制大整数的乘法以提高乘法的效率减少乘法次数。

    【问题】 最接近点对问题

    问题描述:

    在应用中,常用诸如点、圆等简单的几何对象代表现实世界中的实体。在涉及这些几何对象的问题中,常需要了解其邻域中其他几何对象的信息。例如,在空 中 交通控制问题中,若将飞机作为空间中移动的一个点来看待,则具有最大碰撞危险的2架飞机,就是这个空间中最接近的一对点。这类问题是计算几何学中研究的基 本问题之一。下面我们着重考虑平面上的最接近点对问题。

    最接近点对问题的提法是:给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小。

    严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。

    这个问题很容易理解,似乎也不难解决。我们只要将每一点与其他n-1个点的距离算出,找出达到最小距离的两个点即可。然而,这样做效率太低,需要O(n2)的计算时间。我们能否找到问题的一个O (nlogn)算法。

    这个问题显然满足分治法的第一个和第二个适用条件,我们考虑将所给的平面上n个点的集合S分成2个子集S1和S2,每个子集中约有n/2个点,然后 在 每个子集中递归地求其最接近的点对。在这里,一个关键的问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点 对,因为S1和S2的最接近点对未必就是S的最接近点对。如果组成S的最接近点对的2个点都在S1中或都在S2中,则问题很容易解决。但是,如果这2个点 分别在S1和S2中,则对于S1中任一点p,S2中最多只有n/2个点与它构成最接近点对的候选者,仍需做n2/4次计算和比较才能确定S的最接近点对。 因此,依此思路,合并步骤耗时为O(n2)。整个算法所需计算时间T(n)应满足:

    T(n)=2T(n/2)+O(n2)

    它的解为T(n)=O(n2),即与合并步骤的耗时同阶,显示不出比用穷举的方法好。从解递归方程的套用公式法,我们看到问题出在合并步骤耗时太多。这启发我们把注意力放在合并步骤上。

    为了使问题易于理解和分析,我们先来考虑一维的情形。此时S中的n个点退化为x轴上的n个实数x1、x2、…、xn。最接近点对即为这n个实数中相 差 最小的2个实数。我们显然可以先将x1、x2、…、xn排好序,然后,用一次线性扫描就可以找出最接近点对。这种方法主要计算时间花在排序上,因此如在排 序算法中所证明的,耗时为O(nlogn)。然而这种方法无法直接推广到二维的情形。因此,对这种一维的简单情形,我们还是尝试用分治法来求解,并希望能 推广到二维的情形。

    假设我们用x轴上某个点m将S划分为2个子集S1和S2,使得S1={x∈S | x≤m};S2={x∈S | x>m}。这样一来,对于所有p∈S1和q∈S2有p  递归地在S1和S2上找出其最接近点对{p1,p2}和{q1,q2},并设δ=min {|p1-p2|,|q1-q2|},S中的最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{p3,q3},其中p3∈S1且 q3∈S2。如图1所示。

    图1 一维情形的分治法

    我们注意到,如果S的最接近点对是{p3,q3},即 | p3-q3 | < δ,则p3和q3两者与m的距离不超过δ,即 | p3-m | < δ,| q3-m | < δ,也就是说,p3∈(m-δ,m),q3∈(m,m+δ)。由于在S1中,每个长度为δ的半闭区间至多包含一个点(否则必有两点距离小于δ),并且m是 S1和S2的分割点,因此(m-δ,m)中至多包含S中的一个点。同理,(m,m+δ)中也至多包含S中的一个点。由图1可以看出,如果(m-δ,m)中 有S中的点,则此点就是S1中最大点。同理,如果(m,m+δ)中有S中的点,则此点就是S2中最小点。因此,我们用线性时间就能找到区间(m-δ,m) 和(m,m+δ)中所有点,即p3和q3。从而我们用线性时间就可以将S1的解和S2的解合并成为S的解。也就是说,按这种分治策略,合并步可在O(n) 时间内完成。这样是否就可以得到一个有效的算法了呢? 还有一个问题需要认真考虑,即分割点m的选取,及S1和S2的划分。选取分割点m的一个基本要求是由此导出集合S的一个线性分割,即S=S1∪S2 ,S1∩S2=Φ,且S1 {x | x≤m};S2 {x | x>m}。容易看出,如果选取m=[max(S)+min(S)]/2,可以满足线性分割的要求。选取分割点后,再用O(n)时间即可将S划分成 S1={x∈S | x≤m}和S2={x∈S | x>m}。然而,这样选取分割点m,有可能造成划分出的子集S1和S2的不平衡。例如在最坏情况下,|S1|=1,|S2|=n-1,由此产生的分 治法在最坏情况下所需的计算时间T(n)应满足递归方程:

    T(n)=T(n-1)+O(n)

    它的解是T(n)=O(n2)。这种效率降低的现象可以通过分治法中“平衡子问题”的方法加以解决。也就是说,我们可以通过适当选择分割点m,使 S1 和S2中有大致相等个数的点。自然地,我们会想到用S的n个点的坐标的中位数来作分割点。在选择算法中介绍的选取中位数的线性时间算法使我们可以在O (n)时间内确定一个平衡的分割点m。

    至此,我们可以设计出一个求一维点集S中最接近点对的距离的算法pair如下。

[AppleScript] 纯文本查看 复制代码
  Float pair(S);


  { if | S | =2 δ= | x[2]-x[1] | /*x[1..n]存放的是S中n个点的坐标*/


  else


  { if ( | S | =1) δ=∞


  else


  { m=S中各点的坐标值的中位数;


  构造S1和S2,使S1={x∈S | x≤m},S2={x∈S | x>m};


  δ1=pair(S1);


  δ2=pair(S2);


  p=max(S1);


  q=min(S2);


  δ=min(δ1,δ2,q-p);


  }


  return(δ);


  }


    由以上的分析可知,该算法的分割步骤和合并步骤总共耗时O(n)。因此,算法耗费的计算时间T(n)满足递归方程:

    解此递归方程可得T(n)=O(nlogn)。

    【问题】循环赛日程表

    问题描述:设有n=2k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:

    (1)每个选手必须与其他n-1个选手各赛一次;

    (2)每个选手一天只能参赛一次;

    (3)循环赛在n-1天内结束。

    请按此要求将比赛日程表设计成有n行和n-1列的一个表。在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。其中1≤i≤n,1≤j≤n-1。

    按分治策略,我们可以将所有的选手分为两半,则n个选手的比赛日程表可以通过n/2个选手的比赛日程表来决定。递归地用这种一分为二的策略对选手进行划分,直到只剩下两个选手时,比赛日程表的制定就变得很简单。这时只要让这两个选手进行比赛就可以了。

    1 2 3 4 5 6 7

    1 2 3 4 5 6 7 8

    2 1 4 3 6 7 8 5

    3 4 1 2 7 8 5 6

    1 2 3 4 3 2 1 8 5 6 7

    1 2 3 4 5 6 7 8 1 4 3 2

    1 2 1 4 3 6 5 8 7 2 1 4 3

    1 2 3 4 1 2 7 8 5 6 3 2 1 4

    2 1 4 3 2 1 8 7 6 5 4 3 2 1

    (1) (2) (3)

    图1 2个、4个和8个选手的比赛日程表

    图1所列出的正方形表(3)是8个选手的比赛日程表。其中左上角与左下角的两小块分别为选手1至选手4和选手5至选手8前3天的比赛日程。据此,将左 上角小块中的所有数字按其相对位置抄到右下角,又将左下角小块中的所有数字按其相对位置抄到右上角,这样我们就分别安排好了选手1至选手4和选手5至选手 8在后4天的比赛日程。依此思想容易将这个比赛日程表推广到具有任意多个选手的情形。

    八、动态规划法

    经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。

    为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用。

    【问题】 求两字符序列的最长公共字符子序列

    问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X= “x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列,使得对所有的j=0,1,…,k- 1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

    给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列。问题要求已知两序列A和B的最长公共子序列。

    如采用列举A的所有子序列,并一一检查其是否又是B的子序列,并随时记录所发现的子序列,最终求出最长公共子序列。这种方法因耗时太多而不可取。

    考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

    (1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

    (2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

    (3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

    这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2” 的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共 子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

    定义c[j]为序列“a0,a1,…,ai-2”和“b0,b1,…,bj-1”的最长公共子序列的长度,计算c[j]可递归地表述如下:

    (1)c[j]=0 如果i=0或j=0;

    (2)c[j]= c[i-1][j-1]+1 如果I,j>0,且a[i-1]=b[j-1];

    (3)c[j]=max(c[j-1],c[i-1][j]) 如果I,j>0,且a[i-1]!=b[j-1]。

    按此算式可写出计算两个序列的最长公共子序列的长度函数。由于c[j]的产生仅依赖于c[i-1][j-1]、c[i-1][j]和c[j-1],故可以从c[m][n]开始,跟踪c[j]的产生过程,逆向构造出最长公共子序列。细节见程序。



    1、动态规划的适用条件

    任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。

    (1)最优化原理(最优子结构性质)

    最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

    例如图2中,若路线I和J是A到C的最优路径,则根据最优化原理,路线J必是从B到C的最优路线。这可用反证法证明:假设有另一路径J‘是B到C的最优路径,则A到C的路线取I和J’比I和J更优,矛盾。从而证明J‘必是B到C的最优路径。

    最优化原理是动态规划的基础,任何问题,如果失去了最优化原理的支持,就不可能用动态规划方法计算。根据最优化原理导出的动态规划基本方程是解决一切动态规划问题的基本方法。

    (2)无后向性

    将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

    (3)子问题的重叠性

    动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种 状态,所以它的空间复杂度要大于其它的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。//unity3d教程手册:www.unitymanual.com

    所以,能够用动态规划解决的问题还有一个显着特征:子问题的重叠性。这个性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。

    2、动态规划的基本思想

    前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段 决策问题时推导出来的,具有严格的数学形式,适合用于理论上的分析。在实际应用中,许多问题的阶段划分并不明显,这时如果刻意地划分阶段法反而麻烦。一般 来说,只要该问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子化原理),则可以考虑用动态规划解决。

    动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。

    由此可知,动态规划法与分治法和贪心法类似,它们都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择 可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的(即 不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解 时,则难以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。

    解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值 的解。若存在若干个取最优值的解的话,它只取其中的一个。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是, 动态规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存 起来,避免每次碰到时都要重复计算。

    因此,动态规划法所针对的问题有一个显着的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。

    3、动态规划算法的基本步骤

    设计一个标准的动态规划算法,通常可按以下几个步骤进行:

    (1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。//unity3d教程手册:www.unitymanual.com

    (2)选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

    (3)确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的 状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。

    (4)写出规划方程(包括边界条件):动态规划的基本方程是规划方程的通用形式化表达式。

    一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。根据动态规划的基本方程可以直接递归计算最优值,但是一般将其改为递推计算,实现的大体上的框架如下:

    标准动态规划的基本框架

    1. 对fn+1(xn+1)初始化;   {边界条件}

    for k:=n downto 1 do

    for 每一个xk∈Xk do

    for 每一个uk∈Uk(xk) do

    begin

    5.         fk(xk):=一个极值;           {∞或-∞}

    6.         xk+1:=Tk(xk,uk);             {状态转移方程}

    7.         t:=φ(fk+1(xk+1),vk(xk,uk));     {基本方程(9)式}

    if t比fk(xk)更优 then fk(xk):=t; {计算fk(xk)的最优值}

    end;

    9. t:=一个极值;                     {∞或-∞}

    for 每一个x1∈X1 do

    11.   if f1(x1)比t更优 then t:=f1(x1);     {按照10式求出最优指标}

    12. 输出t;

    但是,实际应用当中经常不显式地按照上面步骤设计动态规划,而是按以下几个步骤进行:

    (1)分析最优解的性质,并刻划其结构特征。

    (2)递归地定义最优值。

    (3)以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。

    (4)根据计算最优值时得到的信息,构造一个最优解。

    步骤(1)~(3)是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤(4)可以省略,若需要求出问题的一个最优解,则必须执行步骤(4)。此 时,在步骤(3)中计算最优值时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速地构造出一个最优解。

    【问题】   凸多边形的最优三角剖分问题

    问题描述:多边形是平面上一条分段线性的闭曲线。也就是说,多边形是由一系列首尾相接的直线段组成的。组成多边形的各直线段称为该多边形的边。多边形相接 两条边的连接点称为多边形的顶点。若多边形的边之间除了连接顶点外没有别的公共点,则称该多边形为简单多边形。一个简单多边形将平面分为3个部分:被包围 在多边形内的所有点构成了多边形的内部;多边形本身构成多边形的边界;而平面上其余的点构成了多边形的外部。当一个简单多边形及其内部构成一个闭凸集时, 称该简单多边形为凸多边形。也就是说凸多边形边界上或内部的任意两点所连成的直线段上所有的点均在该凸多边形的内部或边界上。

    通常,用多边形顶点的逆时针序列来表示一个凸多边形,即P=表示具有n条边v0v1,v1v2,…,vn-1vn的一个凸多边形,其中,约定v0=vn 。

    若vi与vj是多边形上不相邻的两个顶点,则线段vivj称为多边形的一条弦。弦将多边形分割成凸的两个子多边形和。多边形的三角剖分是一个将多边形分割成互不重迭的三角形的弦的集合T。图1是一个凸多边形的两个不同的三角剖分。

    (a)   (b)

    图1 一个凸多边形的2个不同的三角剖分

    在凸多边形P的一个三角剖分T中,各弦互不相交且弦数已达到最大,即P的任一不在T中的弦必与T中某一弦相交。在一个有n个顶点的凸多边形的三角刮分中,恰好有n-3条弦和n-2个三角形。

    凸多边形最优三角剖分的问题是:给定一个凸多边形P=以及定义在由多边形的边和弦组成的三角形上的权函数ω。要求确定该凸多边形的一个三角剖分,使得该三角剖分对应的权即剖分中诸三角形上的权之和为最小。

    可以定义三角形上各种各样的权函数ω。例如:定义ω(△vivjvk)=| vivj |+| vivk |+| vkvj |,其中,| vivj |是点vi到vj的欧氏距离。相应于此权函数的最优三角剖分即为最小弦长三角剖分。

    (1)最优子结构性质

    凸多边形的最优三角剖分问题有最优子结构性质。事实上,若凸(n+1)边形P=的一个最优三角剖分T包含三角形v0vkvn,1≤k≤n-1,则T的权为 3个部分权的和,即三角形v0vkvn的权,子多边形的权和的权之和。可以断言由T所确定的这两个子多边形的三角剖分也是最优的,因为若有或的更小权的三 角剖分,将会导致T不是最优三角剖分的矛盾。//Unity3D教程手册:www.unitymanual.com

    (2)最优三角剖分对应的权的递归结构

    首先,定义t[i,j](1≤i的最优三角剖分所对应的权值,即最优值。为方便起见,设退化的多边形具有权值0。据此定义,要计算的凸(n+1)边多边形P对应的权的最优值为t[1,n]。

    t[i,j]的值可以利用最优子结构性质递归地计算。由于退化的2顶点多边形的权值为0,所以t[i,i]=0,i=1,2,…,n 。当j一i≥1时,子多边形至少有3个顶点。由最优于结构性质,t[i,j]的值应为t[i,k]的值加上t[k+1,j]的值,再加上△vi- 1vkvj的权值,并在i≤k≤j-1的范围内取最小。由此,t[i,j]可递归地定义为:

    (3)计算最优值

    下面描述的计算凸(n+1)边形P=的三角剖分最优权值的动态规划算法MINIMUM_WEIGHT,输入是凸多边形P=的权函数ω,输出是最优值t [i,j]和使得t[i,k]+t[k+1,j]+ω(△vi-1vkvj)达到最优的位置(k=)s[i,j],1≤i≤j≤n 。

[AppleScript] 纯文本查看 复制代码
 Procedure MINIMUM_WEIGHT(P,w);


  Begin


  n=length[p]-1;


  for i=1 to n do t[i,i]:=0;


  for ll=2 to n do


  for i=1 to n-ll+1 do


  begin


  j=i+ll-1;


  t[i,j]=∞;


  for k=i to j-1 do


  begin


  q=t[i,k]+t[k+1,j]+ω(△vi-1vkvj);


  if qbegin


  t[i,j]=q;


  s[i,j]=k;


  end;


  end;


  end;


  return(t,s);


  end;