001--算法之"高手過招"[分治算法專題]

作者: CC老師(劉依)

? 著作權(quán)歸作者所有; 未經(jīng)作者授權(quán),不得商業(yè)轉(zhuǎn)載/非商業(yè)轉(zhuǎn)載;
? 算法之"高手過招"知識(shí)付費(fèi)系列下的視頻/文章/源碼/資料已申請(qǐng)版權(quán)保護(hù).
? 最終解釋權(quán)歸邏輯教育所有;

算法之"高手過招"

1.1 什么是分治策略算法?

在計(jì)算機(jī)科學(xué)中,分治策略是非常重要的算法思想. 字面上的意思就是把一個(gè)復(fù)雜問題分解成2個(gè)或者多個(gè)相同或者相似的子問題. 再子問題的分解成更小的子問題; 直到最后的子問題可以簡單的直接求解. 再將子問題的結(jié)果合并得到原問題的結(jié)果;

這樣的方式,比如常用的排序算法中, 快速排序以及歸并排序都是利用了分治策略算法的思想實(shí)現(xiàn)的. 在分治策略中, 我們遞歸地求解一個(gè)問題, 在每層遞歸中應(yīng)用如下三個(gè)步驟:

  • 分解(Divide)步驟, 將問題劃分為一些子問題. 子問題的形式與原問題一樣. 只是規(guī)模更小;
  • 解決(Conquer)步驟,遞歸地求解出子問題,如果子問題的規(guī)模足夠小,即停止遞歸.直接求解;
  • 合并(Combine)步驟,將子問題的解組合成原問題的解;
image.png

當(dāng)子問題足夠大, 需要遞歸求解時(shí). 我們就稱之為"遞歸情況". 當(dāng)子問題變的足夠小, 不再需要遞歸時(shí), 這時(shí)遞歸就已經(jīng)"觸底". 進(jìn)入了基本情況;

并不是所有的問題都能化解成完全一樣的子問題. 也會(huì)出現(xiàn)需要求解與原問題不完全一樣的子問題. 接下來在這一階段的課程,我們會(huì)看到更多基于分治策略的算法.

分治策略,在理解以及設(shè)計(jì)分治策略的算法的能力需要一定時(shí)間去掌握.它需要運(yùn)用歸納法去證明一個(gè)理論. 為了使得遞歸能夠被推行. 很多時(shí)候需要用一個(gè)較為概括或復(fù)雜的問題去取代原有問題,而且并沒有一個(gè)系統(tǒng)性的方法去適當(dāng)?shù)母爬▎栴};

分治算法需要不錯(cuò)的數(shù)學(xué)歸納能力.因?yàn)榉种尾呗缘乃惴ㄍǔP枰詳?shù)學(xué)歸納法來驗(yàn)證, 它的計(jì)算成本則多數(shù)以求解的遞歸關(guān)系式來判定;

由于分治策略的特性, 它最直接的表現(xiàn)形式常常以遞歸出現(xiàn).

1.2 連續(xù)數(shù)列

  • 難度系數(shù): ☆☆☆

  • 題目來源: LeetCode 下分治策略專題

  • 題目描述: 給定一個(gè)整數(shù)數(shù)組,找出總和最大的連續(xù)數(shù)列, 并返回總和;

  • 輸入: [-2,1,-3,4,-1,2,1,-5,4]

  • 輸出: 6

  • 解釋: 連續(xù)子數(shù)組[4,-1,2,-1];

  • 題目解讀:

  • 實(shí)際上, 這個(gè)題目就是單純的獲取從一個(gè)數(shù)組中,找出連續(xù)索引值下元素相加的總和最大的序列; 例如題目描述的例子. 從頭到尾進(jìn)行查找, 使得連續(xù)的元素相機(jī)得到一個(gè)最大的總和;

  • 關(guān)鍵詞: 連續(xù)數(shù)列, 最大, 整數(shù)數(shù)組. 總和;

  • 出現(xiàn)過企業(yè)面試題: 百度

1.2.1 暴力法

LeetCode 執(zhí)行結(jié)果

image.png

暴力算法思路

  • 判斷數(shù)組中的元素個(gè)數(shù), 如果元素個(gè)數(shù)為0,則直接返回0;

  • 判斷數(shù)組中的元素個(gè)數(shù), 如果元素個(gè)數(shù)為1,則直接返回索引為0下的元素值;

  • 定義臨時(shí)變量: i,temp,maxValue; 默認(rèn)將maxValue 設(shè)置一個(gè)極小的值作為初始化值;

  • 開始循環(huán),

  • 循環(huán)起始值i = 0;

  • 循環(huán)結(jié)束條件: 當(dāng)i從頭到尾都遍歷了一次;

  • 循環(huán)遞增條件: i每次自增1;

  • 在循環(huán)體中;

  • 將臨時(shí)變量temp 記錄從0~i的和的累積;

  • 判斷當(dāng)前的temp 是否大于 maxVaue. 如果大于MaxValue 則更新maxValue;

  • 判斷當(dāng)前的temp如果小于0的情況出現(xiàn),則將temp 賦值為0;

  • 最后返回maxValue;

暴力算法代碼實(shí)現(xiàn)

int maxSubArray(int* nums, int numsSize){
    if(numsSize==0) return 0;
    if(numsSize==1) return nums[0];
    int i=0,temp=0,maxValue=-1024;
    for(i=0;i<numsSize;i++)
    {
        temp=temp+nums[i];
        if(temp>maxValue) maxValue=temp;
        if(temp<0) temp=0;
    }
    return maxValue;
}

執(zhí)行過程動(dòng)畫效果

連續(xù)數(shù)列(暴力法).gif

溫馨提示: 上圖為動(dòng)圖效果. 網(wǎng)頁閱讀更佳

1.2.2 分治策略

站在分治策略的角度下思考, 求最大連續(xù)數(shù)組. 我們可以預(yù)估到最大連續(xù)數(shù)組的和 有可能出現(xiàn)的3個(gè)位置如下:

  1. 數(shù)組的左半部分的最大連續(xù)數(shù)組和;
  2. 數(shù)組的右半部分的最大連續(xù)數(shù)組和;
  3. 橫跨數(shù)組的左半部分和右半部分得到最大連續(xù)數(shù)組和;
  4. 三者比較大小, 最大者即為我們所求的最大連續(xù)數(shù)組的和.

LeetCode 執(zhí)行結(jié)果

image.png

接下來,我們分析案例中提供的數(shù)組. 來推演 分治策略的算法思想;

如果推演過程,數(shù)組中元素太多.可能會(huì)造成大家對(duì)于 分治策略中提出的 關(guān)于有可能出現(xiàn)最大連續(xù)數(shù)組和的3個(gè)猜想造成理解負(fù)擔(dān);

所以我們假設(shè)此時(shí) 數(shù)組中只有2個(gè)元素. 數(shù)組nums[2] = {-2,1}; 其中-2和1只是隨機(jī)設(shè)計(jì)的數(shù)字.

也就是 這2個(gè)數(shù)字最多組成3個(gè)可能性;

  • -2; 表示數(shù)組左半部分的有可能是最大連續(xù)數(shù)組的和;
  • -2 + 1 ; 表示數(shù)組橫跨左半部分和右半邊部分有可能是連續(xù)數(shù)組的和;
  • 1 ; 表示數(shù)組右半部分有可能是最大連續(xù)數(shù)組的和;
  • 最后, 比較這3個(gè)數(shù)字,取最大就能獲得最大連續(xù)數(shù)組的和;
image.png

分治策略算法思路

分析: 分治策略在開篇,我們就談過. 分治策略的本質(zhì)就是將問題拆解成最小子問題, 再將子問題的結(jié)合進(jìn)行合并; 而連續(xù)數(shù)列問題, 其實(shí)就可以用到分治策略中的遞歸的方式來進(jìn)行解決; 剛剛我們通過對(duì)2個(gè)元素的數(shù)列進(jìn)行小基數(shù)數(shù)據(jù)的推演. 我們需要將數(shù)列拆解左右2個(gè)半部分, 依次遞歸拆解. 直到只剩下一個(gè)數(shù)字時(shí)就觸碰到遞歸的底線; 那么現(xiàn)在我們捋順一下這個(gè)問題的思路;

思路:

  • 將數(shù)列一分為二. 求解mid = left + (right - left)/2 ; 那么為什么要這么計(jì)算mid 是因?yàn)閿?shù)列在不斷拆解的過程,數(shù)列本身就被折斷了. 但是我們不能記錄錯(cuò)誤的mid; mid 還是基于nums 原始數(shù)組的index ;
  • 遞歸求解數(shù)列左半部分的和 則調(diào)用 subMaxValue(nums, left, mid);
  • 求出左半部分和之后, 則繼續(xù)求解從left 跨越mid 到 right 這橫跨中間部分的和; MidValue(nums,left,mid,right);
  • 繼續(xù)遞歸求解數(shù)列右半部分的和.則調(diào)用 subMaxValue(nums, mid+1,right);
  • 最后在每一次遞歸回調(diào)上層之前,判斷 left_sum,mid_sum,right_sum**** 誰的值更大,則返回上一層遞歸
  • 繼續(xù)遞歸回滾. 直到所有的遞歸都回滾到入口時(shí),就求解出來 連續(xù)數(shù)列的最大和

image.png

分治策略代碼實(shí)現(xiàn)

//
//  main.c
//  001--連續(xù)數(shù)組(分治策略)
//
//  Created by CC老師 on 2020/9/7.
//  Copyright ? 2020 CC老師. All rights reserved.
//

#include <stdio.h>
//宏定義無窮小
#define INF -2147483647
//求a和b的最大值
#define max( a , b ) ( a > b ? a : b )

//求跨越中點(diǎn)mid的最小子數(shù)組和
int MidValue( int * nums , int left , int mid , int right ){

    int left_sum = INF , right_sum = INF;
    int sum = 0;

    //求中點(diǎn)最半部分和
    for( int i = mid ; i >= left ; i-- ){

        sum += *( nums + i );

        if( sum > left_sum ){

            left_sum = sum;

        }

    }

    sum = 0;

    //求中點(diǎn)右半部分和
    for( mid++ ; mid <= right ; mid++ ){

        sum += *( nums + mid );

        if( sum > right_sum ){

            right_sum = sum;

        }

    }

    return right_sum + left_sum;

}

int subMaxValue( int * nums , int left , int right ){

    if( left >= right ){

        return *( nums + left );

    }

    int mid = left + ( right - left ) / 2;
    //僅求中點(diǎn)左部分和
    int left_sum = subMaxValue( nums , left , mid );
    //求跨越中點(diǎn)的和
    int mid_sum = MidValue( nums , left , mid , right );
    //求中點(diǎn)右部分和
    int right_sum = subMaxValue( nums , mid + 1 , right );
    
    return max( left_sum , max( right_sum , mid_sum ) );

}

int maxSubArray( int * nums , int numsSize ){

    return subMaxValue( nums , 0 , numsSize - 1 );

}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    //int nums[] = {-2,1,-3};
    int nums[] = {-2,1,-3,4,-1,2,1,-5,4};
    int maxSum = maxSubArray(nums, 9);
    printf("%d\n",maxSum);
    return 0;
}


總結(jié)與分析

連續(xù)數(shù)列這個(gè)問題, 我們實(shí)現(xiàn)了2種方法. 第一種是暴力法, 第二種是分治策略; 但是從代碼實(shí)現(xiàn)的結(jié)果以及他們的執(zhí)行時(shí)間和內(nèi)存空間占用上,可以發(fā)現(xiàn)第一種方法更有優(yōu)勢(shì). 所以在解決算法問題時(shí), 并不是單純的認(rèn)為分治策略的解決的方案就會(huì)比暴力法高級(jí). 其實(shí)算法重要的還是比較他們的時(shí)間/空間復(fù)雜度. 更重要的是從不同的解決策略去實(shí)現(xiàn)算法, 最大的收益是開拓的解決問題的思維方式;

實(shí)際該問題,還有一種解決方案. 是動(dòng)態(tài)規(guī)劃. 因?yàn)樵撈碌闹鹘鞘?"分治策略" 所以我們就不在這篇中 來進(jìn)行喧賓奪主的事情. 后續(xù)會(huì)有專門關(guān)于 動(dòng)態(tài)規(guī)劃的篇章,我們?cè)诶^續(xù)延伸學(xué)習(xí).

算法之"高手過招" 專欄會(huì)以 幾大算法策略為主題的進(jìn)行不斷更新. 后續(xù)會(huì)繼續(xù)更新"分治策略"篇章.

致謝!

該文章,僅獻(xiàn)給在編程路上, 持續(xù)熱愛算法的開發(fā)者

文章如有錯(cuò)誤,歡迎指正

著作權(quán)歸作者所有; 未經(jīng)作者授權(quán),不得商業(yè)轉(zhuǎn)載/非商業(yè)轉(zhuǎn)載

算法之"高手過招"知識(shí)付費(fèi)系列下的視頻/文章/源碼/資料已申請(qǐng)版權(quán)保護(hù)

致謝(1).jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容