基于递归的技术
??????遞歸是一種簡化運(yùn)算規(guī)模的方法。將每個(gè)問題分為一個(gè)個(gè)子問題,子問題從結(jié)構(gòu)上來看與原問題相同,但是運(yùn)算規(guī)模小于原問題。不斷遞歸下去,直到有一個(gè)子問題是已知可解決的。反過來再將每一個(gè)完成的子問題作為父問題的輸入,直到回到第一層得到最終的結(jié)果。
??????書中把遞歸分為了歸納法,分治法,和動(dòng)態(tài)規(guī)劃。由于課程中動(dòng)態(tài)規(guī)劃占的課時(shí)比重較大,所以把它單列一篇博客,本文只介紹歸納法和分治法。
??????歸納法和分治法的差別就像,歸納法是一個(gè)一個(gè)的遞歸,類似于棧的結(jié)構(gòu)。分治法的遞歸像樹的結(jié)構(gòu),在不斷向下的同時(shí)也不斷分裂成為更小規(guī)模。下面就介紹一下歸納法和分治法吧!
一、 歸納法(尾遞歸):
??????歸納法源自數(shù)學(xué)歸納法。當(dāng)我們已知如何求解一個(gè)小一點(diǎn)的數(shù)量的時(shí)候,想要求一個(gè)更大的數(shù)量,只需要把小數(shù)做一個(gè)擴(kuò)展即可。下面從兩個(gè)例子出發(fā):
5.2.1選擇排序:
??????在前文中對(duì)選擇排序,進(jìn)行了介紹。從第一個(gè)數(shù)字開始,向后依次選出最小的交換至當(dāng)前開頭。在遞歸中,如果我們已知如何將從1-n中選擇出最小的元素。那么接下來就可以使用遞歸完成操作。
??????從遞歸調(diào)用中,元素的比較次數(shù)為:
??????最后求出的時(shí)間復(fù)雜度是O(n^2)。
5.2.2插入排序:
??????插入排序是一個(gè)從后往前的過程,假如我已知如何對(duì)第n個(gè)元素進(jìn)行插入排序,那么依次向前遞歸就能是數(shù)組有序。
??????最后求出的時(shí)間復(fù)雜度是O(n^2)
5.4整數(shù)冪
??????對(duì)于正數(shù)冪的運(yùn)算通常是使用<math.h>頭文件中包含的pow函數(shù)。但如果禁止使用該函數(shù),常規(guī)一點(diǎn)的想法是將n個(gè)x相乘。這要n-1次乘法。(已經(jīng)證實(shí)調(diào)用pow函數(shù)所花費(fèi)的時(shí)間大于自己編寫實(shí)現(xiàn)乘方的函數(shù))。相較于輸入來說是指數(shù)級(jí)。于是我們采取以下算法:假設(shè)我們已經(jīng)知道如何計(jì)算x的(n/2)次方,這么可以再通過一次平方得到x^2。以此種方法,類推下去就可以求出x的任意次方,偽代碼如下:
5.7尋找多數(shù)元素:
??????多數(shù)元素的定義:數(shù)組A中元素a出現(xiàn)的次數(shù)大于n/2向下取整,則認(rèn)為a是多數(shù)元素。
??????求解多數(shù)元素,方法一是使用蠻力法:從第一個(gè)元素開始,依次向后掃描,計(jì)算每對(duì)相同的個(gè)數(shù)最后得出結(jié)論,這樣的時(shí)間復(fù)雜度是O(n^2)。較方法一更好的方法二是對(duì)數(shù)組進(jìn)行排序(選用最佳的排序算法,時(shí)間復(fù)雜度是O(nlogn)),由多數(shù)元素的特點(diǎn)知,位于n/2向上取整的元素有可能是多數(shù)元素,再花費(fèi)O(n)的時(shí)間進(jìn)行判斷。總體時(shí)間復(fù)雜度是O(nlogn)。第三種算法是從第一個(gè)元素開始,向后掃描,遇到相同的count加1(count初始化為0),遇到不同的count減一,看最終count和零比較,若大于零則是多數(shù)元素。并且可以將此過程遞歸,程序偽代碼如下圖:
二、分治法
??????分治的主要思想是把大規(guī)模數(shù)據(jù)劃分成小規(guī)模,再把小規(guī)模數(shù)據(jù)組合起來。通常這樣的做法能夠使算法時(shí)間復(fù)雜度降低一個(gè)數(shù)量級(jí)。在
??????通常情況下,分治都是使用二分法。下文中很多例子都是基于二分法的思想提出的。下面結(jié)合一些具體應(yīng)用,體會(huì)遞歸中的分治法思想。
1. 尋找最大最小值:
??????遍歷整個(gè)數(shù)組,尋找最大最小值所花費(fèi)的比較次數(shù)具體是2n-2,可以使用另一種思想:將數(shù)組分割成兩半,尋找到每部分的最大最小值,再將兩部分做比較,較大值作為最大值,較小值作為最小值。依次遞歸下去,根據(jù)遞推公式可求得該比較次數(shù)為3n/2-2。偽代碼如下:
2. 二分搜索:
??????二分搜索是一種常用于有序數(shù)組得搜索方式,通過和中間的數(shù)字進(jìn)行比較確定下一次比較的范圍。時(shí)間復(fù)雜度為O(logn)。具體的偽代碼如下:
??????注意:在本遞歸算法中,需要O(logn)的空間復(fù)雜度,而在迭代算法中只需要O(1)得空間復(fù)雜度。我理解為在于遞歸過程中需要保存每層迭代的mid值。
3. 合并排序:
??????合并排序是將數(shù)組從上方不斷分為兩部分,直至最小部分后。再反向排序遞歸向上,時(shí)間復(fù)雜度為O(nlogn)。合并排序需要O(n)的輔助空間。這也是合并排序的缺點(diǎn)之一。具體的偽代碼如下圖:
??????需要注意的是合并排序和自底向上排序不是完全一樣的(不然也不會(huì)出現(xiàn)兩個(gè)名字)。合并排序強(qiáng)調(diào)相對(duì)均分,無論奇數(shù)還是偶數(shù)。自底向上排序在最底層是兩兩集合的,如果剩下單個(gè)是不會(huì)加入到過程中的排序的(這是兩者最大的區(qū)別)。所以當(dāng)使用二分法,并且數(shù)組大小是2的冪次方時(shí),兩者并無區(qū)別。
?????? 為什么說,歸并排序的實(shí)現(xiàn)類似于二叉樹的后序遍歷?
???????????從總體上看,歸并排序的結(jié)構(gòu)以二叉樹型存在。而且在排序的實(shí)現(xiàn)過程中,都是從下往上的(此處理解為先葉子節(jié)點(diǎn)再根節(jié)點(diǎn)),與后序遍歷類似。而且從分層方面,每一層排序(merge排序)的時(shí)間復(fù)雜度都是o(n)。層數(shù)為logn層,也解釋了時(shí)間復(fù)雜度是O(nlogn)
??????對(duì)于分治操作大致可分為三個(gè)主要步驟:劃分、治理、組合。不同算法對(duì)三個(gè)步驟的時(shí)間復(fù)雜度是不同的。在治理步驟中,比較重要是的尋找一個(gè)合適的闕值,如果數(shù)據(jù)規(guī)模小于闕值,可以直接采用簡單方法,而不會(huì)對(duì)時(shí)間造成較大影響。照目前我掌握的知識(shí)來看,組合的算法時(shí)間復(fù)雜度很大程度上決定了最后遞歸的好壞。
4. 尋找中項(xiàng)和第k個(gè)元素(SELECT方法)
??????常規(guī)做法是將數(shù)組排序后,選擇對(duì)應(yīng)位置。這樣的時(shí)間復(fù)雜度是O(nlogn)。提出一種選擇的思想:將數(shù)組劃分,選擇出每個(gè)劃分組的中值,組成數(shù)組A。再從A中選出中值B。將數(shù)組分為三部分:大于B,小于B,等于B。并且出三組內(nèi)對(duì)應(yīng)的元素個(gè)數(shù),和想要尋找位置進(jìn)行比較。選擇合適的組,再進(jìn)行遞歸,直到結(jié)果。具體的偽代碼如下:
??????可證,這種選擇法的時(shí)間復(fù)雜度是O(n)。
5. 快速排序:
??????快速排序是一種時(shí)間復(fù)雜度為O(nlogn)的算法。基本思想利用了分治的思想。治理的過程調(diào)用了SPLIT算法,SPLIT算法思想是選定一個(gè)標(biāo)準(zhǔn),將數(shù)組中小于標(biāo)準(zhǔn)的數(shù)據(jù)全部放在標(biāo)準(zhǔn)左側(cè),大于標(biāo)準(zhǔn)的數(shù)據(jù)全部放在標(biāo)準(zhǔn)右側(cè)。所以快速排序是原地排序。SPILT的偽代碼如下:
??????對(duì)于SPILT來說,要想達(dá)到做好的效果,要做到兩邊數(shù)據(jù)平衡。若整個(gè)數(shù)組已按照非降序排列,每次SPILT的時(shí)間復(fù)雜度就變成了O(n),而SPILT的次數(shù)也變成了n,整個(gè)快速排序此時(shí)處于最壞情況O(n2)。最佳情況選擇的標(biāo)準(zhǔn)就恰好使兩邊平衡,此時(shí)可以使用前文提到的SELECT算法,直接選出,最中間的數(shù)據(jù)。這是SPILT的時(shí)間復(fù)雜度不變,但次數(shù)變?yōu)閘ogn。總體時(shí)間復(fù)雜度變?yōu)镺(nlogn)。
??????快速排序雖然使用原地排序,但遞歸過程中需要保存過程中間的標(biāo)準(zhǔn),所以總體空間復(fù)雜度也是O(n)。
??????在算法設(shè)計(jì)中,乘法消耗的時(shí)間是比較多的。可以采用一種思想:將乘法轉(zhuǎn)換為加法。這樣往往能降低時(shí)間復(fù)雜度的數(shù)量級(jí)。例如常規(guī)矩陣乘法時(shí)間復(fù)雜度是O(n3),使用STRASSEN算法,就能把時(shí)間復(fù)雜度降低至O(n2.81)。
6. 最近點(diǎn)對(duì)問題:
??????最近點(diǎn)對(duì)問題要求求出二維平面中哪兩個(gè)點(diǎn)間距離最小。使用蠻力算法:計(jì)算每一個(gè)點(diǎn)到其余n-1個(gè)點(diǎn)的距離,最后求出最小值。時(shí)間復(fù)雜度為O(n2)。使用CLOSEPATH算法可將時(shí)間復(fù)雜度降低至O(nlogn)。CLOSEPATH算法基于分治思想,選擇中線將二維平面劃分為兩部分。這樣不斷遞歸下去,能找到最短兩點(diǎn)距離。最短有三種情況,兩點(diǎn)都在左側(cè)或右側(cè),兩點(diǎn)一左一右。在遞歸的過程中,就將兩點(diǎn)在同側(cè)的計(jì)算出來。再選出更小的x1,只需判斷x1和兩點(diǎn)一左一右的最小距離x2誰大誰小即可。若x1更小,則以x1為邊界就足夠。以尋找到x1的中線為標(biāo)準(zhǔn),向左向右畫長度x1,同意縱向也畫x1長度的表格。形成一個(gè)x1*2x1的矩形。利用鴿舍原理可證明,在表格的一段最多有d。對(duì)于此過程證明相對(duì)繁瑣。本處指的d不一定是實(shí)際存在的點(diǎn),指的是最多的情況。這些情況都將發(fā)生在邊界上。只需對(duì)邊界及以內(nèi)符合要求的點(diǎn)進(jìn)行判斷即可。對(duì)此判斷的方式選擇了對(duì)Y進(jìn)行排序。對(duì)此問題更為詳細(xì)的介紹可參照博客:https://blog.csdn.net/lishuhuakai/article/details/9133961。link
??????CLOSEPATH的偽代碼如下:
分治法為什么好?
??????從時(shí)間復(fù)雜度的方向解釋,下面的話引自一位博主(原文鏈接:https://blog.csdn.net/code_star_one/article/details/72724555)
link
??????“一個(gè)分治法將規(guī)模為 n 的問題分成 k 個(gè)規(guī)模為 n/m 的子問題去解。設(shè)分解閥值 n0 = 1,且adhoc解規(guī)模為1的問題耗費(fèi)1個(gè)單位時(shí)間。再設(shè)將原問題分解為k個(gè)子問題以及用 merge 將 k 個(gè)子問題的解合并為原問題的解需用 f(n) 個(gè)單位時(shí)間。用T(n)表示該分治法解規(guī)模為 |P| = n的問題所需的計(jì)算時(shí)間,則有:
??????T(n)= k T(n/m)+f(n)
??????通過迭代法求得方程的解:
???????????遞歸方程及其解只給出n等于m的方冪時(shí) T(n) 的值,但是如果認(rèn)為 T(n) 足夠平滑,那么由 n 等于 m 的方冪時(shí) T(n) 的值可以估計(jì)T (n) 的增長速度。通常假定 T(n) 是單調(diào)上升的,從而當(dāng) mi ≤ n < mi+1時(shí),T(mi) ≤ T(n) < T(mi+1)。“
??????在很多情況下,分治法的時(shí)間復(fù)雜度決定于組合算法的選擇。選擇正確的組合算法有可能使時(shí)間復(fù)雜度降低一個(gè)數(shù)量級(jí)。如:最小點(diǎn)對(duì)問題。
??????本文作者水平有限,如有不正確之處。請(qǐng)各位下方評(píng)論區(qū)指正。謝謝!
總結(jié)
- 上一篇: CentOS6.5 常用命令
- 下一篇: 查找算法:折半查找算法实现及分析