国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

Javascript 高性能之遞歸,迭代,查表法詳解及實例

2019-11-19 18:04:59
字體:
來源:轉載
供稿:網友

Javascript 高性能之遞歸,迭代,查表法詳解

遞歸

概念:函數通過直接調用自身,或者兩個函數之間的互相調用,來達到一定的目的,比如排序,階乘等

簡單的遞歸

階乘

function factorial(n) {  if (n == 0) {    return 1;  } else {    return n * factorial(n - 1);  }}

遞歸實現排序

/*  排序且合并數組 */function myMerge(left, right) {  // 保存最后結果的數組  var res = [];  // 有一個數組結束了就結束排序,一般情況下,兩個數組長度應該保持一樣  while (left.length > 0 && right.length > 0) {    if ( left[0] < right[0] ) {      // 把left第一個成員刪除,存儲到新數組res中      res.push(left.shift());    } else {      res.push(right.shift());    }  }  // 如果還有剩余直接合并到新數組  return res.concat(left).concat(right);}/*  遞歸方式 */function recursion(items) {  if (items.length == 1) {    return items;  }  var middle = Math.floor(items.length / 2),    left = items.slice(0, middle), // 取數組前半部分    right = items.slice(middle);  // 取數組后半部分  // 遞歸排序  return myMerge(recursion(left), recursion(right));}

迭代

每個瀏覽器對遞歸都有調用棧上限的問題,且如果不小心使用也很容易造成死循環導致程序崩潰。如果考慮到性能問題,可以使用迭代來代替遞歸,因為運行循環總比反復調用函數的開銷少很多

/*  迭代方式,不使用遞歸,可以避免出現棧溢出問題 */function iteration(items) {  if (items.length == 1) {    return items;  }  var work = [];  // 將items成員全部轉化成數組,保存到數組中  for (var i = 0, len = items.length; i < len; i++) {    work.push([items[i]]);  }  work.push([]); // 數組長度為奇數時  // 迭代  for (var lim = len; lim > 1; lim = (lim + 1) / 2) {    for (var j = 0, k = 0; k < lim; j++, k+=2) {      work[j] = myMerge(work[k], work[k + 1]);    }    work[j] = [];  // 數組長度為奇數時  }  return work[0];}/* 迭代過程分析  假設: var test = [1, 3, 9, 7, 4, 8, 6, 5, 0, 2]; // len == 10  work = [[1], [3], [9], [7], [4], [8], [6], [5], [0], [2], []]; // len == 11;  // 迭代(二分法)  a) lim: 11 > 1    1) k = 0, work[0] = myMerge([1], [3]) ==> work[0] = [1, 3]    2) k = 2, work[1] = myMerge([9], [7]) ==> work[1] = [7, 9]    3) k = 4, work[2] = myMerge([4], [8]) ==> work[2] = [4, 8]    4) k = 6, work[3] = myMerge([6], [5]) ==> work[3] = [5, 6]    5) k = 8, work[4] = myMerge([0], [2]) ==> work[4] = [0, 2]    > 在后面添加個空數組是為了數組長度為奇數時的情況,能有個對象做比較,否則會出現越界錯誤  b) lim: 6 > 1, work = [[1,3], [7,9], [4,8], [5,6], [0,2], []];    1) k = 0, work[0] = myMerge([1,3], [7,9]) ==> work[0] = [1, 3, 7, 9]    2) k = 2, work[1] = myMerge([4,8], [5,6]) ==> work[1] = [4, 5, 6, 8]    3) k = 4, work[2] = myMerge([0,2], [])  ==> work[2] = [0,2]    > 最后一個[]會被myMerge函數給合并,所以不用擔心添加的空數組對后續產生影響  c) lim: 3 > 1, work = [[1, 3, 7, 9], [4, 5, 6, 8], [0, 2], []];    1) k = 0, work[0] = myMerge([1, 3, 7, 9], [4, 5, 6, 8]) ==> work[0] = [1,3,4,5,6,7,8,9]    2) k = 2, work[1] = myMerge([0, 2], []) ==> work[1] = [0, 2]  d) lim: 2, work = [[1,3,4,5,6,7,8,9], [0,2], []];    1) k = 0, work[0] = myMerge([1,3,4,5,6,7,8,9], [0,2]) ==> work[0] = [0,1,2,3,4,5,6,7,8,9]    > 至此排序整個過程全部完成  // 關鍵點  a) 將數組中的每個元素數組化,以便后續存放已經排好序的元素  b) k的取值,k+=2, 每次取兩個數組進行比較,形成一個新的數組  c) 一定要在比較完之后附加空數組,否則會在數組個數為奇數個的時候出現訪問越界錯誤  d) 最外層循環的lim的取值, lim = (lim + 1) / 2即原數組長度的一半,作為內循環終止的條件*/

遞歸優化,查表法-Memoization(記憶): 函數可以用對象去記住先前操縱的成果,從而能避免無謂的運算

避免重復工作,將執行過的運算或操作,緩存起來,如果后續有相同的操作可直接從緩存中查找,沒有則進行遞歸,可大大減少遞歸的工作量,提高性能。

var count = 0;function factorial(n) {  console.log("factorial count = " + count++);  if (n == 0) {    return 1;  } else {    return n * factorial(n - 1);  }}// var f1 = factorial(6);// var f2 = factorial(5);// var f3 = factorial(4);// >>>>> 結果是函數被調用了:18次/*  遞歸優化:查表法,通過緩存 */function memFactorial(n) {  console.log("memFactorial count = " + count++);  // JS中函數即可視為一個對象,所以可以直接通過函數名+點語法給此對象添加屬性  if (!memFactorial.cache) {     memFactorial.cache = {      "0": 1,      "1": 1    };  }  // hasOwnProperty(n)可以判斷對象中是否存在該屬性,不會查找原型(但是"in"會先查實例再原型)  if (!memFactorial.cache.hasOwnProperty(n)) {    // 緩存中不存在的情況下再進行遞歸,并且將新數組緩存起來    memFactorial.cache[n] = n * memFactorial(n - 1);  }  // 最終數據都可以在緩存中找到  return memFactorial.cache[n];}var f1 = memFactorial(6);var f2 = memFactorial(5);var f3 = memFactorial(4);// >>>>> 結果是函數被調用了:只有8次,大大的減少了函數被調用的次數

Memoization通用版,但不建議使用,建議自己手動在普通版中實現函數內容

通用版需要緩存特定參數的函數調用結果,即,傳入的參數不同,調用函數的時候,其結果需要緩存起來

/*  遞歸優化通用版,性能不如普通版,需要緩存每次調用結果, 即內部函數   */ function memoize(func, cache) {  // 緩存池  cache = cache || {};  // 沒有則新建  var result = function(arg) {    // console.log("memoize count = " + count++);    if (!cache.hasOwnProperty(arg)) {      cache[arg] = func(arg);    }  };  return result;}  // 使用// 將階乘函數緩存起來// 只是優化了cache的通用性,但損失了一部分性能var memOpfactorial = memoize(factorial, {"0": 1, "1": 1});var f1 = memOpfactorial(6);var f2 = memOpfactorial(5);var f3 = memOpfactorial(4);// 結果次數依舊是:18次。因此不推薦使用

總結

  1. 謹慎使用遞歸,以防造成死循環,程序崩潰,或者調用棧溢出;
  2. 學會使用迭代來替代遞歸,可以避免遞歸調用棧或死循環問題出現;
  3. 查表法,遞歸的優化版:Memoization減少重復工作,提升性能。但不推薦使用通用版,相比普通版會損失部分性能。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 东阳市| 景谷| 正镶白旗| 肃北| 丹阳市| 竹溪县| 鄢陵县| 泰安市| 星子县| 余庆县| 错那县| 多伦县| 山西省| 黄大仙区| 沙河市| 永吉县| 寿阳县| 平遥县| 集安市| 卢氏县| 宝鸡市| 巨野县| 五寨县| 休宁县| 同德县| 清镇市| 滨州市| 莲花县| 大连市| 微博| 柳江县| 色达县| 城市| 黄陵县| 芜湖县| 岳普湖县| 重庆市| 三江| 盐山县| 延庆县| 山东省|