說在前面
個人讀書筆記
起泡排序
在由一組整數組成的序列中,滿足的相鄰元素稱作順序的,否則是逆序的。不難看出,有序序列中每一對相鄰元素都是順序的,亦即,對任意都有;反之,所有相鄰元素均順序的序列,也必然整體有序。
由有序序列的上述特徵,我們可以通過不斷改善局部的有序性實現整體的有序:從前向後依次檢查每一對相鄰元素,一旦發現逆序即交換二者的位置。對於長度爲的序列,共需做次比較和不超過次交換,這一過程稱作一趟掃描交換。
可見,經過這樣的一趟掃描,序列未必達到整體有序。果真如此,則可對該序列再做一趟掃描交換。事實上,很有可能需
要反覆進行多次掃描交換,直到在序列中不再含有任何逆序的相鄰元素。多數的這類交換操作,都會使得越小(大)的元素朝上(下)方移動,直至它們抵達各自應處的位置。
排序過程中,所有元素朝各自最終位置亦步亦趨的移動過程,猶如氣泡在水中的上下沉浮,起泡排序(bubblesort)算法也因此得名。
經過趟掃描交換之後,最大的前個元素必然就位;經過k趟掃描交換之後,待求解問題的有效規模將縮減至
時間複雜度
隨着輸入規模的擴大,算法的執行時間將如何增長?執行時間的這一變化趨勢可表示爲輸入規模的一個函數,稱作該算法的時間複雜度(time complexity)。具體地,特定算法處理規模爲n的問題所需的時間可記作。
大記號
同樣地出於保守的估計,我們首先關注的漸進上界。爲此可引入所謂“大記號”(big-O notation)。具體地,若存在正的常數和函數,使得對任何,都有。則可認爲在足夠大之後,給出了增長速度的一個漸進上界。此時,記之爲:
由這一定義,可導出大記號的以下性質:
- 對於任一常數,有
- 對於任意常數,有
前一性質意味着,在大記號的意義下,函數各項正的常係數可以忽略並等同於1。後一性質則意味着,多項式中的低次項均可忽略,只需保留最高次項。可以看出,大O記號的這些性質的確體現了對函數總體漸進增長趨勢的關注和刻畫。
以大記號形式表示的時間複雜度,實質上是對算法執行時間的一種保守估計,稱作最壞實例或最壞情況。
起泡排序的時間複雜度
bubblesort1A()算法由內、外兩層循環組成。內循環從前向後,依次比較各對相鄰元素,如有必要則將其交換。故在每一輪內循環中,需要掃描和比較n - 1對元素,至多需要交換n - 1對元素。元素的比較和交換,都屬於基本操作,故每一輪內循環至多需要執行2(n - 1)次基本操作。另外,外循環至多執行n - 1輪。因此,總共需要執行的基本操作不會超過次。若以此來度量該算法的時間複雜度,則有
根據大記號的性質,可進一步簡化和整理爲:
複雜度分析
常數時間複雜度
一般地,僅含一次或常數次基本操作的算法均屬此類。此類算法通常不含循環、分支、子程序調用等。
對數時間複雜度
考查如下問題:對於任意非負整數,統計其二進制展開中,數位1的總數。
根據右移運算的性質,每右移一位,n都至少縮減一半(n是輸入的十進制整數)。也就是說,至多經過次循環,n必然縮減至0,從而算法終止。實際上從另一角度來看,恰爲n二進制展開的總位數,每次循環都將其右移一位,總的循環次數自然也應是。
無論是該循環體之前、之內還是之後,均只涉及常數次(邏輯判斷、位與運算、加法、右移等)基本操作。因此,countOnes()算法的執行時間主要由循環的次數決定,亦即:
由大記號定義,在用函數界定漸進複雜度時,常底數的具體取值無所謂,故通常不予專門標出而籠統地記作。
線性時間複雜度
對於輸入的每一單元,此類算法平均消耗常數時間。就大多數問題而言,在對輸入的每一單元均至少訪問一次之前,不可能得出解答。以數組求和爲例,在尚未得知每一元素的具體數值之前,絕不可能確定其總和。
遞歸
以數組求和問題爲例。易見,若n = 0則總和必爲0,這也是最終的平凡情況;否則一般地,總和可理解爲前n - 1個整數之和,再加上末元素。
按這一思路,可基於線性遞歸模式,設計出另一sum()算法如下圖所示。
由此實例,可以看出保證遞歸算法有窮性的基本技巧:
首先判斷並處理n = 0之類的平凡情況,以免因無限遞歸而導致系統溢出。這類平凡情況統稱“遞歸基”(base case of recursion)。平凡情況可能有多種,但至少要有一種(比如此處),且遲早必然會出現。
線性遞歸的模式,往往對應於所謂減而治之(decrease-and-conquer)的算法策略:
遞歸每深入一層,待求解問題的規模都縮減一個常數,直至最終蛻化爲平凡的小(簡單)問題。
按照減而治之策略,此處隨着遞歸的深入,調用參數將單調地線性遞減。因此無論最初輸入的n有多大,遞歸調用的總次數都是有限的,故算法的執行遲早會終止,即滿足有窮性。當抵達遞歸基時,算法將執行非遞歸的計算(這裏是返回0)。
爲保證有窮性,遞歸算法都必須設置遞歸基,且確保總能執行到。爲此,針對每一類可能出現的平凡情況,都需設置對應的遞歸基,故同一算法的遞歸基可能(顯式或隱式地)不止一個。
遞歸算法所消耗的空間量主要取決於遞歸深度
遞歸要保證子問題與原問題在接口形式上的一致
結語
如果您有修改意見或問題,歡迎留言或者通過郵箱和我聯繫。
手打很辛苦,如果我的文章對您有幫助,轉載請註明出處。