Recursion and stack

Two ways of thinking

// 創造 pow(x, n) 帶入參數得到 x 的 n 次方 
pow(x, n) = x**n
pow(2, 3) = 8
pow(2, 4) = 16

// 有 2 種思維模式
// 第 1 種用迴圈
function pow(x, n) {
  let result = 1;
  // multiply result by x n times in the loop
  for (let i = 0; i < n; i++) {
    result *= x;
  }
  return result;
}
alert( pow(2, 3) ); // 8

// 第 2 種用遞回
function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n - 1);
  }
}
alert( pow(2, 3) ); // 8

              if n==1  = x // 遞回基礎
             /
pow(x, n) =
             \
              else     = x * pow(x, n - 1)

// 遞回函式通常比較簡短       
function pow(x, n) {
  return (n == 1) ? x : (x * pow(x, n - 1));
}

// 巢狀函式使用次數有限制,10,000次是可以的,次數依據引擎不同而有差異,這限制遞回的使用,但遞回仍被
// 廣泛應用,因為讓程式碼更簡單更容易維護

The execution context and stack

當函式執行巢狀函式時,會發生以下步驟

  • 目前函式停止執行

  • 執行內容被儲存到課書資料結構 execution context stack

  • 執行巢狀函式

  • 當巢狀函式執行完畢取回原本的韓式繼續執行

function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n - 1); // 停止執行函式,執行巢狀函式
  }
}
alert( pow(2, 3) );

Context: { x: 2, n: 1, at line 1 } pow(2, 1) // 返回 2
Context: { x: 2, n: 2, at line 5 } pow(2, 2) // 跳到 pow(2, 1)
Context: { x: 2, n: 3, at line 5 } pow(2, 3) // 跳到 pow(2, 2)

// 遞回消耗記憶體容量,求 N 次方需要 N 個執行內容,回圈相對來說更省記憶體,任何遞回都可以用迴圈改寫
// 迴圈一般來說更有效,但有時寫成迴圈比較複雜,優化顯得不值得,因此遞迴還是被使用,且簡短好維護。

Recursive traversals

// 假設有一間公司底下有分部門,部門下面要分子部門,子部門下面又分子子部門...
let company = { // the same object, compressed for brevity
  sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }],
  development: {
    sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
    internals: [{name: 'Jack', salary: 1300}]
  }
};

// 用遞回可以得到所有巢狀物件的值,用迴圈要寫好幾個巢狀迴圈
// The function to do the job
function sumSalaries(department) {
  if (Array.isArray(department)) { // case (1)
    return department.reduce((prev, current) => prev + current.salary, 0); // sum the array
  } else { // case (2)
    let sum = 0;
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // recursively call for subdepartments, sum the results
    }
    return sum;
  }
}
alert(sumSalaries(company)); // 6700

// 這就是遞迴的威力,可以拿到任何巢狀物件的值且很好理解程式碼。

Recursive structures

除上述的公司組織是遞回,在網頁端 HTML、XML 也是遞回結構,HTML 標籤裡面包含內容、註解、其他HTML 標籤,以下在舉一個遞回例子,某些情況他會優於 array。

// array 要新增或刪除開頭中間元素有些麻煩,會需要大量的執行程序,這時 linked-list 結構較好用,
// 但無法透過資料的順序快速找到要的值,我們也沒有很常需要這樣的操作,因此 array 還是被廣泛應用。
let arr = [obj1, obj2, obj3];

// linked-list
let list = {
  value: 1,
  next: {
    value: 2,
    next: {
      value: 3,
      next: {
        value: 4,
        next: null
      }
    }
  }
};

// same
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
let secondList = list.next.next;
list.next.next = null;

list.next.next = secondList;
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };

// prepend the new value to the list
list = { value: "new item", next: list };
list.next = list.next.next;

Last updated