資料結構與演算法 java描述 筆記
第一章 演算法及其複雜度
演算法的定義
在特定計算模型下,在資訊處理過程中為了解決某一類問題而設計的一個指令序列。
要素
- 輸入:待處理的資訊,即對具體問題的描述。
- 輸出:經過處理之後得到的資訊,即問題的答案
- 確定性:任一演算法都可以描述為由若干種基本操作組成的序列。
- 可行性:在相應的計算模型中,每一基本操作都可以實現,且能夠在常數時間內完成。
- 有窮性:對於任何輸入,按照演算法,經過有窮次基本操作都可以得到正確的輸出。
演算法效能的分析與評價
問題規模、執行時間及時間複雜度
為了簡化分析,我們通常只考慮輸入規模這一主要因素。
如果將某一演算法為了處理規模為 n 的問題所需的時間記作 T(n),那麼隨著問題規模 n 的增長,執行時間 T(n)將如何增長?我們將 T(n) 稱作演算法的時間複雜度。
漸進複雜度
在評價演算法的執行時間時,我們往往可以忽略其在處理小規模問題時的效能,轉而關注其在處理足夠大規模問題時的效能,即所謂的漸進複雜度(Asmpototic complexity)。
大 O 記號
如果存 在正常數 a、N 和一個函式 f(n),使得對於任何 n > N,都有
T(n) < a × f(n)
我們就可以認為在 n 足夠大之後,f(n)給出了 T(n)的一個上界。
對於這種情況,我們記之為 T(n) = O(f(n)) 這裡的 O 稱作“大 O 記號(Big-O notation)”。
大Ω記號
如果存在正常數 a、N 和一個函式 g(n),使得對於任何 n > N,都有
T(n) > a × g(n)
我們就可以認為在 n 足夠大之後,g(n)給出了 T(n)的一個下界。
對於這種情況,我們記之為 T(n) = Ω(g(n)) 這裡的Ω稱作“大Ω記號(Big-Ω notation)”。
Θ記號
如果存在正常數 a N,都有
a × h(n) < T(n) < b × h(n)
我們就可以認為在 n 足夠大之後,h(n)給出了 T(n)的一個確界。
對於這種情況,我們記之為 T(n) = Θ(h(n)) Θ記號是對演算法執行效率的一種準確估計⎯⎯對於規模為 n 的任意輸入,演算法的執行時間都與 Θ(h(n))同階。
空間複雜度
演算法所需使用的儲存空間量,即演算法空間複雜度。
就漸進複雜度的意義而言,在任何一個演算法的任何一次執行過程中,其實際佔用的存 儲空間都不會多於其間執行的基本操作次數。
引入時間複雜度的各種記號來度量演算法的空間複雜度。
演算法複雜度及其分析
O(1)⎯⎯取非極端元素
問題:給定整數子集S, +∞ > |S| = n ≥ 3,從中找出一個元素 a∈S,使得 a ≠ max(S)且 a ≠ min(S)。也就是說,在最大、最小者之外,取出任意一個數。
演算法:NonextremeElement(S[], n)
輸入:由n個整數構成的集合S
輸出:其中的任一非極端元素
{
任取的三個元素x, y, z ∈ S; //既然S是集合,這三個元素必互異
通過比較,找出其中的最小者min{x, y, z}和最大者max{x, y, z};
輸出最小、最大者之外的那個元素;
}
思路:
S 是有限集,故其中的最大、最小元素各有且僅有一個。
因此,無論 S 的規 模有多大,在前三個元素 S[0]、S[1]和 S[2]中,必包含至少一個非極端元素。
我們可以取 x = S[0]、y = S[1]和 z = S[ 2],這隻需執行三次基本操作,耗費 O(3)時間。
為了確定這三個元 素的大小次序,我們最多需要做三次比較(請讀者自己給出證明),也是 O(3)時間。
最後,輸出居中 的那個元素只需 O(1)時間。
執行時間為: T(n) = O(3) + O(3) + O(1) = O(7) = O(1)
O(logn)⎯⎯進位制轉換
問題:給定任一十進位制整數,將其轉換為三進製表示。比如
23(10) = 212(3)
101(10) = 10202(3)
演算法:BaseConversion(n)
輸入:十進位制整數n
輸出:n的三進製表示
{
不斷迴圈,直到n = 0 {
輸出 n % 3; //取模
令 n = n/3; //整除
}
}
以 101(10)為例思路:
第一輪迴圈,輸出 101 mod 3 = 2,n = 100/3 = 33; 2
第二輪迴圈,輸出 33 mod 3 = 0,n = 33/3 = 11; 0
第三輪迴圈,輸出 11 mod 3 = 2,n = 11/3 = 3; 2
第四輪迴圈,輸出 3 mod 3 = 0,n = 3/3 = 1; 0
第五輪迴圈,輸出 1 mod 3 = 1,n = 1/3 = 0。 1
result=10202(3)
該演算法由若干次迴圈構成, 每一輪迴圈內部,都只需進行兩次基本操作(取模、整除)。
每經過一輪迴圈,n都至少減少至 1/3。於是,至多經過
次迴圈,即 可減小至 0。
因此,該演算法需要執行 O(2×(1+[log3n])) = O(log3n)時間。
鑑於大 O 記號的性質,我們通常會忽略對數函式的常底數。比如這裡的底數為常數 3,故通常 將上述複雜度記作 O(logn)。
O(n)⎯⎯陣列求和
問題:給定n個整數,計算它們的總和。
演算法:Sum(A[], n)
輸入:由n個整陣列成的陣列A[]
輸出:A[]中所有元素的總和
{
令s = 0;
對於每一個A[i],i = 0, 1, …, n-1
令s = s + A[i];
輸出s;
}
思路
對s的初始化需要O(1)時間。
每一輪迴圈中只需進行一次累 加運算,這屬於基本操作,可以在O(1)時間內完成。
O(1) + O(1)×n = O(n+1) = O(n)
O(n\(^2\) )⎯⎯起泡排序
問題:氣泡排序
演算法:Bubblesort(S[], n)
輸入:n個元素組成的一個序列S[],每個元素由[0..n-1]之間的下標確定,元素之間可以比較大小
輸出:重新調整S[]中元素的次序,使得它們按照非降次序排列
{
從S[0]和S[1]開始,依次檢查每一對相鄰的元素;
只要它們位置顛倒,則交換其位置;
反覆執行上述操作,直到每一對相鄰元素的次序都符合要求;
}
思路:
為了對n個整數排序,該演算法的外迴圈最多需要做n輪。
經過第i輪迴圈,元素 S[n-i-1]必然就位,i = 0, 1, …, n-1。\(r\)
在第i輪外迴圈中,內迴圈需要做n-i-1 輪。
在每一輪內迴圈中, 需要做一次比較操作,另外至多需要做三次賦值操作,這些都屬於基本操作,可以在O(4)的時間內 完成。
鑑於大 O 記號的特性,低次項可以忽略,常係數可以簡化為 1,故再次得到 T(n) = O(n^2 )
O(2\(^r\) )⎯⎯冪函式
問題:慮冪函式的計算
演算法:PowerBruteForce(r)
輸入:非負整數r
輸出:冪2^r
{
power = 1;
while (0 < r--)
power = power * 2;
return power;
}
共需要做r次迭代,每次迭代只涉及常數次基本操作,故總共需要執行O(r)時間。
問題的輸入規模為n,故有O(r) = O(2\(^n\) )。
計算模型
-
可解性
現代意義上的電子計算機所對應的計算模型,就是所謂的圖靈機
-
有效可解
具體來說就是指存在某一演算法,能夠在多項式時間以內解決這一問題。反之,若某問題的任一 演算法都具有不低於指數的複雜度,則不是有效可解的
-
下界
在任何一種特定計算模型下,對於任一可有效解決的問題,任何演算法的時間複雜度都 不可能低於某一範圍,我們稱之為該問題在這一計算模型下的複雜度下界,或簡稱該問題的下界。
遞迴
當某個方法呼叫自己時,我們就稱之為遞迴呼叫(Recursive call )。
線性遞迴
類方法的每個例項只能遞迴地呼叫自己至多一次.
最後一次遞迴呼叫被稱作遞迴的“基底”,簡稱“遞迴基”。
性質:經過有限的時間後,它必須能夠終止。
線性遞迴式演算法都具有如下形式:
- 檢測遞迴基。首先要檢測是否到達遞迴基,也就是最基本、最簡單的情況,在這些平凡情 況下無需做進一步遞迴呼叫。
- 遞迴處理。如果尚未遇到平凡的情況,則執行一次遞迴呼叫。通常,遞迴呼叫有多種可能, 此時需要經過進一步的檢測以判斷具體應按何種方式做遞迴呼叫。
遞迴演算法的複雜度分析
遞迴跟蹤法
一種直觀的、可視的分析方法,就是將遞迴方法的執行過程表示為圖形的形式.方法的每一例項都對應於一個方框,其中註明了該例項呼叫的引數;若方法例項 M 呼叫方法例項 N,則在 M 與 N 對 應的方框之間新增一條有向聯線,指明呼叫與被呼叫的關係。
遞推方程法
對遞迴的模式進行歸納從而匯出關於複雜度函式的遞推方程,遞迴方程的解將給出演算法的複雜度。
二分遞迴
將一個大問題分解為兩個子問題,然後分別通過遞迴呼叫來求解,這種情 況稱作二分遞迴(Binary recursion )。
多分支遞迴
一個問題可能需要分解為不止兩個子問題,此時就要採用多分支遞迴(Multiple recursion)。