算法分析-分治 归并排序,递归插入排序,二分查找
反正分治的套路就是 相同子問題,遞歸做,我之前有介紹express源碼,其中的中間件使用就是用next()函數一直遞歸,想看的看我的express源碼分析:
分治3步驟:
下面給出歸并排序的js代碼:
1 var A = [5, 2, 4, 6, 1, 3]; 2 var len = A.length - 1; 3 4 MERGE_SORT(A, 0, len); 5 6 A.forEach(function (element, index, arr) { 7 console.log(index, "-----------", element); 8 }); 9 10 function MERGE_SORT(A, start, end) { 11 if (start < end) { 12 var middle = Math.floor((start + end) / 2); //向下去整 13 MERGE_SORT(A, start, middle); //左邊遞歸 14 MERGE_SORT(A, middle + 1, end); //右邊遞歸 15 MERGE(A, start, middle, end); 16 } 17 } 18 19 function MERGE(A, start, middle, end) { 20 var Arr1 = A.slice(start, middle + 1), 21 Arr2 = A.slice(middle + 1, end + 1),//slice(start,end) end不包括,所以加1 22 len1 = Arr1.length, 23 len2 = Arr2.length, 24 i = 0, 25 j = 0; 26 for (i, j; i < len1 && j < len2;) { 27 (Arr1[i] < Arr2[j]) ? (A[start++] = Arr1[i++]) : (A[start++] = Arr2[j++]); 28 } 29 30 while (i == len1 && j < len2) { 31 32 A[start++] = Arr2[j++]; 33 } 34 while (j == len2 && i < len1) { 35 A[start++] = Arr1[i++]; 36 } 37 38 }像這種分治遞歸的,你需要找到一個出口來結束遞歸。
?
還記得插入排序嗎,我們換成遞歸的寫法,怎么寫呢,思路是這樣的,將A[n],插入A[0...n-1]內,而A[0..n-1]是已經排序好的。上代碼:
1 var A = [5, 2, 4, 6, 1, 3]; 2 3 RECURSIVE_INSERT_SORT(A, 6); 4 A.forEach(function (element, index, arr) { 5 console.log(index, "-----------", element); 6 }); 7 8 function RECURSIVE_INSERT_SORT(A, len) { 9 if (len > 1) { 10 var last_var = A[len - 1]; 11 A.splice(--len, 1); 12 RECURSIVE_INSERT_SORT(A, len); 13 INSERT(A, last_var); 14 } 15 } 16 17 function INSERT(A, last_var) { 18 var len = A.length, 19 k = len - 1; 20 while (A[k] > last_var && k >= 0) { 21 22 A[k + 1] = A[k]; 23 k--; 24 } 25 A[k + 1] = last_var; 26 27 }?
那思考一下,如果A=[1,2,3,5,6,7]是有序的,用二分查找去查找v=3,v=4,怎么寫,有思路嗎?
1 var result = {index: false}, 2 A = [1, 2, 3, 5, 6, 7]; 3 function BINARY_SEARCH(A, start, end, val) { 4 if (end >= start) { 5 var middle = Math.floor((start + end) / 2); 6 if (A[middle] == val) { 7 result.index = middle; 8 console.log("找到了~"); 9 return; 10 } 11 if (A[middle] < val) { 12 13 BINARY_SEARCH(A, middle + 1, end, val); 14 } 15 if (A[middle] > val) { 16 BINARY_SEARCH(A, start, middle - 1, val); 17 } 18 } 19 } 20 21 BINARY_SEARCH(A, 0, 5, 7); 22 console.log(result.index);?
既然我們已經學會了二分查找策略,我們能不能將插入排序再改一改,將while循環改成二分查找策略呢?
?
1 var A = [99, 12, 77, 103, 1000, 3, 11, 4324, 3, 321, 545, 65, 76765, 78, 889, 98, 324, 23, 4, 544, 6, 2]; 2 3 BINARY_INSERT_SROT(A); 4 A.forEach(function (element, index, arr) { 5 console.log(index, "-----------", element); 6 }); 7 8 function BINARY_INSERT_SROT(A) { 9 var len = A.length; 10 for (var i = 1; i < len; i++) { 11 var key = A[i]; 12 var j = i - 1; 13 14 /* 15 * 16 * A是要修改的數組對象 17 * 0表示已經排號序列的start下標, 18 * j表示已經排號序列的end下標, 19 * key 表示需要插入的值,也就是第五個參數need_insert_index下標對應的值 20 * need_insert_index 就是我們要插入的key值的下標。也就是j+1; 21 * 22 * */ 23 BINARY_SEARCH(A, 0, j, key, j + 1); //這里替換掉while(key<A[j]&&j>=0) 24 } 25 } 26 function BINARY_SEARCH(A, start, end, val, need_insert_index) { 27 28 /* 29 * 30 * 我們知道二分查找是查找中間下標對于的值是否為所求,是就找到,不是,就對半再遞歸查找。 31 * 32 * 這一步在我們這個程序里還是需要的,畢竟可能會直接找到相同的,找到后,我們默認將值插在后面, 33 * 34 * 這里有個關鍵步驟,也就是第5個參數的用處,我們是通過對比查找的。我們如果往后移動,也一定要在 35 * 36 * 這個middle下標到need_insert_index下標之間全部移動,不然可能只移動了一部分。 37 * 38 * 當start==end的時候,或者start + 1 ==end的時候,middle都等于stsrt,這時候,其實都是比較一個數, 39 * 40 * 下標的移動和middle這個找到的特殊情況一樣,當找不到的時候根據條件去移動。 41 * 42 * */ 43 44 45 if (end >= start) { 46 var middle = Math.floor((start + end) / 2); 47 if (A[middle] == val) { 48 var j = middle; 49 while (need_insert_index > middle) { 50 A[need_insert_index] = A[need_insert_index - 1]; 51 need_insert_index--; 52 } 53 A[middle + 1] = val; //默認是插后的 54 return; 55 } 56 if (middle == start) { 57 if (A[start] <= val && A[end] >= val) { 58 while (need_insert_index > start) { 59 A[need_insert_index] = A[need_insert_index - 1]; 60 need_insert_index--; 61 } 62 A[start + 1] = val; 63 64 } else if (A[start] <= val && A[end] <= val) { 65 while (need_insert_index > end) { 66 A[need_insert_index] = A[need_insert_index - 1]; 67 need_insert_index--; 68 } 69 A[end + 1] = val; 70 } else { 71 while (need_insert_index > start) { 72 A[need_insert_index] = A[need_insert_index - 1]; 73 need_insert_index--; 74 } 75 A[start] = val; 76 } 77 return 78 } 79 (A[middle] > val) ? (BINARY_SEARCH(A, start, middle - 1, val, need_insert_index)) : (BINARY_SEARCH(A, middle + 1, end, val, need_insert_index)); 80 } 81 }?思考:請給出一個運行時間為O(nlgn)的算法,使之能在給定一個由n個整數構成的集合S和另一個整數x時,判斷出S中是否存在有兩個其和等于x的元素。
分析:
?????? 若要整個算法的時間復雜度為O(nlgn),那么只要算法中最復雜的模塊的復雜度為O(nlgn)就可以了。
1 var A = [99, 12, 77, 103, 1000, 3, 11, 4324, 3, 321, 545, 65, 76765, 78, 889, 98, 324, 23, 4, 544, 6, 2], 2 result = {index: false}; 3 var isfind = isExisted(A, 89); 4 console.log(isfind); 5 6 function isExisted(A, val) { 7 var len = A.length; 8 //先給集合排序 9 MERGE_SORT(A, 0, len - 1); 10 11 //循環次數最多為n 12 for (var i = 0; i < len; i++) { 13 //每次次二分搜索,時間消耗lgn 14 BINARY_SEARCH(A, 0, len - 1, val - A[i]); 15 if (result.index) { 16 //下面這個判斷是考慮到了x的值是序列中某個元素的2倍的情況 17 if (result.index != i) { 18 result.index = true; 19 break; 20 }else { 21 result.index = false; 22 } 23 } 24 } 25 return result.index; 26 } 27 28 29 //復制粘貼 30 function BINARY_SEARCH(A, start, end, val) { 31 if (end >= start) { 32 var middle = Math.floor((start + end) / 2); 33 if (A[middle] == val) { 34 result.index = middle; 35 console.log("找到了~"); 36 return; 37 } 38 if (A[middle] < val) { 39 40 BINARY_SEARCH(A, middle + 1, end, val); 41 } 42 if (A[middle] > val) { 43 BINARY_SEARCH(A, start, middle - 1, val); 44 } 45 } 46 } 47 48 //復制粘貼 49 function MERGE_SORT(A, start, end) { 50 if (start < end) { 51 let middle = Math.floor((end + start) / 2); 52 MERGE_SORT(A, start, middle); 53 MERGE_SORT(A, middle + 1, end); 54 MERGE(A, start, middle, end); 55 } 56 } 57 58 //復制粘貼 59 function MERGE(A, start, middle, end) { 60 var arr1 = A.slice(start, middle + 1); 61 var arr2 = A.slice(middle + 1, end + 1); 62 var len1 = arr1.length; 63 var len2 = arr2.length; 64 var i = 0; 65 var j = 0; 66 for (i, j; i < len1 && j < len2;) { 67 (arr1[i] < arr2[j]) ? (A[start++] = arr1[i++]) : (A[start++] = arr2[j++]); 68 } 69 while (i == len1 && j < len2) { 70 A[start++] = arr2[j++]; 71 } 72 while (j == len2 && i < len1) { 73 A[start++] = arr1[i++]; 74 } 75 }總結:
? ? ? ? ? merge_sort這個函數是歸并排序算法,它的時間復雜度是O(nlgn)。
?????????? 第12行處,這個for循環中,有一個二分查找算法。它的時間復雜度是O(lgn),所以整個for循環模塊的時間復雜度為O(nlgn)。
?
霍納規則的正確性:
?
公式的簡單推理:
a0+a1*x+a2*x^2+a3*x^3+a4*x^4+…+ak*x^k+…+an*x^n
?
?
計算機的循環計算。
1 ? ? ?y = 0 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?時間花費 ?1
2 ? ? ?for i=n down to 0 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? n+1
3 ? ? ?? ? ? ? y = ai + x*y ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? n
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 總時間花費 ?2n+2
這樣循環計算出來的y就是上面匯總的值。
?
a)、Θ(n), 推理過程看上面。
b)、偽代碼實現的樸素的多項式求值算法。
?
下面是一個取巧的算法,時間消耗是 3n, 在n >2 時 時間消耗大于 2n+2
void Ploynomial() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?時間消耗 = 3n
{
? ? ? ? int t; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1
? ? ? ??sum = a[0]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1
? ? ? ??for (i = 1; i < n; i++) ? ? ? ? ? ? ? ? ? ? ? ? ? ?n
? ? ? ??{
? ? ? ??? ? ? ??sum += a[i]*x; ? ? ? ? ? ? ? ? ? ? ? ? ? ? n-1
? ? ? ??? ? ? ??x = x*x; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? n-1
? ? ? ??}
}
c)、
初始化: 有 y = 0, i = n , 這樣 計算 下面公式的右邊 為 0 ,?所以初試化滿足循環不變式。?
? ? ? ? ??
保持:假設當第i=j滿足時,考察i=j-1。
終止: 當循環結束時候,有 i= -1,
?
------------
由于0從0到n-(i+1),因此有:
y = Σ ak+i+1 * x^k
? = ak+i+1 + ak+i+2 * x + ... + an * x^(n-(i+1))
霍納規則代碼段循環不變式證明如下:
初始:
?? ?i=n,y[n] = 0,迭代開始時,循環后有y[n] = a[n]。
保持:
?? ?對于任意 0 ≤ i ≤ n,循環后有:
?? ??? ?y[i] = a[i] + y[i+1] * x = a[i] + (a[i+1] * x + a[i+2] * x + ... + a[n] * x^(n-(i+1))) * x
?? ??? ??? ? = a[i] + a[i+1] * x + a[i+2] * x^2 + ... + a[n] * x^(n-i)
終止:
?? ?i小于0時終止,此時有 y[0] = a[0] + a[1] * x + a[2] * x^2 + a[n] * x^n
證明和y = Σ a[k+i+1] * x^k的關系:
?? ?k 從0到n-(i+1),等價于 0 ≤ k ≤ n-(i+1)。因此
?? ??? ?y = Σ a[k+i+1] * x^k
?? ??? ??? ?= a[i+1] + a[i+2] * x + ... + a[n-(i+1)+i+1] * x^(n-i)
?? ??? ??? ?= a[i+1] + a[i+2] * x + ... + a[n] * x^(n-i)
?? ?由于i+1循環之后和i循環之前的值相等,用y'[i]表示i循環之前的值,則有:
?? ??? ?y'[i] = y[i+1]
?? ?霍納規則循環不變式的結果表明:
?? ??? ?y[i] = a[i] + a[i+1] * x + a[i+2] * x^2 + ... + a[n] * x^(n-i)
?? ?因此有:
?? ??? ?y'[i] = y[i+1] = a[i+1] + a[i+2] * x + ... + a[n] * x^(n-(i+1))
?? ?令k=n-(i+1),則n=k+i+1,所以:
?? ??? ?y'[i] = a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^(k+i+1-(i+1))
?? ??? ??? ??? ?= a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^k
?? ?用y表示y'[i],則有:
?? ??? ?y = a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^k
?? ??? ??? ?= Σ a[k+i+1] * x^k
?? ?其中 k從0到n-(i+1)
?? ?證畢。
?
思考題:逆序對
設A[1..n]是一個包含n個不同數的數組。如果i<j且A[i]>A[j],則(i,j)就稱為A中的一個逆序對(inversion)。
a)列出數組〈2,3,8,6,1〉的5個逆序。
b)如果數組的元素取自集合{1, 2, ..., n},那么,怎樣的數組含有最多的逆序對?它包含多少個逆序對?
c)插入排序的運行時間與輸入數組中逆序對的數量之間有怎樣的關系?說明你的理由。
d)給出一個算法,它能用Θ(nlgn)的最壞情況運行時間,確定n個元素的任何排列中逆序對的數目。(提示:修改合并排序)
a) ?(2,1) ?(3,1) (8,1) (6,1),(8,6)
b)?數組從大到小有序排列時,逆序對最多,為n(n-1)/2個。
c)?逆序對增加時,插入排序時間增加。
沒有逆序對時,插入排序時間最少,為Θ(n)。
逆序對最多時,插入排序時間最多,為Θ(n^2)。
d) ?歸并算法, 每次移動牌,次數加1, 合計的次數就是逆序對的個數。
給出修改后的程序:
1 var num = 0,A = [5,2,4,6,1,3]; 2 MERGE_SORT(A, 0, A.length - 1); 3 console.log("最終結果是:",num); 4 5 function MERGE_SORT(A, start, end) { 6 if (start < end) { 7 let middle = Math.floor((end + start) / 2); 8 MERGE_SORT(A, start, middle); 9 MERGE_SORT(A, middle + 1, end); 10 MERGE(A, start, middle, end); 11 } 12 } 13 14 function MERGE(A, start, middle, end) { 15 var arr1 = A.slice(start, middle + 1); 16 var arr2 = A.slice(middle + 1, end + 1); 17 var len1 = arr1.length; 18 var len2 = arr2.length; 19 var i = 0; 20 var j = 0; 21 for (i, j; i < len1 && j < len2;) { 22 /* (arr1[i] < arr2[j]) ? (A[start++] = arr1[i++]) : (A[start++] = arr2[j++]);*/ 23 if (arr1[i] > arr2[j]) { 24 num += (len1 - i); 25 A[start++] = arr2[j++]; 26 27 } else { 28 A[start++] = arr1[i++] 29 } 30 } 31 while (i == len1 && j < len2) { 32 A[start++] = arr2[j++]; 33 34 } 35 while (j == len2 && i < len1) { 36 A[start++] = arr1[i++]; 37 38 } 39 console.log(A, "對應的num數目:",num); 40 41 }分析:
? ? 我們使用歸并排序,其實遞歸執行的時候 ,是從左往右的,大家可以畫圖:
? ? ? ? ? ? ? ? ? ? ? ? ? ?[5,2,4,6,1,3]
? ? ? ? ? ? ? ? ? ? ? ?[5,2,4] ? ? ? ?[6,1,3]
? ? ? ? ? ? ? ? ??[5,2] ? [4] ? ? ?[6,1] ?[3]
? ? ? ? ? ? ? [5] [2] ? ? ? ? ? ? [6] ?[1]?
? ? ?我們發現,當歸并[5] [2] 的時候,因為左邊大于右邊,所以數目加1,歸并后變成[2,5],[2,5]和[4]歸并,因為5大于4,數目加1變為2,
? ?歸并后為[2,4,5],同理分析右邊,最后歸并[2,4,5] [1,3,6] 這里是關鍵,也就是為什么代碼中是綠色的部分,因為2大于1,所以2后面的所有都大于1
?4大于3,4后面的全大于3.
?
轉載于:https://www.cnblogs.com/huenchao/p/5900802.html
總結
以上是生活随笔為你收集整理的算法分析-分治 归并排序,递归插入排序,二分查找的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AngularJs 基础教程 —— 控制
- 下一篇: Tuxera NTFS for Mac中