算法(二):分治法

来源:互联网 发布:小满软件怎么样 编辑:程序博客网 时间:2024/06/16 03:42

分治法的基本思想

分治法的基本思想是将一个规模为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;    }

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

原创粉丝点击