算法设计策略-分治法

参考书籍:算法设计与分析——C++语言描述(第二版)

算法设计策略-分治法

分治法

分治法的基本思想

分治法就是分而治之一个问题能够用分治法求解的要素是第一问题能够按照某种方法分解成若干个规模较小,相互独立且与原问题类型相同的子问题第二子问题足够小时可以直接求解第三能够将子问题的解组合成原问题的解

由于分治法要求分解成同类子问题,并允许不断分解,使问题的规模逐步减小,最终可用已知的方法求解足够小的问题,因此分治法求解很自然导致一个递归算法。

分治法求解框架:

SolutionType DandC(ProblemType P)
{
  ProblemType P1,P2,...,Pk;
  if(Small(P))  //子问题P足够小,用S(P)直接求解
    return S(P);
  else{
    Divide(P,P1,P2,...,Pk); //将问题P分解成子问题P1,P2,...,Pk
    return Combine(DandC(P1),DandC(P2),...,DandC(Pk));  //求解子问题,合并求解
  }
}

其中Small(P)是一个布尔值函数,它判定问题P是否足够小。Divide函数以某种方式,将问题P分解成规模较小,相互独立的若干同类型子问题。Combine函数将各子问题的解组合成原始问题的解。

特殊地,一分为二的分治法:

SolutionType DandC(int left, int right)
{
  if(Small(left, right))
    return S(left,right);
  else{
    int m = Divide(left, right);    //以m为界将问题分解成两个子问题
    return Combine(DandC(left, m),DandC(m+1, right));   //分别求解子问题,合并求解
  }
}

如果较大的问题可以分为几个同样大小的问题,那么往往可以得到以下的递推公式:
\[ T(n)=aT(n/b)+cn^k,\ T(1)=c \]
定理:设\(a\)\(b\)\(c\)\(k\)为常数,\(T(n)=aT(n/b)+cn^k,\ T(1)=c\),则
\[ T(n)=\left\{\begin{aligned} &\Theta (n^{\log_ba}) &如果&a>b^k\\ &\Theta(n^k\log n) &如果&a=b^k\\ &\Theta (n^k) &如果&a<n^k \end{aligned}\right. \]

分治法求最大元最小元

问题描述

在一个元素集合中寻找最大元素和最小元素的问题,即在互不相同的\(n\)个数\({x_1,x_2,\cdots,x_n}\)中,找出最大和最小的数。

分治法求解

用分治法求最大最小元。可以将原问题分解成大小基本相等的两个子问题。显然在一个或两个元素的表中求最大、最小元是容易的,可以直接求得;如果已经求得了由分解所得的两个字表中的最大、最小元,则原表的最大元是两个字表中的最大元之较大者,原表的最小元是两子表中的最小元之较小者。

template <class T>
void SortableList<T>::MaxMin(int i, int j, T& max, T& min)const
//前置条件:i和j,0<=i<=j<表长,是表的下标范围的界
{
    T min1, max1;
    if(i == j){
        max=min=l[i];   //表中只有一个元素
    }   else{           //表中有两个元素
        if(i == j-1){
            if(l[i] < l[j]){
                max = l[j];
                min = l[i];
            }   else{
                max = l[i];
                min = l[j];
                }
        }   else{       //表中多于两个元素时
            int m = (i+j)/2;    //对半分割
            MaxMin(i,m,max,min);    //求前一半部分子表中的最大最小元素
            MaxMin(m+1,j,max1,min1);    //求后一半部分子表中的最大最小元素
            if(max < max1){         //两子表最大元的大者为原表最大元
                max = max1;
            }
            if(min > min1){         //两子表最小元的小者为原表最小元
                min = min1;
            }
        }
    }
  }

使用归纳法可以证明算法的正确性。

用C语言实验如下:

#include <stdio.h>

void MaxMin(int l[], int i, int j, int *max, int *min)
//前置条件:i和j,0<=i<=j<表长,是表的下标范围的界
{
  int min1=l[i], max1=l[i];
  if(i == j){
    *max=*min=l[i]; //表中只有一个元素
  } else{         //表中有两个元素
      if(i == j-1){
        if(l[i] < l[j]){
          *max = l[j];
          *min = l[i];
        } else{
            *max = l[i];
            *min = l[j];
        }
      } else{       //表中多于两个元素时
          int m = (i+j)/2;  //对半分割
          MaxMin(l,i,m,max,min);  //求前一半部分子表中的最大最小元素
          MaxMin(l,m+1,j,&max1,&min1);  //求后一半部分子表中的最大最小元素
          if(*max < max1){           //两子表最大元的大者为原表最大元
            *max = max1;
          }
          if(*min > min1){          //两子表最小元的小者为原表最小元
            *min = min1;
          }
      }
  }
  printf("i= %d; j= %d; Max = %d; Min = %d\n", i, j, *max, *min);
}

int main()
{
    int P[10] = {1, 3, 5, 2, 3 ,6 ,7, 1, 10, -11};
    int max = 0, min = 0;
    printf("array P:");
    for(int i = 0; i<10; i++)
        printf(" %d", P[i]);
    printf("\n");
    MaxMin(P, 0, 9, &max, &min);

    return 0;
}

实验结果:

array P: 1 3 5 2 3 6 7 1 10 -11
i= 0; j= 1; Max = 3; Min = 1
i= 2; j= 2; Max = 5; Min = 5
i= 0; j= 2; Max = 5; Min = 1
i= 3; j= 4; Max = 3; Min = 2
i= 0; j= 4; Max = 5; Min = 1
i= 5; j= 6; Max = 7; Min = 6
i= 7; j= 7; Max = 1; Min = 1
i= 5; j= 7; Max = 7; Min = 1
i= 8; j= 9; Max = 10; Min = -11
i= 5; j= 9; Max = 10; Min = -11
i= 0; j= 9; Max = 10; Min = -11

Press any key to continue.

时间分析

分治法求最大元最小元程序在最好、平均和最坏情况下的比较次数都为\(3n/2-2\)

小结

分治法的思想

对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

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

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

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

  • 该问题的规模缩小到一定的程度就可以容易地解决;
    • 因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征
  • 该问题可以分解为若干个规模较小的相同问题;
    • 这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用
  • 利用该问题分解出的子问题的解可以合并为该问题的解;
    • 能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划算法
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
    • 这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好

猜你喜欢

转载自www.cnblogs.com/born2run/p/9093492.html