算法(二):分治法

分治法的基本思想

分治法的基本思想是将一个规模为n的问题分解为k个规模较小的的子问题,这些子问题相互独立且与原问题相同,递归解决这些子问题,然后将各个子问题的解合并得到原问题的解。

分治法的使用条件

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

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

分治法的基本步骤

分治法在每一层递归上都有三个步骤
  step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
  step3 合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下

Divide-and-Conquer(P)

  1. if |P|≤n0

  2. then return(ADHOC(P))

  3. 将P分解为较小的子问题 P1 ,P2 ,…,Pk

  4. for i←1 to k

  5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi

  6. T ← MERGE(y1,y2,…,yk) △ 合并子问题

  7. return(T)

其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时直接用算法ADHOC(P)求解。算法MERGE(y1,y2,…,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,…,Pk的相应的解y1,y2,…,yk合并为P的解。

分治法的复杂度分析

一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:

T(n)= k T(n/m)+f(n)

分治法的经典示例

在下面的示例中,主要介绍分治法在二分搜索、归并排序和快速排序的应用。这三个算法算是比较经典的了。。

示例一: 二分搜索技术
问题描述:给定已按升序排好序的n个元素的数组a[n],现要在这n个元素中找到一个特定元素x的索引。如果不存在x元素,返回-1。

public int binarySearch(int[] a, int x, int n) {
    int lo = 0;
    int hi = n - 1;
    while(lo <= hi) {
        int mid = (lo + hi) >> 1;
        if(a[mid] == x)return mid;
        else if(a[mid] < x) lo = mid + 1;
        else hi = mid -1;
    }
    return -1;
}

示例二:归并排序
问题描述:有一个无序的大小为n的数组a[n],使用归并排序将数组进行升序排序。

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

//归并排序
public void mergeSort(int[] a, int start, int end) {
        if(start >= end)
            return;
        int mid = (start + end) >> 1;
        //体现了分治的思想,分别将每一部分进行排序,然后合并得到最终的排序
        mergeSort(a, start, mid);
        mergeSort(a, mid+1, end);
        merge(a, start, mid, end);
    }

    //将两个子排序合并为一个排序
    public void merge(int[] a, int lo, int mid, int hi) {
        int i = lo;
        int j = mid + 1;
        int k = 0;
        int[] temp = new int[hi - lo + 1];
        while(i <= mid && j <= hi) {
            if(a[i] <= a[j]) {
               temp[k++] = a[i++];
            }else {
                temp[k++] = a[j++];
            }
        }

        if(i <= mid) {
            while(i <= mid) {
                temp[k++] = a[i++];
            }
        }

        if(j <= hi) {
            while(j <= hi) {
                temp[k++] = a[j++];
            }
        }

        for(int m = 0; m < hi-lo+1; m++) {
            a[lo+m] = temp[m];
        }
    }

示例三: 快排
问题描述:有一个无序的大小为n的数组a[n],使用快速排序将数组进行升序排序

快排的核心是查询每次划分的中间位置

public void quickSort(int[] a, int start, int end) {
        if(end <= start)
            return;
        int mid = getMid(a, start, end);
        quickSort(a, start, mid - 1);
        quickSort(a, mid + 1, end);
    }

    //查询每次划分的中间位置
    public int getMid(int[] a, int start, int end) {
        int i = start;
        int j = end;
        int x = a[i];
        while(i < j) {
            while(i < j && x <= a[j]) j--;
            if(i < j) a[i] = a[j];
            while(i < j && x >= a[i]) i++;
            if(i < j) a[j] = a[i];
        }
        a[i] = x;
        return i;
    }

总结:分治法类似于数学归纳法,首先需要找到最下问题规模时的求解方法,然后考虑随着问题规模的增大的求解方法。找到求解的递推函数式后,设计递归程序即可。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值