算法分析
算法分析是關于計算機程序性能和資源利用的理論研究;性能研究主要是學習如何讓算法或者應用程序 運行的更快; 資源利用主要指的是諸如通信、存儲器(無論是RAM Memory還是disk Memory)等的使用情況。
算法是關注性能問題的學科,因此這部分內容很重要。
設想,如果你在一家公司從事軟件開發(fā)工程的從事編程工作,有那些比性能更重要的東西?
- 代碼簡潔
- 可維護性
- 開發(fā)的時間成本
- 崩潰率
- 安全性
- 用戶友好
佷明顯,真實的軟件開發(fā)場景中有諸多的方面都比性能重要。但是算法是關注性能的科學,如果算法和性能都不重要,我們?yōu)槭裁催€要學習這樣一個課程。
性能的好壞可以決定解決方案可行性。
算法是一種描述程序行為的語言,業(yè)已廣泛應用于計算機科學領域,且已經被所有的實踐者所采用的理論語言,它是思考程序最為簡潔的一種方式。
性能為什么處于程序開發(fā)中的最底層。這里有一個很好的類比,性能在程序中扮演的角色就如同經濟中的貨幣一樣,貨幣可以購買人基本生活中所需的一切東西,如,食物,衣服,房子和水。可能水對你的生命而言比錢重要,但是你能買下這些商品的前提是有錢。同樣,性能是確保良好用戶體驗的前提。追求運行速度,是算法研究最讓人興奮的地方。
排序問題
通過排序問題,進行算法分析。
插入排序
插入排序的偽代碼如下:
INSERTION-SORT(A)
1 for j=2 to A.length
2 key=A[j]
3 //將A[j]插入到有序的子數組A[1..j-1]。
4 i=j-1
5 while i>0 and A[i]>key
6 A[i+1]=A[i]
7 i=i-1
8 A[i+1]=key
Java 版本代碼實現如下:
void insertSort(int[] arr){
if(arr==null||arr.length<2) return;
for(int i=1;i<arr.length;i++){
int key=arr[i];
int j=i-1;
while (j>=0 && arr[j]>key){
arr[j+1]=arr[j];
j--;
}
arr[j+1]=key;
}
}
運行時間分析
運行時間依賴于下面的因素
- 輸入項
- 輸入項的規(guī)模
分析運行時間的方式
- 最壞情況-Worst-case(usually)
T(n) = max time on any input of size n - 平均情況-Average-Case(somtimes)
T(n) =excepted time all input size n
前提是,需要一個有關輸入統(tǒng)計分布的假設
每種輸入的運行時間乘以該輸入出現的概率進行加權平均所得到的時間,就是期望運行時間 - 最好情況Best_Case(假象)
假設,用已經做好排序的序列檢驗"低速"算法,得到的運行時間可以的到最好情況。
最壞情況的運行時間分析:
- 依賴使用的計算機、
相對運行時間(在相同計算機上運行)
絕對運行時間(在不同計算機上運行)
漸進分析(Asymptotic Analysis)
- 忽略那些依賴于機器的常量
- 當n趨近于∞過程中,忽略機器實際的運行時間,而是T(n)的增長
漸進符號
- Θ 標記
漸進緊確界
舍棄低階項,并忽略前面的常數因子
例如,如果公式為3n3+90n2-5n+6046=Θ(n3)
當n->∞時Θ(n2)的算法總是能戰(zhàn)勝一個Θ(n3)的算法,其他項不會動搖這個結果,假設在一臺性能好的機器上運行Θ(n3)的算法,在一臺性能差的機器上運行Θ(n2)的算法,只要n足夠大,Θ(n2)的算法總是能戰(zhàn)勝一個Θ(n3)的算法,這個結論依然成立,這就是漸進符號的偉大之處(它能一舉滿足我們對相對和絕對速度的雙重比較要求)。
從工程視角來看有時臨界值n0的過大,大到計算機無法運行,這就是我們?yōu)槭裁磿Φ退俚乃惴^興趣的原因,有一些算法盡管用漸進的觀點來看,他們有可能比較慢,但是在合理規(guī)模輸入的情況下運行的更快,所以我們要從數學的理解和工程的直覺之間尋找平衡,這樣我們才能寫出更好的程序。僅僅會做算法分析,并不能是你成為一個編程高手。
老司機講段子,“如果你想成為編程高手,你可以在兩年中每天編程;如果你想成為世界級的編程高手,你可以每天編程,然后上一門算法課”。
插入排序的算法分析
內存引用計數。
最壞情況:T(n)=
O 標記
Θ 標記漸進的給出了一個函數的上界和下界,當只有一個漸進上界時,使用O 記號。Ω 標記
正如O標記提供了一個函數的漸近上界,Ω 記號提供了漸近下界。
算法設計
我們可以選擇使用的算法設計技術有很多。插入排序使用了增量方法:在排序子數組A[1..j-1]后,將單個元素A[j] 插入子數組的適當位置,產生排序好的子數組A[1..j]。
下面考查一種“分治法”的設計方法,我們將用分治法來設計一種排序算法,該算法的最壞情況的運行時間比插入排序要少得多。分鐘算法的優(yōu)點之一是,通過算法分析技術很容易確定其運行時間。
分治法
早在中國漢代就有使用。中國歷史版本"分治法"之推恩令。
推恩令是漢武帝為了鞏固中央集權而頒布的一項重要政令。這項政令要求諸侯王將自己的封地分給自己的子弟。后來根據這項政令,諸侯國被越分越小,漢武帝再趁機削弱其勢力。
許多算法結構上的遞歸的:為了解決一個給定的問題,算法一次或多次的遞歸調用其自身以解決相關的若干子問題。將原問題分解為幾個規(guī)模較小但是類似原問題的子問題,遞歸地求解這些子問題,然后再合并這些子問題的解來建立原問題的解。
分治模式在每層遞歸時的三個步驟:
- 分解
- 解決
- 合并
歸并排序
- 分解
分解待排序的n個元素的序列成n/2個元素的兩個子序列。 - 解決
使用歸并排序遞歸的排序兩個子序列。 - 合并
合并兩個已經排序的的子序列以產生一排序的答案。
插入排序的偽代碼如下:
MERGE-SORT(A)
1 for j=2 to A.length
2 key=A[j]
3 //將A[j]插入到有序的子數組A[1..j-1]。
4 i=j-1
5 while i>0 and A[i]>key
6 A[i+1]=A[i]
7 i=i-1
8 A[i+1]=key
Java 版本代碼實現如下:
/**
* 前提[l,mid]有序并且(mid,r]有序 目標:通過
*
* @param src
* i:{6,9,10,2,8,11} out:[2,6,8,9,10,11]
* @param tmp
* 臨時存放順序的數組
* @param left
* @param mid
* @param right
*/
public static void mergeArray(int[] src, int[] tmp, int left, int mid,
int right) {
int tmpIndex = left;
int leftStart = left;
int rightStart = mid + 1;
while (tmpIndex <= right) {// 臨時數組為填滿表明合并未完成
if (leftStart <= mid && rightStart <= right) {// 這種情況是兩個subarray都未合并結束
tmp[tmpIndex++] = src[leftStart] < src[rightStart] ? src[leftStart++]
: src[rightStart++];// 條件成立者賦值給臨時數組后索引右移(+1)
} else if (leftStart <= mid) {// 這種情況證明右側的subArray合并結束
tmp[tmpIndex++] = src[leftStart++];
} else if (rightStart <= right) {// 這種情況表明左側的subArray合并結束
tmp[tmpIndex++] = src[rightStart++];
}
}// 臨時數組保存了 [left,right]的合并結果
System.arraycopy(tmp, left, src, left, right - left + 1);
}
/**
* 二路歸并實現
* @param src
* @param tmp
* @param left
* @param right
*/
public static void mergeSort(int[] src, int[] tmp, int left, int right) {
if (left >= right)
return;
int mid = (right + left) / 2;
mergeSort(src, tmp, left, mid);
mergeSort(src, tmp, mid + 1, right);
mergeArray(src, tmp, left, mid, right);
}
歸并排序的時間復雜度為 Θ(nlgn),隨著規(guī)模n的增大,歸并排序的運行時間達到了漸進最優(yōu),但是歸并排序的一個缺點是需要開辟一個同等長度的內存空間才能正常運行。
以上,謝謝閱讀,希望你有所收獲!
算法導論公開課筆記(一)算法分析與設計
算法導論公開課筆記(二)快速排序和隨機化算法
算法導論公開課筆記(三)線性時間排序
算法導論公開課筆記(四)順序統(tǒng)計、中值
動態(tài)規(guī)劃算法