易码技术论坛

 找回密码
 加入易码
搜索
查看: 131466|回复: 4

有关Pascal

[复制链接]
发表于 2004-9-12 10:20:00 | 显示全部楼层
>> 动态规划

    在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都要做出决策,从而使整个过程达到最好的活动效果。因此,各个阶段决策确定后,组成一个决策序列,因而也就确定了整个过程的一条活动路线。这种把一个问题看作是一个前后关联具有链状结构的多阶段过程,就称为多阶段决策过程,这种问题称为多阶段决策问题。
在多阶段决策问题中,各个阶段采取的决策。一般来说是和时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有"动态"的含义,我们称这种解决多阶段决策最优化的过程为动态规划。

    动态规划特征:

    动态规划的显著特征是:无后效性,有边界条件,且一般划分为很明显的阶段。
    动态规划一般还存在一条或多条状态转移方程。

 

  例题 Catcher防卫导弹 (GDOI'98)
    题目讲得很麻烦,归根结底就是求一整串数中的最长不上升序列
    这道题目一开始我使用回溯算法,大概可以拿到1/3的分吧,后来发现这其实是动态规划算法中最基础的题目,用一个二维数组C[1..Max,1..2]来建立动态规划状态转移方程(注:C[1..Max,1]表示当前状态最多可击落的导弹数,C[1..Max,2]表示当前状态的前继标志):Ci=Max{C[j]+1,(j=i+1..n)},然后程序也就不难实现了.
示范程序:

program catcher_hh;
var
f:text;
i,j,k,max,n,num:integer;
a:array [1..4000] of integer; {导弹高度数组}
c:array [1..4000,1..2] of integer; {动态规划数组}
procedure readfile;
begin
assign(f,'catcher.dat'); reset(f);
readln(f,num);
for i:=1 to num do
readln(f,a);
end;
procedure work;
begin
fillchar(c,sizeof(c),0); c[num,1]:=1; {清空数组,赋初值}
{开始进行动态规划}
for i:=num-1 downto 1 do
begin
n:=0; max:=1;
for j:=i+1 to num do
if (a>a[j]) and (max<1+c[j,1])
then begin n:=j; max:=1+c[j,1]; end;
c[i,1]:=max; c[i,2]:=n;
end;
writeln; writeln('Max : ',max); {打印最大值}
max:=0; n:=0;
for i:=1 to num do
if c[i,1]>max then begin max:=c[i,1]; n:=i; end;
{返回寻找路径}
repeat
writeln(n,' '); n:=c[n,2];
until n=0;
end;
begin
readfile; work;
end.

 

>> 标号算法

标号法是一种最佳算法,多用于求图的最短路问题。
    一、标号法的概念:
     所谓标号,是指与图的每一个顶点相对应的一个数字。标号法可以说是动态规划,它采用顺推的方法,对图的每一边检测一次,没有重复的回溯搜索,因此标号法是一种最佳算法。

    二、标号法的算法流程:
     现有一图G,求从起点Vs到终点Ve的最短距离。 设:
     Sum(j)───顶点Vj的标号,代表的是Vs到Vj的最短距离。
     Vj已标味着Vs到Vj的最短路以及这条路径的长度已求出。
     M(i,j)───Vi到Vj的非负长度。
     H(j)───顶点Vj的前趋结点。 标号法的算法流程如下:

                sum(s)←0
                   ↓
               Vs进入队列L
                   ↓      
  -----→移出队列L的队首Vk←-----
|                 ↓                                  |
|          Vk是不是Ve------------------|---→计算结束打印路径
|              N∣  Y                                 |
|                 ↓                                  |
|           由Vk扩展出结点Vj                          |
|          (Vk与Vj之间相连)                         |
|            Sj←Sum(k)+M(k,j)                       |
|                     ↓                              |
|               Sj小于Sum(j)                        |
|                       |                             |
|               Y     |    N                          |
|                      |     -------------------------
|                      |
|                    ↓
|          Sum(j)←Sj
|           H(j)← Vk
|       Vj加入队列L并对队列L按Sum值由小到大排序
|                    ↓
  ---------------

注意:1.只有两个顶点间的距离为非负时,才可用标号法。 2.只有队列的首结点是目标结点时,才可停止计算。否则得出的不一定是最优解。

例题解析:
     1.相邻项序列(GDOI97第四题)
    问题描述:
     对于一个N*N(<=100)的正整数矩阵M,存在从M[A1,B1] 开始到M[A2,B2]结束的相邻项序列.两个项M[I,J]和M[K,L]相邻的件是指满足如下情况之一:
(1)I=K+-1和J=L
     (2)I=K和J=L+-1。
     任务:从文件中输入矩阵M,再读入K(K<=4)组M[A1,B1]和M[A2,B2]的值。对于每一组M[A1,B1]和M[A2,B2],求一相邻项序列,使得相邻项之差的绝对值之和为最小。
     输入格式:
     4 ───N
     1 9 6 12 ───每行N个数据,共N行
     8 7 3 5
     5 9 11 11
     7 3 2 6
     2 ───K
     4 1 1 4 ───表示A1,B1和A2,B2的值,共K行 2 2 3 4
     输出格式:
    1 17 ───第一组数据相邻项之差的绝对值之和的最小值是17
     7 5 8 7 9 6 12───第一组数据的相邻项序列
     2 4
     7 9 11 11
     解析:本题若将相邻的两个数看作是两个顶点,两个数之差的绝对值作为权,则问题转化成求两个顶点的最短路问题。 设:Sum[I,J]为从起点Vs到结点M[I,J]的最短距离。 H[I,J]记录结点M[I,J]的前趋结点。 L为记录待扩展的结点的队列。 鉴于数组进行排序时速度较慢,所以用链表作为记录结点的队列的类型,适于排序。
     参考程序:

Program gdoi974;
const fang:array [1..4,1..2] of integer =((-1,0),(0,-1),(1,0),(0,1));
{上下左右四个方向}
type
{定义POINT类型,其中X,Y为结点在矩阵中的坐标,NEXT为队列中的后继结点}
    point=^note;
    note=record
         x,y:byte;
         next:point;
         end;
var
    sum:Array [1..100,1..100] of integer;
    m:Array [1..100,1..100] of integer;
    h:Array [1..100,1..100,1..2] of byte;
    f1,f2:text;
    a,b,x1,y1,x2,y2,n,k,zz:integer;
procedure print;
var
    a,b,x,y,x3,y3:integer;
    c:array [1..100] of integer;
    flag:boolean;

begin
     flag:=true; a:=1; c[a]:=m[x2,y2];
     x:=x2; y:=y2;
     while flag do
     begin
          a:=a+1; x3:=x; y3:=y;
          x:=h[x3,y3,1]; y:=h[x3,y3,2];
          c[a]:=m[x,y];
          if (x=x1) and (y=y1) then flag:=false;
     end;   {求出整条路径,放入数组C中}
     writeln (f2,zz,' ',sum[x2,y2]);
     for b:=a downto 1 do
     write (f2,c,' '); {打印结果}
     writeln (f2);
end;
procedure add(x,y,i:integer;var l:point);
var
   e,f,g:point;
   a,b,c:integer;
   flag:boolean;
begin
     new (e);
     e^.x:=x; e^.y:=y;
     if i=0 then l^.next:=e {加入队列}
     else begin
          f:=l; g:=f^.next; flag:=true;
          for a:=1 to i do
          begin
               if sum[g^.x,g^.y]>sum[x,y] then begin
                 e^.next:=g; f^.next:=e; flag:=false; a:=i; {加入队列}
               end;
               f:=f^.next; g:=f^.next;
          end;
          if flag then f^.next:=e; {加入队列}
          end;
end;
procedure try(xz,yz:byte);
var
   a,b,c,sj,x,y,x1,y1:integer;
   e,l,v:point;
   flag:boolean;
begin
     fillchar (sum,sizeof (sum),255); {置Sum值为-1}
     sum[xz,yz]:=0;{置起点Sum值为0}
     flag:=true;
     new (e); e^.x:=xz; e^.y:=yz;
     new (l); l^.next:=e; {起点进入队列}
     c:=1; {现在队列结点个数}
     while flag do
     begin
          v:=l^.next; dispose (l); {取出首结点V}
          l:=v; c:=c-1;{指针下移一位,结点个数减一}
          x:=v^.x; y:=v^.y;
          if (x=x2) and (y=y2) then flag:=false; {若为目标结点,则结束计算}
          if flag then
          begin
               for a:=1 to 4 do  {向四个方向扩展}
               begin
                    x1:=x+fang[a,1];
                    y1:=y+fang[a,2];
                    if (x1>0) and (x1<=n) and (y1>0) and (y1<=n) then
                    begin
                         sj:=sum[x,y]+abs (m[x,y]-m[x1,y1]);
                         if (sj < sum[x1,y1]) or (sum[x1,y1]=-1) then
                         begin
                               sum[x1,y1]:=sj;
                               h[x1,y1,1]:=x; h[x1,y1,2]:=y;{记录路径}
                               add(x1,y1,c,l); {将新扩展出来的结点进入队列}
                               c:=c+1; {结点个数加一}
                         end;
                    end;
               end;
         end;
     end;
     print;{打印结果}
end;
Begin
     assign (f1,'gdoi974.dat');
     assign (f2,'gdoi974.out');
     reset (f1); rewrite (f2);
     readln (f1,n);
     for a:=1 to n do
     begin
          for b:=1 to n do
             read (f1,m[a,b]);
          readln (f1);
     end; {读入数组}
     readln (f1,k);
     for a:=1 to k do
     begin
          zz:=a;
          readln (F1,x1,y1,x2,y2); {读入任务}
          try(x1,y1);
     end;
     close(f1);
     close(f2);
End.




[此贴子已经被作者于2004-9-12 10:22:14编辑过]

 楼主| 发表于 2004-9-12 10:21:00 | 显示全部楼层
Pascal编译错误对照表

下面列出在编译程序时可能出现的错误,在集成环境下,Turbo Pascal将自动加载源程序并定位于出错处。  
l 内存溢出2 缺标识符
3 标识符未定义
4 标识符重定义
5 语法错误
6 实型常量错
7 整型常量错
8 字符串常量跨行
9 文件嵌套过多
10 非正常文件结束
11 行过长
12 缺类型标识符
13 打开文件过多
14 无效文件名
15 文件未找到
16 磁盘满
17 无效编译指示
18 文件过多
19 指针定义中未定义类型
20 缺变量标识符
21 类型错误
22 结构过长
24 文件分量不能为文件
25 无效字符串长度
26 类型不匹配
27 无效子界基类型
28 下界大于上界
29 缺有序类型
30 缺整型常数
31 缺常数
32 缺整型或实型常数
33 缺指针类型标识符
34 无效的函数结果类型
35 缺标号标识符
36 缺BEGIN
37 缺END
38 缺整型表达式
39 缺有序表达式
40 缺布尔表达式
41 操作数类型与操作符不匹配
42 表达式错
43 非法赋值
44 缺字段标识符
45 目标文件过长
46 未定义外部标识符
47 数据段过长
50 缺DO
51 无效PUBLIC定义
52 无效EXTRN定义
53 EXTRN定义过多
54 缺0F
55 缺INTERFACE
56 无效重定位引用
57 缺THEN
58 缺T0或DOWNTO
59 未定义的向前引用
60 过程过多
61 无效类型转换
62 被零除D
63 无效文件类型
64 不能读写该类型的变量
65 缺指针变量
66 缺字符串变量
67 缺字符串表达式
68 单元循环引用
69 单元名不匹配
70 单元版本不匹配
71 单元重名
72 单元文件格式错误
73 缺IMPLEMENTATl0N
74 常数与CASE类型不相匹配
75 缺记录变量
76 常数越界
77 缺文件变量
78 缺指针变量
79 缺整型或实型表达式
80 标号不在当前块中
81 标号已定义
82 标号未定义
83 无效参数
84 缺UNIT
85 缺“;”
86 缺“:”
87 缺“,”
88 缺“(”
89 缺“)”
90 缺“=”
91 缺“:=”
92 缺“[”或“(.”
93 缺“]”或“.)”
94 缺“.”
96 变量过多
97 无效FOR控制变量
98 缺整型变量
99 此处不允许用文件和
100 字符串长度不匹配
101 无效字顺序
102 缺字符串常数
103 缺整型或实型变量
104 缺有序变量
105 INLINE错
106 缺字符表达式
107 重定位项过多
112 CASE常量越界
113 语句错
114 不能调用中断过程
116 必须在8087方式下编译
117 末找到目标地址
118 此处不允许包含文件
120 缺NIL
121 无效限定符
122 无效变量引用
123 符号过多
124 语句部分过长
126 文件必须为变量参数
127 条件符号过多
128 条件指令错位
130 初始条件定义错
13l 过程和函数头与前面定义的不匹酉
132 严重磁盘错误
133 不能计算该表达式
134 表达式错误结束
l35 无效格式说明符
136 无效间接引用
137 此处不允许结构变量
138 无SYSTEM单元不能计算
l39 不能存取该符号
140 无效浮点运算
141 不能将覆盖编译至内存
142 缺过程和函数变量
143 无效过程或函数引用
144 不能覆盖该单元
147 缺对象类型
148 不允许局部对象类型
149 缺VIRTUAL
150 缺方法标识符
151 不允许虚拟构造方法
152 缺构造方法标识符
153 缺释放方法标识符
154 FAIL只允许在构造方法内使用
155 无效的操作符和操作数组合
156 缺内存引用
l57 不能加减可重定位符号
158 无效寄存器组合
159 未激活286/287指令
160 无效符号引用
161 代码生成错
162 缺ASM
 楼主| 发表于 2004-9-12 10:26:00 | 显示全部楼层
说明:
以上来自 陈杰个人主页
zxy_0112 该用户已被删除
发表于 2005-2-27 00:52:00 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
 楼主| 发表于 2004-9-12 10:19:16 | 显示全部楼层 |阅读模式
Pascal算法设计
>> 序 言   
    算法就是一组(有限个)规则,它为某个特定问题提供了解决问题的运算序列,通俗点说就是计算机解题过程。在这个过程中,无论是形成解题思路还是编写程序,都是在实施某种算法。前者是推理实现的算法,后者是操作实现的算法。

       计算机的核心就是算法,一个算法应该具有以下五个特征:

  (1).有穷性      一个算法必须保证执行有限步之后结束

  (2).确切性      算法的每一步必须有确切定义

  (3).输  入      一个算法有0或多个输入,以刻划对象的初始情况。所谓0个

                  输出是指算法本身给出了初始条件。

  (4).输  出      一个算法的输出有一个或多个,以反映对输入数据加工以后

                  的结果,没有输出的算法是毫无意义的。

  (5).能行性      算法原则上能够精确地运行,且人们有笔纸也能作出来。

    下面我想对构成算法的一些基本方法(公式,方案,原则),即对解题思路的讨论。对于读者来说,为了获得一个既有效有优美的算法,必须掌握一些基本的算法。

>> 递推算法

    有一类题,每相邻两项之间的变化都有一定的规律性,我们将这种规律性简洁地归纳为递推式:

       Fn=g(Fn-1)

    这就在数的序列中建立前项与后项的关系。然后从初始条件(或结果)入手,一步步地按关系递 推,求出最后结果(或初始值)。很多程序都是按照这中方法执行的,如果能找到前项与后项的递推 关系,让高速的计算机重复计算,可真起到“物尽其用的效果”。

    递推分为顺推和倒推,一般分析思路为:

    If 求解初始条件F1;

      Then Begin(倒推);

        由递推关系求出最终结果Fn;

          求出倒推关系Fi-1=g'(Fi);

         i:=n;(由最终结果出发进行倒推);

         While 当前结果非初始值 do 倒推前项;

         输出初始值和倒推过程;

         end

    Else (顺推) Begin

         由递推关系求出初始值F1;

         求出倒推关系Fi=g'(Fi-1);

         i:=1;(由初始值出发进行顺推);

         While 当前结果非最终结果 do 顺推后项;

         输出最终结果和顺推过程;

         end;

 

>> 贪心算法

    和顺推法相仿,贪心算法也是从某个初始解出发,向给定的目标递推。但不同的是递推的每一步不是依据某一固定的递推式,而是做一个最佳的贪心选择,不断地把一个问题归纳成更小的相似问题。并期望通过局部的选择产生一个全局最优的解。

一、贪心法的基本思路:

    从问题的初始解出发,逐步逼向目标,尽可能快地找更好的解,当算法达到某一步不再继续前进时,算法宣告停止。
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。

二、实现该算法的过程:

   从问题某一初始值出发;
while 能朝给定总目标前进一步 do
  求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;   

  

例题:最优乘车问题
  输入数据:输入文件INPUT.TXT。文件的第行是一个数字M(1≤M≤100)表示开通了M条单向巴士线路,第2行是一个数字N(1<N≤500),表示共有N个车站。从第3行到第M+2行依次给出了第一条到第M条巴士线路的信息。其中第i+2行给出的是第i条巴士线路的信息,从左至右依次按行行驶顺序给出了该线路上的所有站点,相邻两个站号之间用一个空格隔开。

  输出数据:输出文件是OUTPUT.TXT。文件只有一行,为最少换车次数(在0,1,…,M-1中取值),0表示不需换车即可达到。如果无法乘车达到S公园,则输出"NO"。

  Program Travel;
  var m:1..100; {m为开通的单向巴士线路数}
    n:1..500; {n为车站总数}
    result:array[1..501] of -1..100; {到某车站的最少换车数}
    num:array[1..500,1..50] of 1..500; {从某车站可达的所有车站序列}
    sum:array[1..500] of 0..50; {从某车站可达的车站总数}
    check:array[1..500] of Boolean; {某车站是否已扩展完}
  Procedure Init;
  var f1:text;
    a,b,c,d:byte;
    data:array[1..100] of 0..100;
  begin
   assign(f1,'input.txt');
   reset(f1);
   readln(f1,m);
   readln(f1,n);
   result[501]:=100;
   for a:=1 to m do
   begin
    for b:=1 to 100 do data:=0;
    b:=0;
    repeat
     inc(b);
     read(f1,data);
    until eoln(f1);
    for c:=1 to b-1 do
     for d:=c+1 to b do
     begin
      inc(sum[data[c]]);
      num[data[c],sum[data[c]]]:=data[d];
     end;
   end;
  end;
  Procedure Done;
  var min,a,b,c,total:integer;
  begin
   fillchar(result,sizeof(result),-1);
   result[1]:=0;
   for c:=1 to sum[1] do result[num[1,c]]:=0;
   b:=data[1,1];
   repeat
    for c:=1 to sum do
     if (result[num[b,c]]=-1) then result[num[b,c]]:=result+1;
    min:=501;
    for c:=1 to n do if (result[c]<>-1) and (result[c]<result[min])
         then min:=c;
    b:=min;
   until result[n]<>-1;
   writeln(result[n]);{到达S公园的最少换车次数}
  end;
  begin
   Init;
  end.

    最佳游览路线问题

  输入数据:输入文件是INPUT.TXT。文件的第一行是两个整数M和N,之间用一个空格符隔开,M表示有多少条旅游街(1≤M≤100),N表示有多少条林荫道(1≤N≤20000)。接下里的M行依次给出了由北向南每条旅游街的分值信息。每行有N-1个整数,依次表示了自西向东旅游街每一小段的分值。同一行相邻两个数之间用一个空格隔开。

  输出文件:输出文件是 OUTPUT.TXT。文件只有一行,是一个整数,表示你的程序找到的最佳浏览路线的总分值。

  Program Tour;
  var m,n:integer; {M为旅游街数,N为林荫道数}
    data:array[1..20000] of -100..100;{data是由相邻两条林荫道所分}
  procedure Init; {隔的旅游街的最大分值}
  var a,b,c:integer;
    f1:text;
  begin
   assign(f1,'input.txt');
   reset(f1);
   read(f1,m,n);
   for a:=1 to n-1 do read(f1,data[a]); {读取每一段旅游街的分值}
   for a:=2 to m do
    for b:=1 to n-1 do
    begin
     read(f1,c);
     if c>data then data:=c; {读取每一段旅游街的分值,并选择}
    end; {到目前位置所在列的最大分值记入数组data}
   close(f1);
  end;
  procedure Done;
  var a,sum,result,c:integer;
    f2:text;
  begin
   result:=0;
   sum:=0;
   a:=0;
   while (a<n) do
   begin
    inc(a); {从数组的第一个数据开始累加,将累加所}
    sum:=sum+data[a]; {得到的最大分值记入result}
    if sum>result then result:=sum;
    if sum<0 then sum:=0; {若当前累加值为负数,则从当前状态起从新}
   end; {累加}
   assign(f2,'output.txt');
   rewrite(f2);
   writeln(f2,result);
   close(f2);
  end;
  begin
   Init;
   Done;
  end.

 

>> 递归算法
    为了描述问题的某一状态,必须用到它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自已来定义自己的方法,称为递归定义。例如:定义函数为:
        F(n):=n*F(n-1) (n>0);
        F(n):=C (n=0);
    则当0时,须用f(n-1)来定义F(n),用F(n-1-1)来定义F(n-1)……当n=0时,F(n)=1。
    由上例我们可看出,递归定义有两个要素:
    (1)递归边界条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义。
         如上例,当n=0时,F(n)=1,不使用F(n-1)来定义。
    (2)递归定义:使问题向边界条件转化的规则。递归定义必须能使问题越来越简单。
         如上例:F(n)由F(n-1)定义,越来越靠近F(0),也即边界条件。最简单的情况是F(0)=1。

      递归算法的效率往往很低, 费时和费内存空间. 但是递归也有其长处, 它能使一个蕴含递归关系且结构复杂的程序简介精炼, 增加可读性. 特别是在难于找到从边界到解的全过程的情况下, 如果把问题推进一步没其结果仍维持原问题的关系, 则采用递归算法编程比较合适。

    递归按其调用方式分为: 1. 直接递归, 递归过程P直接自己调用自己;

                          2. 间接递归, 即P包含另一过程D, 而D又调用P。
     递归算法适用的一般场合为:
    1. 数据的定义形式按递归定义.
       如裴波那契数列的定义: f(n)=f(n-1)+f(n-2); f(0)=1; f(1)=2.
       对应的递归程序为:
         Function fib(n : integer) : integer;
         Begin
            if n = 0 then fib := 1     { 递归边界 }
                     else if n = 1 then fib := 2
                                   else fib := fib(n-2) + fib(n-1)   { 递归 }
         End;
       这类递归问题可转化为递推算法, 递归边界作为递推的边界条件。
    2. 数据之间的关系(即数据结构)按递归定义. 如树的遍历, 图的搜索等。
    3. 问题解法按递归算法实现. 例如回溯法等。

    递归的应用

1.经典递归
  例如汉诺塔问题:经典的递归,原问题包含子问题。有些问题或者数据结构本来就是递归描述的,用递归做很自然。

2.递归与递推
  利用递归的思想建立递推关系,如由兔子生崽而来的fibonacci数列。但递推由于没有返回段,因此更为简单,有时可以直接用循环实现。

3.分治
  不少分治方法是源于递归思想,或是递归分解+合并处理。

4.回溯
  规模较小的问题用回溯解决比较自然。注意递归前后要保证现场保存和恢复,即正确的转化问题。

5.动态规划
  动态规划的子问题重叠性质与递归有某种相似之处。递归+动态修改查表是一种不错的建立动态规划模型的方法。

6.其他
  其他么,就是不好归类。例如表达式处理,排列组合等。附带说一下,用递归来处理打印方案的问题还是很方便的。


>> 分治算法
    有许多算法在结构上是递归的,为了解决某一特定问题,算法要多次地调用自己解决相关的子问题。这些算法通常采用分治策略:将原问题分解成规模小而结构与原来相似的子问题。递归地解决这些小问题,然后把得到的结果合并就得到原问题的解。如N=2是分治有叫二分治。

   

    分治法的适用条件
   分治法所能解决的问题一般具有以下几个特征:

      1.该问题的规模缩小到一定的程度就可以容易地解决;
      2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
      3.利用该问题分解出的子问题的解可以合并为该问题的解;
      4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

    上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题
,此时虽然可用分治法,但一般用动态规划法较好。

     

    分治法的基本步骤
    分治法在每一层递归上都有三个步骤:

      1.分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
      2.解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
      3.合并:将各个子问题的解合并为原问题的解。

    它的一般的算法设计模式如下:

           Divide-and-Conquer(P)
           1. if |P|≤n0
           2. then return(ADHOC(P))
           3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk
           4. for i←1 to k
           5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi
           6. T ← MERGE(y1,y2,...,yk) △ 合并子问题
           7. return(T)

    其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时,直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,...,Pk的相应的解y1,y2,...,yk合并为P的解。

    根据分治法的分割原则,原问题应该分为多少个子问题才较适宜?各个子问题的规模应该怎样才为适当?这些问题很难予以肯定的回答。但人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。换句话说,将一个问题分成大小相等的k个子问题的处理方法是行之有效的。许多问题可以取k=2。这种使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。

    分治法的合并步骤是算法的关键所在。有些问题的合并方法比较明显,有些问题合并方法比较复杂,或者是有多种合并方案,或者是合并方案不明显,如例5。究竟应该怎样合并,没有统一的模式,需要具体问题具体分析。


     分治法的复杂性分析
    从分治法的一般设计模式可以看出,用它设计出的程序一般是一个递归过程。因此,分治法的计算效率通常可以用递归方程来进行分析。为方便起见,设分解阈值n0=1,且算法ADHOC解规模为1的问题耗费1个单位时间。又设分治法将规模为n的问题分成k个规模为n/m的子问题去解,而且,将原问题分解为k个子问题以及用算法MERGE将k个子问题的解合并为原问题的解需用f(n)个单位时间。如果用T(n)表示该分治法Divide-and-Conquer(P)解规模为|P|=n的问题P所需的计算时间,则有:
     (1) T(1) = 1
           T(n) = kT(n/m) + f(n)

    用算法的复杂性中递归方程解的渐进阶的解法介绍的解递归方程的迭代法,可以求得(1)的解:

     logm(n-1)
     (2)T(n) = n^(logmK) + ∑ (k^j)*f(n/(m^j))
          j=0

    注意,递归方程(1)及其解(2)只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由等于m的方幂时T(n)的值可以估计T(n)的增长速度。通常,
我们可以假定T(n)是单调上升的,从而当mi≤n<mi+1时,T(mi)≤T(n)<T(mi+1)。


    另一个需要注意的问题是,在分析分治法的计算效率时,通常得到的是递归不等式:

     (3) T(n0) ≤ c
           T(n) ≤ kT(n/m) + f(n) n>n0

    由于我们关心的一般是最坏情况下的计算时间复杂度的上界,所以用等于号(=)还是小于或等于号(≤)是没有本质区别的分治法的几种变形。

     

     几种分治的方法

    1.二分法 dichotomy一种每次将原问题分解为两个子问题的分治法,是一分为二的哲学思想的应用。这种方法很常用,由此法产生了许多经典的算法和数据结构。

    2.分解并在解决之前合并法 divide and marriage before conquest一种分治法的变形,其特点是将分解出的子问题在解决之前合并。

    3.管道传输分治法 pipelined divide and conquer一种分治法的变形,它利用某种称为“管道”的数据结构在递归调用结束前将其中的某些结果返回。此方法经常用来减少算法的深度。

 

>> 枚举算法   

     所谓枚举就是从所有可能的解的集合一一枚举各元素,有程序给定的检验条件,判断哪些有用,哪些无用。能使命题成立的,即位其解。

     从枚举的定义可以看出,枚举算法本质上属于搜索算法,但它还必须满足两个条件:

   1.可预先确定解的个数N;

   2.解变量A1,A2,A3……An的可能变化范围预先确定

         A1∈{X11…………X1p};

         Ai∈{Xi1…………Xiq};

         An∈{Xn1…………Xnk};

     一般说来,如果预先对问题确定了解的个数,以及各个解的值域,我们则可以用for语句和条件判断语句逐步求解或证明。


[此贴子已经被作者于2004-9-12 10:19:57编辑过]

您需要登录后才可以回帖 登录 | 加入易码

本版积分规则

Archiver|手机版|小黑屋|EMAX Studio

GMT+8, 2024-4-25 16:28 , Processed in 0.010803 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表