算法设计与分析(二)分治

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SakuraMashiro/article/details/78454262

一、分治法基本思想
1.求解问题算法的复杂性一般都与问题规模相关,问题规模越小越容易处理。
2.分治法的基本思想是,将一个难以直接解决的大问题,分解为规模较小的相同子问题,直至这些子问题容易直接求解,并且可以利用这些子问题的解求出原问题的解。各个击破,分而治之。
3.分治法产生的子问题一般是原问题的较小模式,这就为使用递归技术提供了方便。递归是分治法中最常用的技术。

这里写图片描述

二、分治法解决问题的先决条件
1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。 这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。

三、分治法解决问题的基本步骤
一般来说,分治法的求解过程由以下三个阶段组成:
1.划分:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
2.求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
3.合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。

divide-and-conquer(P){
if ( | P | <= n0) adhoc(P); //解决小规模的问题
divide P into smaller subinstances P1,P2,…,Pk;//分解问题
for (i=1; i<=k; i++)
yi=divide-and-conquer(Pi); //递归的解各子问题
return merge(y1,…,yk); //将各子问题的解合并为原问题的解
}

四、分治法复杂性分析

1.分治法的复杂性分析依据—递推方程
2.两类递推方程:
这里写图片描述

3.求解方法:
迭代法,递归树,Master定理

典型的递推方程
这里写图片描述
迭代法可求得
这里写图片描述

四、分治法求解问题典型案例

排列问题
问题:R是由n个元素构成的序列集合,R={r1, r2, … ,rn},求R的全排列perm(R)。

理解问题
(1) 若R中只有1个元素{r},则perm(R)=(r)
(2) 若R中只有2个元素{r1, r2},则
perm(R)=(r1)perm(R1)∪(r2)perm(R2)
其中,Ri=R-{ri}
(3) 若R中有3个元素{ r1, r2, r3},则
perm(R)=(r1)perm(R1)∪(r2)perm(R2)∪(r3)perm(R3)

思想:分治法
依次将待排列的数组的后n-1个元素与第一个元素交换,则每次递归处理的都是后n-1个元素的全排列。当数组元素仅有一个时为此递归算法的出口。

算法设计 伪代码
算法 perm(Type list[], int k, int m)
//生成列表list的全排列
//输入:一个全排列元素列表list[0..n-1]
//输出:list的全排列集合

流程图/伪代码

if k == m
  for i←0 to m do
    输出list[i]
else
  for i←k to m do
    swap list[k] and list[i]
    perm(list, k+1, m)
    swap list[k] and list[i]

效率分析
T(n) = O(1) n=1
T(n) = nT(n-1)+O(1) n > 1

二分查找
问题:给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。

算法设计 伪代码

template<class Type> 
int BinarySearch(Type a[], const Type& x, int n){
    int left = 0; int right = n-1
    while (left <= right){ 
        int middle = (left + right)/2;
        if (x == a[middle]) return middle;
        if (x > a[middle]) left = middle + 1; 
        else right = middle - 1;
    }
    return -1;  //x未找到
} 

算法实现

/*
     * Find the first occurrence of x in sorted array a[first..max].
     * @param x value to find
     * @param a array sorted in increasing order 
     *          (a[0] <= a[1] <= ... <= a[n-1])
     * @param first low end of range. 
     *              Requires 0 <= first <= a.length-1.
     * @param max high end of range.  
     *              Requires 0 <= max <= a.length-1.
     * @return lowest i such that first<=i<=max and a[i]==x, 
     *         or -1 if there's no such i. 
     */  
    public static int binarySearch(int[] a, int x, int left, int right){
        int mid = (left+right)/2;

        if( left >= right ){
            return -1 ;
        }
        if( x < a[mid]){
            return binarySearch(a,x,left,mid-1);
        }
        if( x > a[mid]){
            return binarySearch(a,x,mid+1,right);
        }
        if( x == a[mid]){
            if( x == a[mid-1] && mid > 0 ){
                return binarySearch( a , x , left , mid-1);
            }else{
                return mid ;
            }
        }
        return -1 ;

   }

效率 T(n) = T(n/2)+O(1) = O(logn)

归并排序
问题:给定一个可排序的n个元素序列(数字、字符或字符串),对它们按照非降序方式重新排列。

基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。

这里写图片描述

算法设计:伪代码
这里写图片描述

这里写图片描述

小规模案例
这里写图片描述

算法实现

private static void mergeSort(int[] a, int left, int right){
        int[] b = new int[a.length];
        mSort(a, b, left, right);
    }

    private static void mSort(int[] a, int[] b, int left, int right){
        if(left < right){
            int i = (left + right)/2;
            mSort(a, b, left, i);
            mSort(a, b, i+1, right);
            merge(a, b, left, i, right);//合并到数组b
            copy(a, b, left, right);//复制回数组a
        }
    }

    private static void merge(int[] a, int[] b, int l, int m, int r){//合并a[l:m]和a[m+1:r]到b[l:r]
        int i = l , j = m + 1 , k = l ;
        while( i <=m && j <= r){
            if( a[i] <= a[j] ) b[k++] = a[i++];
            else b[k++] = a[j++];
        }
        if( i > m ){
            for( int q = j ; q <= r ; q++ ){
                b[k++]=a[q];
            }
        }else{
            for( int q = i ; q <= m ; q++ ){
                b[k++]=a[q];
            }
        }


    }

    private static void copy(int[] a, int[] b, int left, int right){//拷贝数组b中元素到数组a
        for(int i = left; i <= right; i++)
            a[i] = b[i];
    }

快速排序
问题:给定一个可排序的n个元素序列(数字、字符或字符串),对它们按照非降序方式重新排列。

基本思想:对待排序数组不断拆分,拆分的同时不断交换元素,归位分裂点元素。记录的比较和交换是从两端向中间进行,记录每次移动的距离较大,因而总的比较和移动次数较少。

这里写图片描述

两次扫描法确定分区的算法

   Partition(A[L ... R])
   {    p←A[L]           //选择中轴
       i←L+1,  j←R   //左右扫描位置指针。左扫描没找到时,i 停在L+1位置
    while (true) 
    {     while (A[ i ] < p) and ( i ≤ R )  do  i←i+1   //左扫描(带限位)
          while (A[ j ] > p) and ( j ≥ L  )  do  j←j-1    //右扫描(带限位)
         if ( i≥j ) then  break    //左右扫描已交叉,退出循环
         swap(A[ i ], A[ j ])   //左右扫描未交叉
        }
    swap(A[ L], A[ j ])
    return ( j )    //返回分裂点 j
   }

最坏时间复杂度:O(n2)
平均时间复杂度:O(n)

总结:进一步提高分治法效率的基本措施
1.代数变换,减少子问题个数
2.利用中间结果
3.数据预处理

猜你喜欢

转载自blog.csdn.net/SakuraMashiro/article/details/78454262