分治算法

算法思想

分治运褪,分而治之仇奶,將原問題劃分成 n 個(gè)規(guī)模較小而結(jié)構(gòu)與原問題相似的子問題,這些規(guī)模小的問題與原問題是同質(zhì)的驾窟,本質(zhì)上還是同一個(gè)問題庆猫,遞歸解決這些子問題,然后合并其結(jié)果纫普,就能得到原問題的解阅悍。(PS:當(dāng)然,遞歸不是必須的)

空間換時(shí)間昨稼,來實(shí)現(xiàn)算法時(shí)間復(fù)雜度的優(yōu)化

【分治算法】是很多高效算法的基礎(chǔ)节视,諸如快速排序、歸并排序假栓、傅立葉變換寻行、二分搜索

特征

  • 原問題的規(guī)模縮小到一定的程度就很容易解決
    -- 絕大多數(shù)問題都可以滿足匾荆,因?yàn)閱栴}的計(jì)算復(fù)雜性通常都是隨著問題規(guī)模的增加而增加
  • 原問題可以分解為若干個(gè)規(guī)模較小的相同問題拌蜘,即該問題具有【最優(yōu)子結(jié)構(gòu)性質(zhì)】
    -- 應(yīng)用分治法的前提杆烁,大多數(shù)問題也可以滿足,它反映了遞歸思想的應(yīng)用
  • 分解出的子問題的解可以合并為該問題的解
    -- 能否利用分治法的關(guān)鍵特征简卧。如果只具備第一兔魂、第二特征,而不具備第三特征举娩,則可以考慮動(dòng)態(tài)規(guī)劃或貪心算法
  • 分解出的各個(gè)子問題相互獨(dú)立析校,即【子問題之間不包括公共的子問題】
    -- 涉及分治算法的效率,如果各個(gè)子問題不是相互獨(dú)立的铜涉,分治算法要做很多不必需要的工作智玻,重復(fù)地解決公共子問題,此時(shí)采用動(dòng)態(tài)規(guī)劃會(huì)比較好

前三個(gè)特征是使用分治法的關(guān)鍵芙代,而特征4 涉及到分治法的效率問題吊奢。如果不符合3、4特征纹烹,可以嘗試使用動(dòng)態(tài)規(guī)劃或胎心算法來解決页滚。

【動(dòng)態(tài)規(guī)劃】是一種特殊的分治,貪心算法是一種特殊的動(dòng)態(tài)規(guī)劃

適用范圍:

  • 分治算法:最優(yōu)子結(jié)構(gòu)
  • 動(dòng)態(tài)規(guī)劃:最優(yōu)子結(jié)構(gòu)滔韵、重疊子問題
  • 貪心算法:最優(yōu)子結(jié)構(gòu)逻谦、重疊子問題、貪心選擇性質(zhì)

分支模式在每一層上都有三個(gè)步驟:

  1. 分解陪蜻,將原問題分解成一系列與原問題同質(zhì)的子問題
  2. 解決,遞歸解決各個(gè)子問題贱鼻,若子問題足夠小宴卖,則直接求解
  3. 合并,將子問題的結(jié)果合并成原問題的解

舉個(gè)例子

有一個(gè)很經(jīng)典的問題:有100枚硬幣邻悬,其中有1枚略輕一些的假幣症昏,如果用天平秤,請問至少稱幾次一定能找到這枚假幣父丰?

  • 如果用傳統(tǒng)的逐枚比較法肝谭,顯然至少需要比較50次
    比較第 i 個(gè)與第 i+1 個(gè)的重量,若相等蛾扇,則i++攘烛,繼續(xù)比較,直到重量不相等镀首,并輸出較輕的硬幣編號(hào)坟漱。

  • 采用分治算法

    1. 100枚硬幣分成3份:33、33更哄、34
    2. 稱重 1芋齿、2 份腥寇,若天枰平衡,則假幣必在另外 34 枚中觅捆;若不平衡赦役,則在較輕的那份 33 枚中
    3. 再將 34 枚分成3份:11、11栅炒、12(或?qū)?33 枚分成 11扩劝、11、11
    4. 稱重兩組 11 枚的硬幣职辅,若平衡棒呛,則假幣在 12 枚里(或第三份 11 枚);若不平衡域携,則在較輕的那份 11 枚中
    5. 12 枚分成3份:4簇秒、4、4(或?qū)?11 枚分成 4秀鞭、4趋观、3),稱重方法同上
    6. 4 枚分成 3 份:1锋边、1皱坛、2,稱重1/1豆巨,若平衡剩辟,則稱重剩下的 2 枚,較輕的 1 枚是假幣往扔;若不平衡贩猎,較輕的 1 枚是假幣。(或?qū)?3 枚分成1萍膛、1吭服、1,稱重1/1蝗罗,若平衡艇棕,則剩下的 1 枚是假幣;若不平衡串塑,則較輕的 1 枚是假幣)

    綜上所述沼琉,最多只需要 5 次就能解決這個(gè)問題!

通過觀察 1-2拟赊、3-4刺桃、5-6 發(fā)現(xiàn),除了硬幣數(shù)量變化了其他步驟完全一樣吸祟,即 這是一個(gè)子問題的分解過程瑟慈,100-33-11-3桃移,將一個(gè)大問題分解成了容易解決的小問題,且 這些小問題相互獨(dú)立葛碧,每一個(gè) 33 枚硬幣和其他硬幣互不影響借杰。
實(shí)際上類似于數(shù)學(xué)歸納法,先找到最小問題規(guī)模的求解方程进泼,然后考慮隨著問題規(guī)模增大時(shí)的求解方程蔗衡,然后根據(jù)方程式設(shè)計(jì)遞歸程序(當(dāng)然,遞歸不是必須的)乳绕,自頂而下的解決問題绞惦。

歸并排序

歸并排序是【分治算法】的典型應(yīng)用:多次分解數(shù)列,直至子數(shù)列中只有一個(gè)元素(【分】)洋措。然后對子數(shù)列排序济蝉,合并相鄰的有序子數(shù)列,最終形成一個(gè)完整的有序數(shù)列(【治】)菠发。

  • 【分】階段:遞歸拆分子序列的過程王滤,遞歸深度為 log2n
歸并排序的分治算法.png
  • 【合】階段:將兩個(gè)已經(jīng)有序的、相鄰的子序列合并成一個(gè)有序的序列滓鸠,效率可以達(dá)到 O(n)
    以最后一次合并為例:[4,5,7,8]雁乡、[1,2,3,6]
合并.png
  • JavaScript 版的實(shí)現(xiàn)
    function sort(arr, i, j) {
        // 直到拆分成只有一個(gè)元素的子序列
        if(i >= j) return
        // 取中間值,分區(qū)
        let mid = Math.floor((i + j) / 2)
        // 1. 拆分左側(cè)一半
        sort(arr, i, mid)
        // 2. 拆分右側(cè)一半
        sort(arr, mid+1, j)
        // 比較, 合并左側(cè)和右側(cè)的結(jié)果
        merge(arr, i, mid, j)
    }
    function merge(arr, left, mid, last) {
        // 一個(gè)臨時(shí)數(shù)組 及其角標(biāo)指針, 存儲(chǔ)排序后的元素糜俗。即:歸并排序 需要申請額外的空間
        let temp = [], k = 0
        // 左側(cè)的起始角標(biāo) i, 右側(cè)的起始角標(biāo) j
        let i = left, j = mid + 1
        while(i <= mid && j <= last) {
            // 左側(cè)和右側(cè)都是有序的, 一次比較踱稍,把較小的先放進(jìn)臨時(shí)數(shù)組
            // 即:相等的元素不會(huì)交換順序,故歸并排序是穩(wěn)定的
            if(arr[i] < arr[j]) {
                temp[k++] = arr[i++]
            } else {
                temp[k++] = arr[j++]
            }
        }
        // 左側(cè)子序列 和 右側(cè)子序列 的長度不一定是相等的吩跋,但都是有序的寞射,
        // 所以多出來的可以直接放進(jìn)臨時(shí)數(shù)組中
        while(i <= mid) {
            temp[k++] = arr[i++]
        }
        while(j <= last) {
            temp[k++] = arr[j++]
        }
        // 把排好序的數(shù)組temp, 添加進(jìn)原數(shù)組中
        for(let n = 0; n < k; n++) {
            arr[left+n] = temp[n]
        }
    }
    let arr = [53, 16, 88, 79, 93, 19, 47, 20]
    //          0   1   2   3   4   5   6  7
    let i = 0, j = arr.length - 1
    sort(arr, i, j)  // [16, 19, 20, 47, 53, 79, 88, 93]
    

【歸并排序】的效率是比較高的,設(shè)數(shù)列長為N锌钮,將數(shù)列分開成小數(shù)列一共要 logN 步,每步都是一個(gè)合并有序數(shù)列的過程引矩,時(shí)間復(fù)雜度可以記為O(N)梁丘,故一共為 O(N*logN)
因?yàn)椤練w并排序】每次都是在 相鄰 的數(shù)據(jù)中進(jìn)行操作旺韭,所以【歸并排序】在 O(N*logN) 的幾種排序方法(快速排序氛谜,歸并排序,希爾排序区端,堆排序)也是效率比較高的值漫。

快速排序

【快速排序】也是采用【分治算法】實(shí)現(xiàn)的,是對【冒泡算法】的改進(jìn)织盼。與【歸并排序】不同的是杨何,它需要從數(shù)列中挑出一個(gè)元素作為 基準(zhǔn)酱塔。

  • 【分區(qū)】操作: 所有元素比 基準(zhǔn) 小的擺放在 基準(zhǔn) 前面,比 基準(zhǔn)值 大的擺在 基準(zhǔn) 的后面(相同的數(shù)可以到任一邊)危虱。在這個(gè)分區(qū)退出之后羊娃,基準(zhǔn)值 就處于數(shù)列的中間位置。
  • 遞歸地重復(fù)【分區(qū)操作】埃跷,直到分區(qū)內(nèi)只有一個(gè)元素蕊玷。

【快速排序】的難點(diǎn)在于 如何選取【基準(zhǔn)點(diǎn)】,并按照【基準(zhǔn)點(diǎn)】排序?
為了簡單起見弥雹,以第一個(gè)元素作為 基準(zhǔn)值

  • 填坑法
    思路:

    1. i=begin, j=last垃帅,將基準(zhǔn)挖出,形成第一個(gè)坑arr[i]
    2. 從右向左(j--)找比基準(zhǔn)值小的數(shù)剪勿,填入arr[i]的坑中贸诚,并把基準(zhǔn)值填入arr[j]
    3. 從左向右(i++)找比基準(zhǔn)值大的數(shù),填入arr[j]的坑中窗宦,并把基準(zhǔn)值填入arr[i]
    4. 重復(fù)步驟2-3赦颇,直至i >= j,則第一個(gè)分區(qū)完成赴涵,基準(zhǔn)值左側(cè)是比基準(zhǔn)小的數(shù)媒怯,基準(zhǔn)值右側(cè)是比基準(zhǔn)大的數(shù),分別對左分區(qū)和有分區(qū)進(jìn)行排序髓窜。
    function sort(arr, begin, last) {
        if(begin >= last) {
            return
        }
        let i = begin, j = last, mid
        let base = arr[i]
        while(true) {
            // j 從后向前找比 base 小的數(shù), 并放到基準(zhǔn)值左側(cè)
            while(i !== j) {
                if(arr[j] < base) {
                    // 找到了, 與基準(zhǔn)值交換位置
                    arr[i] = arr[j]
                    arr[j] = base  // j 指向基準(zhǔn)值
                    break
                } else {
                    // 沒找到, 則向后移動(dòng)角標(biāo), 繼續(xù)查找
                    j--
                }
            }
            if(i >= j) {
                // 結(jié)束了, 記錄基準(zhǔn)值的角標(biāo) j
                mid = j
                break
            }
            // i 從前向后找比 base 大的數(shù), 并放到基準(zhǔn)值右側(cè)
            while(i !== j) {
                if(arr[i] > base) {
                    // 找到了, 與基準(zhǔn)值交換位置
                    arr[j] = arr[i]
                    arr[i] = base  // i 總是指向基準(zhǔn)值
                    break
                } else {
                    // 沒找到, 則向后移動(dòng)角標(biāo), 繼續(xù)查找
                    i++
                }
            }
            if(i >= j) {
                // 結(jié)束了, 記錄基準(zhǔn)值的角標(biāo) i
                mid = i
                break
            }
        }
        // 左分區(qū)
        sort(arr, begin, mid-1)
        // 右分區(qū)
        sort(arr, mid + 1, last)
    }
    let arr = [53, 16, 88, 79, 93, 19, 47, 20]
    //          0   1   2   3   4   5   6  7
    let i = 0, j = arr.length - 1
    sort(arr, i, j)
    
  • 交換法
    思路:

    1. 分別設(shè)置左右兩個(gè)指針:i=begin, j=last
    2. 以左側(cè)第一個(gè)元素為基準(zhǔn)時(shí)扇苞,先移動(dòng)右側(cè)指針j(稍后解釋為什么)
    3. 當(dāng)j遇到比基準(zhǔn)小的數(shù)時(shí),停止掃描寄纵;開始掃描左側(cè)指針i
    4. 當(dāng)i遇到比基準(zhǔn)大的數(shù)時(shí)鳖敷,停止掃描,并交換ij處的值程拭,此時(shí)i處的元素值一定比基準(zhǔn)小
    5. 繼續(xù)移動(dòng)指針j定踱,重復(fù)步驟3-4,直到ji相遇恃鞋,則交換基準(zhǔn)與i/j處的元素值崖媚;
    6. 至此,第一個(gè)分區(qū)完成恤浪〕┭疲基準(zhǔn)值左側(cè)一定是比基準(zhǔn)小的數(shù),基準(zhǔn)值右側(cè)是比基準(zhǔn)大的數(shù)水由,然后分別對左分區(qū)和有分區(qū)進(jìn)行排序荠呐。

    注:為什么先移動(dòng)右指針j?當(dāng)然,這不是絕對的泥张,這取決于基準(zhǔn)的位置呵恢,因?yàn)楫?dāng)兩個(gè)指針相遇時(shí),需要與基準(zhǔn)交換元素值圾结。當(dāng)基準(zhǔn)值在左邊時(shí)瑰剃,必須確保指針相遇的值一定比基準(zhǔn)小,而左指針i 始終指向小于基準(zhǔn)值的元素筝野,所以讓右指針j先移動(dòng)晌姚,讓ji靠攏,最終相遇歇竟。

    function sort(arr, begin, last) {
        if(begin >= last) {  // 結(jié)束
            return
        }
        let base = arr[begin]
        let i = begin, j = last
        while(i < j) {
            // 先移動(dòng)右側(cè)指針j, 找比基準(zhǔn)值小的數(shù)
            while(i < j && arr[j] >= base) {
                j--
            }
            // 再移動(dòng)左側(cè)指針i, 找比基準(zhǔn)值大的數(shù)
            while(i < j && arr[i] <= base) {
                i++
            }
            if(i < j) {
                // i 記錄比基準(zhǔn)值大的值, j 記錄比基準(zhǔn)值小的值, 交換元素值
                let temp = arr[i]
                arr[i] = arr[j]
                arr[j] = temp
            }
        }
        // 此時(shí) i === j, 且 i 處的元素一定比基準(zhǔn)值小, 交換元素值
        arr[begin] = arr[i]
        arr[i] = base
        // 第一次分區(qū)結(jié)束, 以基準(zhǔn)值的位置i 為分區(qū)線, 遞歸分區(qū)比較
        // 1. 左側(cè)分區(qū)
        sort(arr, begin, i - 1)
        // 2. 右側(cè)分區(qū)
        sort(arr, i + 1, last)
    }
    

【快速排序】是不穩(wěn)定的挥唠,它的速度與【基準(zhǔn)點(diǎn)】有關(guān),【基準(zhǔn)點(diǎn)】的好壞大大影響速度焕议。

  • 最差情況下宝磨,劃分由 n 個(gè)元素構(gòu)成的數(shù)組需要進(jìn)行 n 次比較和 n 次移動(dòng),因此劃分所需時(shí)間為 O(n)盅安。最差時(shí)間復(fù)雜度 (n-1)+(n-2)+…+2+1= O(n^2)
  • 在最佳情況下唤锉,每次將數(shù)組劃分為規(guī)模大致相等的兩部分。和【歸并排序】的分析相似别瞭,快速排序的T(n) = O(nlogn)

求數(shù)組中第K個(gè)最大(辛椤)元素

思路:這也是分治思想的應(yīng)用,與【快速排序】類似蝙寨。但不同的是晒衩,【快速排序】每次都要處理基準(zhǔn)兩側(cè)的分區(qū),而【求第K個(gè)最大(星酵帷)元素】只需要處理一側(cè)分區(qū)即可听系。

求最接近原點(diǎn)的 K 個(gè)點(diǎn)

找出最大子序列

求 x 的 n 次冪

棋盤覆蓋問題

整數(shù)劃分問題

全排列問題

漢諾塔問題

大整數(shù)乘法

快速傅里葉變換

循環(huán)比賽日程表

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虹菲,隨后出現(xiàn)的幾起案子靠胜,更是在濱河造成了極大的恐慌,老刑警劉巖毕源,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓帽,死亡現(xiàn)場離奇詭異,居然都是意外死亡脑豹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門衡查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘩欺,“玉大人,你說我怎么就攤上這事【愣觯” “怎么了歌粥?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拍埠。 經(jīng)常有香客問我失驶,道長,這世上最難降的妖魔是什么枣购? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任嬉探,我火速辦了婚禮,結(jié)果婚禮上棉圈,老公的妹妹穿的比我還像新娘涩堤。我一直安慰自己,他們只是感情好分瘾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布胎围。 她就那樣靜靜地躺著,像睡著了一般德召。 火紅的嫁衣襯著肌膚如雪白魂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天上岗,我揣著相機(jī)與錄音福荸,去河邊找鬼。 笑死液茎,一個(gè)胖子當(dāng)著我的面吹牛逞姿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捆等,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼滞造,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栋烤?” 一聲冷哼從身側(cè)響起谒养,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎明郭,沒想到半個(gè)月后买窟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薯定,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年始绍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片话侄。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亏推,死狀恐怖学赛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吞杭,我是刑警寧澤盏浇,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站芽狗,受9級特大地震影響绢掰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜童擎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一滴劲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柔昼,春花似錦哑芹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乙嘀,卻和暖如春末购,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虎谢。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工盟榴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人婴噩。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓擎场,卻偏偏與公主長得像,于是被迫代替她去往敵國和親几莽。 傳聞我的和親對象是個(gè)殘疾皇子迅办,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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

  • 分治法學(xué)習(xí)總結(jié) 分治法是我們經(jīng)常用到的算法,合理利用分治算法可以使我們更好的解決問題章蚣。我們在使用二分查找站欺、歸并排序...
    魚魚魚三條魚ii閱讀 5,078評論 0 5
  • Divide-and-Conquer算法的設(shè)計(jì) 設(shè)計(jì)過程分為三個(gè)階段: Divide:整個(gè)問題劃分為多個(gè)子問題 C...
    三三de酒閱讀 3,255評論 0 4
  • 分治算法簡介 在計(jì)算機(jī)科學(xué)中,分治法是一種很重要的算法纤垂。字面上的解釋是“分而治之”矾策,簡單來說就是把一個(gè)問題分解為很...
    呼嚕嚕11閱讀 509評論 0 0
  • # 基本思想: 字面上的解釋是“分而治之”,就是將一個(gè)規(guī)模為N的問題分解為K個(gè)規(guī)模較小的子問題( 反復(fù)分解直到問題...
    Tenloy閱讀 489評論 0 3
  • 5月以來,哪怕對市場風(fēng)向再不敏感的人庆尘,也感覺到陣陣涼意。二級市場連續(xù)下挫巷送,一級市場融資環(huán)境惡化驶忌,不論企業(yè)融資數(shù)量還...
    錢皓頻道閱讀 6,032評論 1 6