Evanalysis
2.1預計閱讀時間: 5 分鐘

2.1 作為遞歸 ADT 的 list

用 head-tail 契約閱讀 list,再用 iteration 與 recursion 實作操作,而不混淆 ADT 與 representation。

課程目錄

CSCI2520:資料結構

針對 CSCI2520 資料結構基礎的結構化筆記,重視操作層級推理與選擇性互動示範。

章節 0程式基礎1
章節 2List 與 recursion1
章節 4Trees 與 BST1

List 是課程第一次把 recursion 變成資料結構本身的語言,而不只是程式技巧。 List 不是單純用括號畫出來的 array;它有一個契約:要麼是 empty list,要麼 由一個 head element 和一個本身仍然是 list 的 tail 組成。

這個遞歸定義解釋了為甚麼 list 程式常常有同一個形狀:先處理 empty list, 再處理 head,然後把剩下的工作交給 tail。

List 契約

定義

List ADT

List 是有次序的元素序列。在 head-tail 觀點下,每個非空 list 都可以寫成 CreateList(head, tail),其中 head 是一個元素,而 tail 是另一個 list。

Lecture 先強調三個操作:

  • CreateList(head, tail):由一個元素和一個 list 建立新 list;
  • ListHead(list):取第一個元素;
  • ListTail(list):取去掉第一個元素後剩下的 list。

Head 和 tail 的類型不同。Head 是元素;tail 是 list。例如 L=[3,6,4,3,0]L = [3, 6, 4, -3, 0] 時,ListHead(L)=3ListHead(L) = 3,但 ListTail(L)=[6,4,3,0]ListTail(L) = [6, 4, -3, 0]

例題

由 empty list 建立 list

List [3, 4, 5] 可以寫成:

CreateList(3, CreateList(4, CreateList(5, EmptyList())))

由內到外讀:先建立 [5],再把 4 放到前面,最後把 3 放到前面。

為甚麼 recursion 自然適合 list

List 的遞歸結構直接給出很多 function 的寫法。

int ListLength(listADT list) {
   if (ListIsEmpty(list)) {
      return 0;
   }
   return 1 + ListLength(ListTail(list));
}

End case 是 empty list。Recursive call 是對 tail 做,而 tail 比原本 list 短。如果沒有 end case,或者 recursive call 沒有令問題變小,遞歸就不安全。

定理

List recursion 的終止理由

對有限 list,不斷把 list 換成它的 tail,最後一定會到 empty list。因此只 對 ListTail(list) 遞歸、並處理 EmptyList() 的 function 會終止。

同樣,NthElement 的思路是:第 0 個元素是 head;第 n 個元素是 tail 的第 n1n-1 個元素。

Iteration 和 recursion 只是保存狀態的方法不同

Iterative ListLength 會用 cursor 逐步走過 successive tails。

int ListLength(listADT list) {
   int count = 0;
   for (listADT L = list; !ListIsEmpty(L); L = ListTail(L)) {
      count++;
   }
   return count;
}

Recursive 版本把未完成工作放在 call stack;iterative 版本把它放在 local variables。兩者正確,都是因為每一步都從 current list 走向 tail。

Representation 會改變成本

Array-based list 可以實作同一個 ADT,但 CreateList 可能要把 tail 的所有 元素複製到新 array。Linked representation 則儲存一個 head 和一個指向 tail 的 pointer,因此 CreateList 可以只 allocate 一個新 cell,再指向原本 的 tail。

ADT 告訴我們操作的意思;representation 決定操作的成本。

定義

Linked-list representation

Linked-list cell 儲存一個元素和一個指向剩餘 list 的 pointer。這個 pointer field 就是 representation 層面的 tail。

常見錯誤

  • ListTail(L) 當成元素,而不是 list。
  • 未處理 empty list 就呼叫 ListHead
  • 假設 array representation 和 linked representation 成本一樣。
  • Recursive call 仍然用原本 list,沒有把問題縮小。

快速檢查

若 L 是 [7, 2, 9],ListHead(L) 和 ListTail(L) 是甚麼?

用 head-tail 契約回答。

解答

答案

快速檢查

為甚麼 recursive ListLength 會在有限 list 上終止?

留意 recursive call 的 argument。

解答

答案

練習

  1. 說明 DisplayList 的 recursive idea:print head,再 display tail。
  2. 解釋為甚麼 ListConcat(list1, list2)list1 empty 時應該 return list2
  3. 比較 array representation 和 linked representation 下 CreateList 的成本。

解答

引導解答