zoukankan      html  css  js  c++  java
  • 0x05算法设计与分析复习(二):算法设计策略-分治法1

    参考书籍:算法设计与分析——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)+cnk, T(1)=c

    定理:设abck为常数,T(n)=aT(n/b)+cnk, T(1)=c,则
    T(n)={Θ(nlogba)a>bkΘ(nklogn)a=bkΘ(nk)a<nk

    分治法求最大元最小元

    问题描述

    在一个元素集合中寻找最大元素和最小元素的问题,即在互不相同的n个数x1,x2,,xn中,找出最大和最小的数。

    分治法求解

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

    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>
    //
    //int gcd(int m, int n)
    //{
    //  //欧几里得辗转相除法
    //  //假定m<n
    //  if(m == 0) {
    //      return n;
    //  } else {
    //      int tmp;
    //      while(m>0) {
    //          tmp = n%m;
    //          n = m;
    //          m = tmp;
    //      }
    //      return n;
    //  }
    //}
    //
    //int Rgcd(int m, int n)
    //{
    //  //递归形式
    //  if(m == 0) {
    //      return n;
    //  } else {
    //      return Rgcd(n%m, m);
    //  }
    //}
    //
    //int main()
    //{
    //  int m, n, r1, r2;
    //  scanf("%d%d", &m, &n);
    //  
    //  if(n>m) {
    //      r1 = gcd(m,n);
    //      r2 = Rgcd(m,n);
    //  } else {
    //      r1 = gcd(n,m);
    //      r2 = Rgcd(n,m);
    //  }
    //  
    //  printf("GCD of m and n: %d
    ", r1);
    //  printf("GCD of m and n: %d
    ", r2);
    //  
    //  
    //  return 0;
    //}
    
    #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
    ", 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("
    ");
        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/22

    小结

    分治法的思想

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

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

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

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

    • 该问题的规模缩小到一定的程度就可以容易地解决;
      • 因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征
    • 该问题可以分解为若干个规模较小的相同问题;
      • 这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用
    • 利用该问题分解出的子问题的解可以合并为该问题的解;
      • 能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划算法
    • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
      • 这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好
  • 相关阅读:
    Python-http请求
    MacOs Big Sur 11.0.1 安装python报错
    linux 根据时间删除某个目录下的文件
    记一次文件上传遇到的坑(文件名|文件格式乱码)
    json_schema参数校验
    K8s
    python实时视频流播放
    pycharm永久激活
    客户端ajax请求为实现Token验证添加headers后导致正常请求变为options跨域请求解决方法
    webstorm修改文件,webpack-dev-server及roadhog不会自动编译刷新
  • 原文地址:https://www.cnblogs.com/born2run/p/9581382.html
Copyright © 2011-2022 走看看