《演算法導論》筆記 第2章 演算法入門

2021-09-11 03:41:58 字數 4766 閱讀 4740

第2章 演算法入門

插入排序:對陣列a進行插入排序

insertion-sort(a)

1 for j<--2 to length[a]

2 do key <-- a[j] //<--代表賦值

3 *insert a[j] into the sorted sequence a[1..j-1]

4 i <-- j-1

5 while a[i] > key and i > 0

6 do a[i+1] <-- a[i]

7 i <-- i-1

8 a[i+1] <-- key

迴圈不變式與插入演算法的正確性:

迴圈不變式用來幫助我們理解演算法的正確性,必須證明它的三個性質:

1 初始化:迴圈不變式在第一輪迭代之前是正確的;

2 保持:如果它在某一輪迭代開始前是正確的,那麼在下一輪開始前也應該保持正確;也就是某一輪的迭代是成立的。

3 終止:當迴圈結束時,不變式給了我們乙個有用的性質。它有助於表明演算法是正確的。

對於插入演算法而言,

初始化:在第一輪迭代之前,j = 2,已排好序的元素是a[1…j-1]即a[1],是只有乙個元素的子陣列,所以是已排序的,初始化性質是正確的。

保持:在某一輪迭代時,對某個a[j]元素來說,它先用變數key來儲存它的值,然後key和已排好序的元素從後往前逐一比較,已排好序中的元素只要比key大的都往後移一位,直至找到小於等於key的第乙個值,然後將key插入,在某一輪的迭代中,保持性質也是成立的。

終止:在終止的時候,j=length[a]+1,此時已經退出了迴圈,而此時的已排好的序列a[1…j-1]為a[ 1…length[a] ],這個子陣列就是整個陣列,即整個陣列排序完畢。所以終止性質也成立,說明演算法是正確的。

插入排序演算法的分析:(n=length[a])

insertion-sort ----------------------cost-----------------times

1 for j<–2 to length[a] -------------------c1---------------------n(迴圈頭始終比迴圈體多一次)

2 do key<–a[j] ----------------------------c2---------------------n-1

3 *insert a[j] into the sorted

sequence a[1…j-1]

4 i <-- j-1 -----------------------------------c4---------------------n-1

5 while a[i] > key and i >0 -------------c5---------------σtj (j=2…n)

6 do a[i+1] <-- a[i] -----------------------c6---------------σ(tj - 1) j=2…n

7 i <-- i-1 -----------------------------------c7---------------σ(tj - 1) j=2…n

8 a[i+1] <-- key ---------------------------c8---------------------n-1

總執行時間t(n) = c1n + c2(n-1) + c4(n-1) + c5σti + c6σ(ti-1) +c7(ti-1) + c8(n-1)

如果輸入的陣列是已經排好序時:(不用進入while迴圈,只需n-1次判斷)

t(n) = c1n + c2(n-1) + c4(n-1) + c5(n-1) + c8(n-1)

= (c1 + c2 + c4 + c5 + c8)n - (c2 + c4 + c5 + c8)

設 a = c1 + c2 + c4 + c5 +c8, b = -(c2 + c4 + c5 + c8)

t(n) = an + b,t(n)此時是乙個線性函式。

如果輸入的陣列是逆序時:

a[j]=key與a[1…j-1]各個元素進行比較,迴圈頭判斷 j 次 (始終比迴圈體多一次以退出迴圈),

σtj = σ j (j = 2…n) = (2 + n) (n - 1) / 2

σ(tj-1)= σ( j-1) = (1+n-1)(n-1) / 2 = n(n-1) / 2

t(n) = c1n + c2(n-1) + c4(n-1) + c5(2 + n)(n - 1) / 2 + c6(n - 1)n /2 + c7(n - 1)n / 2 + c8(n - 1)

= (c5/2 + c6/2 + c7/2)n^2 + (c1 + c2 + c5/2 - c6/2 - c7/2 + c8)n -(c2 + c4 + c5 + c8)

設 a = c5/2 + c6/2 + c7/2, b = c1 + c2 + c5/2 - c6/2 - c7/2 +c8, c = -(c2 + c4 + c5 + c8)

t(n) = an^2 + bn + c,t(n)此時是關於n的二次函式。

演算法設計:

插入排序使用的是增量法,即不斷將元素插入到子陣列[1…j-1]中,不斷增加子陣列的長度,直至

子陣列等於整個陣列的大小。

分治法:

將原問題劃分成n個規模較小而結構與原問題相似的子問題,遞迴地解決這些子問題,然後再合併其結果,就得到原問題的解。分治模式在每一層遞迴都有三個步驟:

1.分解:將原問題分解成一系列的子問題;

2.解決:遞迴地解決各個子問題,若子問題足夠小,則直接求解;

3.合併:將子問題的結果合併成原問題的解。

合併排序完全依照分治模式:

1.分解:將n個元素分成2個各含n/2個元素的子串行;

2.解決:用合併排序法對兩個子串行遞迴地排序,只有乙個元素時,則視為有序(即子問題足夠小,直接求解)

3.合併:合併兩個已排序的子串行以得到排好序的陣列

對於將原問題分解,分解不止分解一次,分解到子問題足夠小可以直接解決為止。合併排序最關鍵的步驟時合併,假設子陣列a[p…q]和a[q+1…r]都排好序,將它們合併成乙個已排好序的子陣列a[p…r]。

為了避免檢查每個子陣列為空,所以在每乙個要合併的子陣列後加乙個」哨兵位「,它包含乙個特殊的值(比如-1)。此處為了簡化**,利用∞來作為哨兵值。

merge(a, p, q, r)

1 n1 <-- q-p+1 //q-p+1是陣列a[p..q]的長度

2 n2 <-- r-q //r-q是陣列a[q+1..r]的長度

3 create arrays l[1..n1+1] and r[1..n2+1]

4 for i <--1 to n1

5 do l[i] <-- a[p+i-1]

6 for j <--1 to n2

7 do r[j] <-- a[q+j]

8 l[n1+1] <-- ∞

9 r[n2+1] <-- ∞

10 i <-- 1

11 j <-- 1

12 for k <--p to r

13 do if l[i] <= r[j]

14 then a[k] <-- l[i]

15 i <-- i+1

16 else a[k] <-- r[j]

17 j <-- j+1

迴圈不變式與排序演算法的正確性:

迴圈不變式是上述的12-17行,我們要證明它的三個性質

1.初始化:在for迴圈的第一輪迭代開始之前,k=p,子陣列a[p…k-1]為空,即k-1-p+1=k-p=0,

l[i]和r[j]的元素都在各自的陣列中。

2.保持:在某一輪的迭代中,假設l[i]<=r[j],將較小的元素l[i]複製到a中,此時子陣列由a[p…k-1]變成了a[p…k],元素個數從k-p變成k-p+1。增加k和i(i=i+1)的值,會為下一輪重新建立迴圈不變式的值。如果這次由l[i] > r[j],則16-17行就會執行適當的操作,以使迴圈不變式保持成立。

3.終止:結束迴圈時,k=r+1,子陣列a[p…k-1]為a[p…r],包含p-r+1個在l[i]或r[j]中的元素,並且排好序。而a[p…r]即為原陣列,說明已經將原陣列排好序了。

現在,可以將merge過程作為合併排序中的乙個子程式來使用:

merge-sort(a, p, r)

1 if p < r

2 then q<--⌊(p+r)/2⌋

3 merge-sort(a, p, q)

4 merge-sort(a, q+1, r)

5 merge(a, p, q, r)

分治法分析:

設t(n)為乙個規模為n的問題的執行時間。如果問題足夠小,如n<=c(c為乙個常量),則得到它的直接解的時間為θ(1)。假設我們把問題分解成a個子問題,每乙個大小是原問題的1/b。(在合併排序中,a=b=2,但在許多分治法中,a≠b),如果分解問題和合併問題的時間個為d(n)和c(n),則遞迴式為:t(n) =

由於d(n) = θ(1),c(n) = θ(n),所以d(n) + c(n) = θ(n)

所以上述的遞迴式可以寫為:

t(n) =

實際上,合併排序最壞情況的執行時間t(n)= θ(nlgn)。

t(n) = ,其中常量c代表規模為1的問題所需的時間,也是在「解決」和「合併」步驟中處理每個陣列元素所需的時間。運用遞迴樹方法求遞迴式,可以知道樹的層數有lgn+1(n為每層的結點數),每一層的代價為cn,所以整棵樹的代價是cn(lgn+1) = cnlgn + cn,忽略低階項和常量c,即得到結果 θ(nlgn)。

《演算法導論》筆記 第2章

本章出現了全書第乙個演算法 插入排序。插入排序並不是最直觀的排序演算法,拿它做第乙個講解應該有其他的理由。通過插入排序的講解,偽 約定 迴圈不變式 演算法分析等最基礎的知識被帶了出來。此後又講了第二個演算法 合併排序,並引出了演算法設計中的兩種常見型別 增量法 incremental 和分治法 di...

《演算法導論》第2章 演算法基礎 個人筆記

insertion sort a for j 2 to a.length key a j i j 1 while i 0 anda i key a i 1 a i i a i 1 key 在insertion sort中,若輸入陣列已排好序,則出現最佳情況,t n n 若輸入陣列已反向排序,則導致最...

演算法導論第2章 演算法基礎

2.1 插入排序 includeusing namespace std void insertion sort int a,int n 宣告 void print int a,int n void insertion sort int a,int n a i 1 key void print int...