分治算法

分冶算法的基本思想是將原問(wèn)題分解為幾個(gè)規(guī)模較小的但類似原問(wèn)題的子問(wèn)題,遞歸地求解這些了問(wèn)題,然后再合并這些子問(wèn)題的解來(lái)建立原問(wèn)題的解

分冶算法在每層遞歸時(shí)都有三個(gè)步驟:

  • 分解,原問(wèn)題分為若干子問(wèn)題,這些子問(wèn)題都是原問(wèn)題的規(guī)模較小的實(shí)例
  • 解決,遞歸解決這些子問(wèn)題,如果子問(wèn)題的規(guī)模足夠小,則直接求解
  • 合并, 合并子問(wèn)題的解成原問(wèn)題的解

分治算法中需要使用遞歸,在使用遞歸時(shí),一定要確定問(wèn)題邊界,即問(wèn)題規(guī)模較小的條件。常見(jiàn)的歸并排序以及求解數(shù)組中連續(xù)子數(shù)組和的最大值,都可以使用分治法解決

歸并排序

歸并排序的基本思想是,將數(shù)組分成兩個(gè)子數(shù)組,使用遞歸對(duì)兩個(gè)子數(shù)組進(jìn)行排序,合并兩個(gè)已正確排序的子數(shù)組。

注意,遞歸的邊界條件即是,子數(shù)組中只有一個(gè)元素。假定數(shù)組中只有兩個(gè)元素,分解成兩個(gè)子數(shù)組,每個(gè)子數(shù)組中只有一個(gè)元素,子數(shù)組中的元素此時(shí)當(dāng)然就是已經(jīng)“排序正確”的,合并子數(shù)組,則整個(gè)數(shù)組已正確排序。

數(shù)組合并是整個(gè)過(guò)程中最復(fù)雜的地方。可以想象手邊有兩堆撲克牌,每堆撲克牌都是從小到大排序完畢,比較每堆撲克牌最上邊的牌的大小,取最小的放到手上,直到有一堆撲克牌已經(jīng)被取完,此時(shí)再將剩下的那一堆撲克牌全部放到手上,則手中的所有牌都是按從小到大順序排列好。

為了界定每堆撲克牌是否已經(jīng)取完,本文中向每堆撲克牌最后放入一張哨兵牌。以便于節(jié)省大量的判斷,牌堆是否已經(jīng)取完。

  //p是數(shù)組中合并的起始index,p是中間位置,r是末位
  public static void merge(int[] array, int p, int q, int r){
    int n1 = q - p + 1;
    int n2 = r - (q + 1) + 1;
    int[] left = new int[n1 + 1];
    int[] right = new int[n2 + 1];
    int i = 0;
    int j = 0;
    for (i = 0; i < n1; i++) {
        left[i] = array[p + i];
    }
    left[n1] = Integer.MAX_VALUE;
    for (j = 0; j < n2; j++) {
        right[j] = array[q + 1 + j];
    }
    right[n2] = Integer.MAX_VALUE;
    i = 0;
    j = 0;
    //哨兵牌為正整數(shù)最大值,所以子數(shù)組中不可能有大于它的數(shù),假設(shè)left子數(shù)組已經(jīng)被取完,只剩下哨兵牌
    //right子數(shù)組剩下的元素則必然全都小于哨兵牌,則可全取right子數(shù)組,而不用判定子數(shù)組是否已經(jīng)取完
    for (int k = p; k <= r; k++) {
        if (left[i] < right[j]) {
            array[k] = left[i];
            i ++;
        }else {
            array[k] = right[j];
            j++;
        }
    }
}

最復(fù)雜的合并工作已經(jīng)完成,則分解子問(wèn)題和求解子問(wèn)題則很簡(jiǎn)單了。

  public static void sort(int[] array, int p, int r){
    if (p < r) {
        int q = (r + p)/2;
        sort(array, p, q);
        sort(array, q + 1, r);
        merge(array, p, q, r);
    }
}

連續(xù)子數(shù)組和最大值

如果在一個(gè)數(shù)組中,找出一個(gè)連續(xù)子數(shù)組和的最大值。此問(wèn)題必須是在有負(fù)數(shù)的數(shù)組中才有意義,如果數(shù)組中全為正數(shù),那么此問(wèn)題的解即為數(shù)組所有元素之和

此問(wèn)題也可以用分治算法解決,將數(shù)組分解為兩個(gè)子數(shù)組,那么問(wèn)題的解必然為以下三個(gè)之一:

  • 左子數(shù)組中的連續(xù)子數(shù)組和最大值
  • 右子數(shù)組中的連續(xù)子數(shù)組和最大值
  • 包含跨越分隔左右子數(shù)組的中間值的連續(xù)子數(shù)組的和的最大值。

分治算法有三步,分解子問(wèn)題、解決子問(wèn)題、全并子問(wèn)題。本問(wèn)題中,合并子問(wèn)題非常簡(jiǎn)單,如果以上三個(gè)值已經(jīng)求出來(lái),通過(guò)比較大小即可得知,時(shí)間復(fù)雜度為1,分解子問(wèn)題和解決子問(wèn)題中只有一步較為復(fù)雜,即是上文中的第三步,求 包含中間值的連續(xù)子數(shù)組和的最大值,不過(guò)將此問(wèn)題單獨(dú)提出來(lái)也是比較簡(jiǎn)單的,因?yàn)榇诉B續(xù)子數(shù)組必然包含中間值。

根據(jù)中間值將數(shù)組分成兩半,分別求取左右兩邊的連續(xù)子數(shù)組和的最大值,再相加即可。時(shí)間復(fù)雜度為n。

  private static int[] getMaxSumSubArrayCorssMidel(int[] array, int low, int middle, int high){
    int left_sum = Integer.MIN_VALUE;
    int sum = 0;
    int maxLeftIndex = 0;
    for (int i = middle; i >= low; i--) {
        sum = sum + array[i];
        if (sum > left_sum) {
            left_sum = sum;
            maxLeftIndex = i;
        }
    }
    sum = 0;
    int right_sum = Integer.MIN_VALUE;
    int maxRightIndex = 0;
    for (int i = middle + 1; i <= high; i++) {
        sum = sum + array[i];
        if (sum > right_sum) {
            right_sum = sum;
            maxRightIndex = i;
        }
    }
    return new int[]{maxLeftIndex, maxRightIndex, left_sum + right_sum};
}

遞歸求解子問(wèn)題的邊界點(diǎn)是什么呢?數(shù)組只有一個(gè)元素,則連續(xù)子數(shù)組和的最大值則為數(shù)組唯一元素。確定了邊界,則剩余代碼非常容易寫(xiě)了

  private static int[] getMaxSumSubArray(int[] array, int low, int high){
    if (low == high) {
        return new int[]{low, high ,array[low]};
    }else {
        int middle = (low + high)/2;
        int[] left = getMaxSumSubArray(array, low, middle);
        int[] right = getMaxSumSubArray(array, middle + 1, high);
        int[] corss = getMaxSumSubArrayCorssMidel(array, low, middle, high);
        if (left[2] >= right[2] && left[2] >= corss[2]) {
            return left;
        }else if (right[2] >= left[2] && right[2] >= corss[2]) {
            return right;
        }else{
            return corss;
        }
    }
}

求取數(shù)組中元素的最大值

求取數(shù)組中元素的最大值也可以使用分治法解決,通常人們都使用二分法查找,后續(xù)將討論分治算法的時(shí)間復(fù)雜度,可計(jì)算得出,二分法和分治算法的時(shí)間復(fù)雜度是一樣的。不過(guò)遞歸本身效率不高,執(zhí)行一次函數(shù)需要入棧出棧多次,分治算法的最終效率肯定是不及二分查找的。不過(guò)此文中只為展示分治算法的使用。

同理,將數(shù)組分成兩個(gè)子數(shù)組,分別求取兩個(gè)子數(shù)組中的最大值,再將兩個(gè)子數(shù)組的最大值比較,取其大者,則可求得此問(wèn)題的解。

本例中合并子問(wèn)題非常簡(jiǎn)單,只需要比較子問(wèn)題的解大小即可,取其大者就ok了,時(shí)間復(fù)雜度為1

  private static int findMax(int[] array, int p, int q){
    if (p == q) {
        return array[q];
    }else {
        int middle = (p + q)/2;
        int left = findMax(array, p, middle);
        int right = findMax(array, middle + 1, q);
        return (left > right) ? left : right;
    }
}

未完待續(xù),關(guān)于分治算法的時(shí)間復(fù)雜度。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Divide-and-Conquer算法的設(shè)計(jì) 設(shè)計(jì)過(guò)程分為三個(gè)階段: Divide:整個(gè)問(wèn)題劃分為多個(gè)子問(wèn)題 C...
    三三de酒閱讀 3,348評(píng)論 0 4
  • 五大常用算法之一:分治算法 一、基本概念 在計(jì)算機(jī)科學(xué)中,分治法是一種很重要的算法。字面上的解釋是“分而治之”,就...
    鮑陳飛閱讀 1,247評(píng)論 0 4
  • http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741...
    RavenX閱讀 461評(píng)論 0 1
  • 1、前言 本文是在閱讀算法導(dǎo)論的時(shí)候做的一點(diǎn)記錄。加上前段時(shí)間閱讀了《計(jì)算機(jī)科學(xué)叢書(shū):設(shè)計(jì)模式 可復(fù)用面向?qū)ο筌浖?..
    BBH_Life閱讀 371評(píng)論 0 0
  • 可以用行走與慢動(dòng)作結(jié)合,也可用于城市人們沒(méi)有靈魂的走動(dòng)
    戲劇教育王楠閱讀 466評(píng)論 0 1