zoukankan      html  css  js  c++  java
  • 分治算法(一)

    当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。

    1、引例:

        如果给你一个装有16枚硬币的袋子,其中有一枚是伪造的,并且那枚伪造硬币的重量和真硬币的重量不同。你能不能用最少的比较次数找出这个伪造的硬币?为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。

        常规的解决方法是先将这些硬币分成两枚一组,每一次只称一组硬币,如果运气好的话只要称1次就可以找到,最坏最多称8次才可以找出那枚硬币,这种直接寻找的方法存在着相当大的投机性,适用于硬币数量少的情况,在硬币数量多的情况下就成为一件费时费力又需要运气的事。

        试着改变一下方法:如果我们将全部硬币分成两组,将原来设计的一次比较两枚硬币变为一次比较两组硬币,我们会发现通过一次比较后.完全可以舍弃全部是真币的一组硬币,选取与原有问题一致的另一半进行下一步的比较,这样问题的规模就明显缩小,而且每一次比较的规模都是成倍减少(如图4-1所示)。

     

        根据以上分析。我们可以得到以下的结论:

        (I)参与比较的硬币数量越多,使用该方法来实现就越快.而且投机性大大减少;

        (2)解决方法关键在于能将大问题分割成若干小问题;

        (3)小问题与原有问题是完全类似的。

    通常我们将这种大化小的设计策略称之为分治法.即“分而治之”的意思。

    2、分治法的基本思想和解题的一般步骤:

    分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。侧重点在于能各个击破。分治法在设计检索、分类算法或某些速算算法中很有效。最常用的分治法是二分法、归并法、快速分类法等。

    分治法解题的一般步骤:

    (1)分解,将要解决的问题划分成若干规模较小的同类问题;

    (2)求解,当子问题划分得足够小时,用较简单的方法解决;

    (3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。

    1、解决算法实现的同时,需要估算算法实现所需时间。分治算法时间是这样确定的:     解决子问题所需的工作总量(由 子问题的个数、解决每个子问题的工作量 决定);合并所有子问题所需的工作量

    2、分治法是把任意大小问题尽可能地等分成两个子问题的递归算法

    3、分治的具体过程大致如下: 

       begin  {分治过程开始}
        if ①问题不可分 then ②返回问题解  
         else begin
              ③从原问题中划出含一半运算对象的子问题1;
              ④递归调用分治法过程,求出解1;
              ⑤从原问题中划出含另一半运算对象的子问题2;
              ⑥递归调用分治法过程,求出解2; 
              ⑦将解1、解2组合成整修问题的解;  
            end;
       end; {结束}

    3、典型例题:

    1】用递归算法和非递归算法实现二分查找即:有 n个已经从小到大排序好的数据,从键盘输入一个数m,用二分查找方法,判断它是否在这个数中。

     1 var a:array[1..20]of integer;
     2    n,i,m,x,y:integer;
     3 procedure  jc(x,y:integer);                    //递归过程
     4   var  k:integer;
     5   begin
     6     k:=(x+y)div 2;                            //取中间位置点
     7     if a[k]=m then writeln('the num in ',k);        //找到查找的数,输出结果
     8     if x>y then writeln('no find')                //找不到该数
     9      else begin
    10            if a[k]<m then jc(k+1,y);            //在后半中查找
    11            if a[k]>m then jc(x,k-1);            //在前半中查找
    12          end;
    13   end;
    14 begin
    15   readln(n);
    16   x:=1;y:=n;
    17   for i:=1 to n do readln(a[i]);            //输入排序好的数
    18   readln(m);                            //输入要查找的数
    19   jc(x,y);                                //递归查找
    20 end.
    递归法
     1 var a:array[1..20]of integer;
     2    n,i,m,x,y,k:integer;
     3 begin
     4   readln(n);
     5   x:=1;y:=n;
     6   for i:=1 to n do readln(a[i]);
     7   readln(m);
     8   repeat
     9     k:=(x+y)div 2;
    10     if a[k]=m then begin writeln('the num in ',k);halt;end
    11     else begin
    12            if a[k]<m then x:=k+1;
    13            if a[k]>m then y:=k-1;
    14          end;
    15   until x>y;
    16   writeln('No find');
    17 end.
    非递归法

    2一元三次方程求解 

    有形如:ax3 +bx2 +cx+d0这样的一个一元三次方程。给出该方程中各项的系数(abc均为实数),并约定该方程存在三个不同实根(根的范围在-100 至 100 之间),且根与根之差的绝对值≥1。 要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 位。 

    提示:记方程 fx=0,若存在 个数 x1 和 x2,且 x1<x2fx1*fx2)<0

            则在(x1x2)之间一定有一个根。

    输入: 

    abc

    输出: 

    三个实根(根与根之间留有空格) 

    输入输出样例 

    输入:  1 -5 -4 20 

    输出:  -2.00 2.00 5.00 

    【例3】循环比赛

    【问题描述】 设有N个选手的循环比赛。其中N=2M,要求每名选手要与其他N-1名选手都赛一次。每名选手每天比赛一次,循环赛共进行N-1天.要求每天没有选手轮空。

    输入:M

    输出:表格形式的比赛安排表

    【样例输入】

      3

    【样例输出】

      1 2 3 4 5 6 7 8

      2 1 4 3 6 5 8 7

      3 4 1 2 7 8 5 6

      4 3 2 1 8 7 6 5

      5 6 7 8 1 2 3 4

      6 5 8 7 1 1 4 3

      7 8 5 6 3 1 1 2

      8 7 6 5 d 3 2 1

    【问题分析】:此题很难直接给出结果,我们先将问题进行分解,n=2^m,将规模减半,如果m=3(即n=8),8名选手的比赛,减半后变成4名选手的比赛(n=4),4个选手的比赛的安排方式还不是很明显,再减半到两名选手队的比赛(n=2),两名选手的比赛安排方式很简单,只要让两名选手直接进行一场比赛即可:

    1

    2

    2

    1

    分析两个球队的比赛的情况不难发现,这是一个对称的方阵,我们把这个方阵分成4部分(即左上,右上,左下,右下),右上部分可由左上部分加1(即加n/2)得到,而右上与左下部分、左上与右下部分别相等。因此我们也可以把这个方阵看作是由n=1的方阵所成生的,同理可得n=4的方阵:

    1

    2

    3

    4

    2

    1

    4

    3

    3

    4

    1

    2

    4

    3

    2

    1

    同理可由n=4方阵生成n=8的方阵:

    1

    2

    3

    4

    5

    6

    7

    8

    2

    1

    4

    3

    6

    5

    8

    7

    3

    4

    1

    2

    7

    8

    5

    6

    4

    3

    2

    1

    8

    7

    6

    5

    5

    6

    7

    8

    1

    2

    3

    4

    6

    5

    8

    7

    2

    1

    4

    3

    7

    8

    5

    6

    3

    4

    1

    2

    8

    7

    6

    5

    4

    3

    2

    1

    这样就构成了整个比赛的安排表。

     

    在设计程序时,我们采用由小到大的方法进行扩展,而数组下标的处理是解决该问题的关键。用数组a记录2^m名选手的循环比赛表,整个循环比赛表从最初的1*1方阵按上述规则生成2*2的方阵,再生成4*4的方阵,……直到生成出整个循环比赛表为止。变量h表示当前方阵的大小,也就是要生成的下一个方阵的一半。

    【参考程序】

     1 program p4_1;
     2 var 
     3   i,j,h,m,n:integer;
     4   a:array[1..100,1..100]of integer;
     5 begin
     6   assign(input,'word.in');  reset(input);
     7   assign(output,'word.out'); rewrite(output);
     8   readln(m);
     9   n:=1;a[1,1]:=1;h:=1;
    10   for i:=1 to m do n:=n*2;
    11   repeat
    12     for i:=1 to h do
    13       for j:=1 to h do begin
    14         a[i,j+h]:=a[i,j]+h;{构造右上角方阵}
    15         a[i+h,j]:=a[i,j+h];{构造左下角方阵}
    16         a[i+h,j+h]:=a[i,j];{构造右下角方阵}
    17       end;
    18     h:=h*2;
    19   until h=n;
    20   for i:=1 to n do
    21   begin
    22     for j:=1 to n do write(a[i,j]:4); writeln;
    23   end;
    24   close(input);  close(output);
    25 end.

    [例4] 求方程的根

    【问题描述】

        输入mnpab,求方程f(x)=mx+nx-px0[a,b]内的根。mnpab均为整数,且a<bmnp都大于等于1。如果有根,则输出,精确到1E-11;如果无方程根,则输出“NO”。

    【样例】

    equation.in

    2 3 4 1 2

    equation.out

    1.5071265916E+00

    2.9103830457E-11

    【算法分析】

    首先这是一个单调递增函数,对于一个单调递增(或递减)函数,如图4-7所示,判断在[a, b]范围内是否有解,解是多少。方法有多种,常用的一种方法叫“迭代法”,也就是“二分法”。先判断f(a)·f(b)≤0,如果满足则说明在[a, b]范围内有解,否则无解。如果有解再判断x(a+b)/2是不是解,如果是则输出解结束程序,否则我们采用二分法,将范围缩小到[a, x)或(x, b],究竟在哪一半区间里有解,则要看是f(a)·f(x)<0还是f(x)·f(b)<0

    当然对于yx,我们需要用换底公式把它换成exp(x*ln(y))。

    4、小结:

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

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

    第4条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。

  • 相关阅读:
    SCHTASKS /CREATE
    手机酷派4G5316 5313s 黑砖 求转成功 9008端口 9006端口 少走弯路选对镜像
    网络共享 相关知识与原理 操作步骤
    电脑 主板 硬盘的 电脑系统
    按键精灵 按键代码
    win7 快捷键 收集
    默认主页更改 主页锁定 打开浏览器时的网页设置
    按键精灵 以时间命名文件夹 创建文件 写入文件 和截图
    按键精灵-----按钮控制(开始子程序)的时候是要用到多线程的
    java web 大总结
  • 原文地址:https://www.cnblogs.com/vacation/p/5182363.html
Copyright © 2011-2022 走看看