趣学算法系列-分治法

来源:互联网 发布:mac地址里ig位 编辑:程序博客网 时间:2024/05/16 08:11

趣学算法系列-分治法

声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门,
原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多实例分析请查看原书内容

第三章 分治法

  • 分治,顾名思义,分而治之。

    大问题分解成一系列规模较小的相同问题,然后逐个解决小问题,即所谓的分而治之。分治法产生的子问题与原始问题相同,只是规模减小。

  • 分治法求解的三个条件
    (1)原问题可分解为若干个规模较小的相同子问题。 分治
    (2)子问题相互独立。单个问题的解不会影响到下个问题。
    (3)子问题的解可以合并为原问题的解。 归并
  • 分治算法秘籍
    (1)分解:将要解决的问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题,确保各个子问题的解具有相同的子结构
    (2)治理:求解各个子问题。由于各个子问题与原问题形式相同,只是规模较小而,
    而当子问题划分得足够小时,就可以用较简单的方法解决。 问题可以解决,直接解决,否则继续用相同的规则接着分解。
    (3)合并:按原问题的要求,将子问题的解逐层合并构成原问题的解。
    分治法的实现模式可以是递归方式,也可以是非递归方式,
  • 应用分治法的目的
    (1)通过分解问题,使无法着手解决的大问题变成容易解决的小问题
    (2)通过减小问题的规模,降低解决问题的复杂度(或计算量)

  • 分治法的难点

    分治法的难点是如何将子问题分解,并且将子问题的解合并原问题的解。
    针对不同的问题,通常有不同的分解与合并方式。
    快速排序的分解思路选择一个标兵数,小于的放到左边 大于的放到右边
    最明显的算法思路体现(Python版本)

    def quicksort(array):if len(array) < 2:return array     #基线条件:为空或只包含一个元素的数组是“有序”的else:pivot = array[0]   #递归条件less = [i for i in array[1:] if i <= pivot]   #由所有小于基准值的元素组成的子数组greater = [i for i in array[1:] if i > pivot] #由所有大于基准值的元素组成的子数组return quicksort(less) + [pivot] + quicksort(greater)print quicksort([10, 5, 2, 3])

    二分搜索的分解思路是对半缩小规模,处理左边部分,然后处理右边部分
    分治法最难也是最灵活的部分就是对问题的分解和结果的合并
    在数学上,只要是能用数学归纳法证明的问题,一般都可以应用分治法解决

  • 递归和分治联系
    问题的分解肯定不是一步到位的,反复调用,层层分解,自然导致了递归的调用。
    分治法得到的子问题和原问题是相同的,当然可以用相同的函数来解决
    区别只在于问题的规模和范围不同,可以通过参数来控制范围
    递归同时也可以该用循环的方式,特别是尾递归(此部分后续会有单独的分析)
  • 贪心算法的典型应用
            最优装载问题
            背包问题

  • 实际案例分析-最优装载问题

    • 问题情景

      一天晚上,我们在家里看电视,某大型娱乐节目在玩猜数游戏。主持人在女嘉宾的手心上写一个 10 以内的整数,让女嘉宾的老公猜是多少,而女嘉宾只能提示大了,还是小了,并且只有 3 次机会

    • 问题分析

      从问题描述来看,如果是 n 个数,那么最坏的情况要猜 n 次才能成功,其实我们没有必要一个一个地猜,因为这些数是有序的,它是一个二分搜索问题。我们可以使用折半查找的策略,每次和中间的元素比较,如果比中间元素小,则在前半部分查找(假定为升序),如果比中间元素大,则去后半部分查找。

    • 算法设计

      问题描述:给定 n 个元素,这些元素是有序的(假定为升序),从中查找特定元素 x。
      算法思想:将有序序列分成规模大致相等的两部分,然后取中间元素与特定查找元素 x
      进行比较,
      如果 x 等于中间元素,则查找成功,算法终止;
      如果 x 小于中间元素,则在序列的前半部分继续查找,即在序列的前半部分重复分解和治理操作;
      否则,在序列的后半部分继续查找,即在序列的后半部分重复分解和治理操作

      算法设计:用一维数组 S[]存储该有序序列,设变量 low 和 high 表示查找范围的下界和上界, middle 表示查找范围的中间位置, x 为特定的查找元素。

    • 完美图解
      用分治法在有序序列( 5, 8, 15, 17, 25, 30, 34, 39, 45, 52, 60)中查找元素 17。
      (1)数据结构。用一维数组 S[]存储该有序序列, x=17,如图 3-2 所示
      这里写图片描述
      (2)初始化。 low=0, high=10,计算 middle=(low+high)/2=5,如图 3-3 所示。
      这里写图片描述
      (3)将 x 与 S[middle]比较。这里写图片描述我们在序列的前半部分查找,搜索的范围缩小到子问题 S[0..middle−1],令 high=middle−1,如图 3-4 所示
      这里写图片描述
      (4)计算 middle=( low+high) /2=2,如图 3-5 所示。
      这里写图片描述
      (5)将 x 与 S[middle]比较。 x=17>S[middle]=15,我们在序列的后半围缩小到子问题S[middle+1..low],令 low=middle+1,如图 3-6 所示
      这里写图片描述
      (6)计算 middle=( low+high) /2=3,如图 3-7 所示
      这里写图片描述
      (7)将 x 与 S[middle]比较。 x=17=S[middle]=17,查找成功,算法结束。

  • 伪代码详解

int BinarySearch(int n,int s[],int x)    {    int low=0,high=n-1; //low 指向数组的第一个元素, high 指向数组的最后一个元素    while(low<=high) //设置判定条件    {    int middle=(low+high)/2;//计算 middle 值(查找范围的中间值)    if(x==s[middle]) //x 等于 s[middle],查找成功,算法结束    return middle;    else if(x<s[middle]) //x 小于 s[middle],则从前半部分查找    high=middle-1;    else //x 大于 s[middle],则从后半部分查找    low=middle+1;    }    return -1;}
  • 实战演练
    //program 3-1    #include <iostream>    #include <cstdlib>    #include <cstdio>    #include <algorithm>    using namespace std;    const int M=10000;    int x,n,i;    int s[M];    int BinarySearch(int n,int s[],int x)    {    int low=0,high=n-1; //low 指向数组的第一个元素, high 指向数组的最后一个元素    while(low<=high)    {    int middle=(low+high)/2; //middle 为查找范围的中间值    if(x==s[middle]) //x 等于查找范围的中间值,算法结束    return middle;    else if(x<s[middle]) //x 小于查找范围的中间元素,则从前半部分查找    high=middle-1;    else //x 大于查找范围的中间元素,则从后半部分查找    low=middle+1;    }    return -1;    }    int main()    {    cout<<"请输入数列中的元素个数 n 为: ";    while(cin>>n)    {    cout<<"请依次输入数列中的元素: ";    for(i=0;i<n;i++)    cin>>s[i];    sort(s,s+n);    cout<<"排序后的数组为: ";    for(i=0;i<n;i++)    {    cout<<s[i]<<" ";    }    cout<<endl;    cout<<"请输入要查找的元素: ";    cin>>x;    i=BinarySearch(n,s,x);    if(i==-1)    cout<<"该数列中没有要查找的元素"<<endl;    else    cout<<"要查找的元素在第"<<i+1<<"位"<<endl;    }    return 0;    }


  • 算法时间复杂度分析

(1)时间复杂度:首先需要进行排序,调用 sort 函数,进行排序复杂度为 O(nlogn),如
果数列本身有序,那么这部分不用考虑。二分查找排序O(logn)
(2)空间复杂度:程序中变量占用了一些辅助空间,这些辅助空间都是常数阶的,因此
空间复杂度为 O(1)