Skip to content · ⁨ข้ามไปยังเนื้อหา⁩

Computational thinking and Problem-solving · ⁨การคิดเชิงคำนวณและการแก้ปัญหา⁩

A-Level Computer Science · ⁨Computer Science A-Level⁩ · Topic 19 · ⁨หัวข้อ 19⁩

Video lesson for this topic · ⁨บทเรียนวิดีโอสำหรับหัวข้อนี้⁩ Open the video page · ⁨เปิดหน้าวิดีโอ⁩
15:33

การค้นหา & การเรียงลำดับ

สมุดโทรศัพท์ที่มีชื่อกี่ล้านชื่อ หากคุณตรวจสอบทีละชื่อ คุณอาจ需要进行几百万次比较。但你已经知道窍门:打开它在…

English narration · English + 中文 subtitles burned in · ⁨การบรรยายภาษาอังกฤษ · คำบรรยายภาษาอังกฤษ + 中文 ลอยตัวบนภาพ⁩

19.1

Searching algorithms · ⁨อัลกอริทึมการค้นหา⁩

Syllabus · ⁨หลักสูตร⁩
English
Candidates should be able to: Notes and guidance
Show understanding of linear search and binary search methods Write an algorithm to implement a linear search Write an algorithm to implement a binary search The conditions necessary for the use of a binary search How the performance of a binary search varies according to the number of data items
Show understanding of insertion sort and bubble sort methods Write an algorithm to implement an insertion sort Write an algorithm to implement a bubble sort Performance of a sorting routine may depend on the initial order of the data and the number of data items
Show understanding of and use Abstract Data Types (ADT) Write algorithms to find an item in each of the following: linked list, binary tree Write algorithms to insert an item into each of the following: stack, queue, linked list, binary tree Write algorithms to delete an item from each of the following: stack, queue, linked list Show understanding that a graph is an example of an ADT. Describe the key features of a graph and justify its use for a given situation. Candidates will not be required to write code for a graph structure
Show how it is possible for ADTs to be implemented from another ADT Describe the following ADTs and demonstrate how they can be implemented from appropriate built-in types or other ADTs: stack, queue, linked list, dictionary, binary tree
Show understanding that different algorithms which perform the same task can be compared by using criteria (e.g. time taken to complete the task and memory used) Including use of Big O notation to specify time and space complexity
ไทย
ผู้เข้าสอบควรสามารถ: หมายเหตุและคำแนะนำ
แสดงความเข้าใจเกี่ยวกับวิธีการ linear search และ binary search เขียนอัลกอริทึมเพื่อทำ linear search เขียนอัลกอริทึมเพื่อทำ binary search เงื่อนไขที่จำเป็นสำหรับการใช้ binary search ประสิทธิภาพของ binary search เปลี่ยนแปลงไปตามจำนวนข้อมูล
แสดงความเข้าใจเกี่ยวกับวิธีการ insertion sort และ bubble sort เขียนอัลกอริทึมเพื่อทำ insertion sort เขียนอัลกอริทึมเพื่อทำ bubble sort ประสิทธิภาพของการเรียงลำดับอาจขึ้นอยู่กับลำดับเริ่มต้นของข้อมูลและจำนวนข้อมูล
แสดงความเข้าใจและใช้งาน Abstract Data Types (ADT) เขียนอัลกอริทึมเพื่อค้นหาไอเท็มใน: linked list, binary tree เขียนอัลกอริทึมเพื่อเพิ่มไอเท็มลงใน: stack, queue, linked list, binary tree เขียนอัลกอริทึมเพื่อลบไอเท็มออกจาก: stack, queue, linked list แสดงความเข้าใจว่า graph เป็นตัวอย่างของ ADT อธิบายคุณสมบัติหลักของ graph และอธิบายเหตุผลในการนำไปใช้ในสถานการณ์ที่กำหนด ผู้เข้าสอบไม่จำเป็นต้องเขียนโค้ดสำหรับโครงสร้าง graph
แสดงให้เห็นความเป็นไปได้ที่ ADTs จะถูกสร้างจาก ADT อื่น อธิบาย ADT ต่างๆ ดังนี้และแสดงวิธีสร้างจากชนิดข้อมูลในตัวหรือ ADT ที่เหมาะสม: stack, queue, linked list, dictionary, binary tree
แสดงความเข้าใจว่าอัลกอริทึมที่ทำหน้าที่เดียวกันแต่แตกต่างกันสามารถเปรียบเทียบกันได้โดยใช้เกณฑ์ (เช่น เวลาที่ใช้ในการดำเนินการและความจำที่ใช้) รวมถึงการใช้ Big O notation เพื่อระบุความซับซ้อนด้านเวลาและพื้นที่

Source: Cambridge International syllabus · ⁨แหล่งที่มา: หลักสูตร Cambridge International⁩

English
Big O: how algorithms scale
Insertion sort: slide each card into place
Bubble sort, pass by pass
Binary search: halve and conquer

A search finds a target value in a collection (often an array 数组) and returns its position, or "not found".

Linear search

A linear search 线性查找 walks from start to end, comparing each element with the target:

No preparation is needed, so it works on any list. Worst case O($n$) (target at the end or absent); best case 1 comparison. Use it on unsorted data or small lists. (The returned -1 is a sentinel value — an impossible position that means "not found"; the caller tests IF result = -1.)

The exam's version. Paper 3 asks you to complete a linear search written with a flag and a WHILE loop, and Paper 4 to write a function that returns the index or a count. Both look like this:

To stop at the first match instead, use a WHILE Index <= 100 AND NOT Found loop that sets Found ← TRUE and remembers the index. The marks are for the loop over every element, the comparison, and what is returned when the value is absent.

Binary search

A binary search 二分查找 needs the data sorted. Look at the middle element; if it is the target, done; if the target is smaller, search the left half, else the right half — halving the range each time:

Worst case O($\log_{2} n$) — for a million items, about 20 comparisons. Much faster than linear search on large sorted arrays, but you must sort first (a one-off O($n \log n$) cost), worth it if you search many times.

"State the condition necessary for a binary search." The data must be in order (sorted, ascending or descending, on the key being searched). "Describe how to perform a binary search" (three marks): (1) find the middle item of the list (or of the current range) and compare it with the target; (2) if it matches, the search ends; if the target is smaller, repeat on the lower half, if larger, on the upper half; (3) keep halving the range until the item is found or the range is empty, which means it is not present.

The exam's version, with the bounds and a flag, is the one to reproduce when asked to complete the algorithm:

"Explain how the performance varies with the number of items." Each comparison halves the number of items left, so the maximum number of comparisons is about $\log_{2} n$: doubling the size of the list adds only one more comparison. This is O($\log n$). "Compare linear and binary search": a linear search needs up to $n$ comparisons (O($n$)) and, on average, half that, but works on unsorted data; a binary search needs at most $\log_{2} n$ (O($\log n$)) and is far faster for large lists, but the data must first be sorted and it must allow direct access to the middle item (an array, not a linked list). For $1000$ items: $1000$ against $10$ comparisons.

ไทย
Big O: การขยายตัวของอัลกอริทึม
Insertion sort: เลื่อนการ์ดแต่ละใบเข้าตำแหน่ง
Bubble sort, pas-by-pass
Binary search:减半และพิชิต

A search finds a target value in a collection (often an array) and returns its position, or "not found".

สมุดโทรศัพท์เปิด
การค้นหาลิสต์ที่เรียงลำดับแล้ว เช่น สมุดโทรศัพท์ ช้ากว่าการตรวจสอบทุกรายการทีละตัวมาก

Linear search

A linear search walks from start to end, comparing each element with the target:

FOR i ← 1 TO n
    IF A[i] = target THEN
        RETURN i
    ENDIF
NEXT i
RETURN -1   // not found

No preparation is needed, so it works on any list. Worst case O($n$) (target at the end or absent); best case 1 comparison. Use it on unsorted data or small lists. (The returned -1 is a sentinel value — an impossible position that means "not found"; the caller tests IF result = -1.)

The exam's version. Paper 3 asks you to complete a linear search written with a flag and a WHILE loop, and Paper 4 to write a function that returns the index or a count. Both look like this:

FUNCTION LinearSearch(Data : ARRAY OF INTEGER, Target : INTEGER) RETURNS INTEGER
    DECLARE Index, Count : INTEGER
    Count ← 0
    FOR Index ← 1 TO 100
        IF Data[Index] = Target THEN
            Count ← Count + 1
        ENDIF
    NEXT Index
    RETURN Count          // how many times Target occurs; 0 means not found
ENDFUNCTION

To stop at the first match instead, use a WHILE Index <= 100 AND NOT Found loop that sets Found ← TRUE and remembers the index. The marks are for the loop over every element, the comparison, and what is returned when the value is absent.

แถวเซลล์ตัวอักษร A ถึง Z; เซลล์ A ถึง V ถูกทาสีเข้มเป็นการตรวจสอบแล้ว และ W被高亮作为匹配项,W下方有一个指针
Linear search checks every letter in turn — 23 comparisons to find W

Binary search

A binary search needs the data sorted. Look at the middle element; if it is the target, done; if the target is smaller, search the left half, else the right half — halving the range each time:

low ← 1
high ← n
WHILE low <= high DO
    mid ← (low + high) DIV 2
    IF A[mid] = target THEN
        RETURN mid
    ENDIF
    IF A[mid] < target THEN
        low ← mid + 1
    ELSE
        high ← mid - 1
    ENDIF
ENDWHILE
RETURN -1

Worst case O($\log_{2} n$) — for a million items, about 20 comparisons. Much faster than linear search on large sorted arrays, but you must sort first (a one-off O($n \log n$) cost), worth it if you search many times.

"State the condition necessary for a binary search." The data must be in order (sorted, ascending or descending, on the key being searched). "Describe how to perform a binary search" (three marks): (1) find the middle item of the list (or of the current range) and compare it with the target; (2) if it matches, the search ends; if the target is smaller, repeat on the lower half, if larger, on the upper half; (3) keep halving the range until the item is found or the range is empty, which means it is not present.

The exam's version, with the bounds and a flag, is the one to reproduce when asked to complete the algorithm:

DECLARE Lower, Upper, Mid : INTEGER
DECLARE Found : BOOLEAN
Lower ← 0
Upper ← 99
Found ← FALSE
WHILE Lower <= Upper AND NOT Found
    Mid ← (Lower + Upper) DIV 2
    IF Names[Mid] = Target THEN
        Found ← TRUE
    ELSE
        IF Names[Mid] < Target THEN
            Lower ← Mid + 1
        ELSE
            Upper ← Mid - 1
        ENDIF
    ENDIF
ENDWHILE
IF Found THEN
    OUTPUT Mid
ELSE
    OUTPUT "Not found"
ENDIF

"อธิบายว่าประสิทธิภาพเปลี่ยนแปลงอย่างไรตามจำนวนรายการ" การเปรียบเทียบแต่ละครั้ง ลดครึ่ง จำนวนรายการที่เหลือไว้ ดังนั้นจำนวนสูงสุดของการเปรียบเทียบคือประมาณ $\log_{2} n$: การเพิ่มขนาดรายการเป็นสองเท่าจะเพิ่มเพียง หนึ่ง ครั้งเปรียบเทียบ additional This is O($\log n$). “เปรียบเทียบการค้นหาแบบเชิงเส้นและการค้นหาแบบทวิภาค”: การค้นหาแบบเชิงเส้นต้องการสูงสุดถึง $n$ ครั้งเปรียบเทียบ (O($n$)) และโดยเฉลี่ย 절반นั้น แต่ทำงานได้กับข้อมูล ที่ไม่เรียงลำดับ; การค้นหาแบบทวิภาคต้องการสูงสุดถึง $\log_{2} n$ (O($\log n$)) และเร็วกว่ามากสำหรับรายการใหญ่ แต่ข้อมูลต้อง เรียงลำดับ ก่อน และต้องอนุญาตให้ เข้าถึงโดยตรง ไปยังองค์ประกอบตรงกลาง (array, ไม่ใช่ linked list) สำหรับ $1000$ รายการ: $1000$ เทียบกับ $10$ ครั้งเปรียบเทียบ

สามแถวแสดง binary search บนตัวอักษรที่เรียงลำดับแล้ว; ช่วง low-to-high ที่กำลังทำงานจะ减半แต่ละขั้นตอนเมื่อเปรียบเทียบตัวกลาง M, แล้ว T, แล้ว W กับ W
Binary search halves the range each step (low / mid / high) — just 3 comparisons to find W
สมุดบัตรข้อมูลห้องสมุด: ผนังของลิ้นชักไม้เล็กๆ หลายอัน, มีหนึ่งลิ้นชักถูกดึงออกเพื่อแสดงบัตรข้อมูลที่จัดเรียงตามลำดับ
A card catalogue: sorted records are what make a binary search possible — halve, look, halve again
Explore · ⁨สำรวจ⁩

Linear vs binary search · ⁨การค้นหาเชิงเส้นเทียบกับ binary search⁩

Search for a value. Binary search halves the list each step (only on sorted data); linear search checks one by one. · ⁨ค้นหาค่าที่ต้องการ การค้นแบบ二分法 ตัดรายการลงครึ่งหนึ่งในแต่ละขั้นตอน (ใช้ได้กับข้อมูลเรียงลำดับเท่านั้น) ส่วน การค้นแบบเส้นตรง จะตรวจสอบทีละตัว⁩

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
insertion sort/ɪnˈsɜːʃn sɔːt/ insertion sort
bubble sort/ˈbʌbl sɔːt/ bubble sort
binary search/ˈbaɪnəri sɜːtʃ/ binary search
array/əˈreɪ/ อาร์เรย์
linear search/ˈlɪnɪə sɜːtʃ/ linear search
linked list/lɪŋkt lɪst/ Linked List
19.1

Sorting algorithms · ⁨อัลกอริทึมการจัดเรียง⁩

English

Bubble sort

A bubble sort 冒泡排序 repeatedly walks the array, swapping adjacent pairs that are out of order, so the largest "bubbles" to the end each pass:

Best case O($n$) (already sorted, with the early exit); average/worst O($n^{2}$). Simple but slow for large $n$.

Insertion sort

An insertion sort 插入排序 builds a sorted prefix from the left, inserting each new element into place by shifting larger ones right:

Best case O($n$) (already sorted); worst O($n^{2}$). Good for small or nearly-sorted arrays. It sorts in place 原地 and is stable 稳定 (keeps the order of equal elements).

Tracing a sort

A common task is to show the array after each outer pass. For [D, T, H, R] with insertion sort: pass 1 (key T) no change; pass 2 (key H) → [D, H, T, R]; pass 3 (key R) → [D, H, R, T].

Writing a sort from scratch. "Write pseudocode to sort DataArray[1:1000] into ascending order" is answered by a complete bubble sort with the early-exit flag, or an insertion sort, declared and indented; either scores full marks if it works for every input:

For descending order change > to <; to sort records or a 2D array by one field, compare that field but swap the whole record (or every column). Asked to write an insertion sort "that performs the same task" as a given bubble sort, keep the same array name and direction and reproduce the insertion sort above with the comparison reversed if the order is descending.

"Describe two ways the performance of a sort is affected by the data" (two marks). (1) The number of items: an $O(n^{2})$ sort takes four times as long for twice as many items. (2) How far the data is already in order: a bubble sort with a flag, or an insertion sort, finishes in one pass over already-sorted data ($O(n)$) and does the most work on data in reverse order; the number of swaps depends on how many pairs are out of order. (Also accepted: the range or number of duplicate values, and whether the items are large records that are expensive to move.) Bubble and insertion sort are both O($n^{2}$) in the worst and average cases and O($n$) at best; quicksort and merge sort are O($n \log n$), which is why they are used for large data.

ไทย

Bubble sort

A bubble sort repeatedly walks the array, swapping adjacent pairs that are out of order, so the largest "bubbles" to the end each pass:

FOR pass ← 1 TO n - 1
    swapped ← FALSE
    FOR i ← 1 TO n - pass
        IF A[i] > A[i + 1] THEN
            temp ← A[i]
            A[i] ← A[i + 1]
            A[i + 1] ← temp
            swapped ← TRUE
        ENDIF
    NEXT i
    IF swapped = FALSE THEN      // already sorted
        EXIT FOR
    ENDIF
NEXT pass

Best case O($n$) (already sorted, with the early exit); average/worst O($n^{2}$). Simple but slow for large $n$.

Insertion sort

An insertion sort builds a sorted prefix from the left, inserting each new element into place by shifting larger ones right:

FOR i ← 2 TO n
    key ← A[i]
    j ← i - 1
    WHILE j >= 1 AND A[j] > key DO
        A[j + 1] ← A[j]
        j ← j - 1
    ENDWHILE
    A[j + 1] ← key
NEXT i

Best case O($n$) (already sorted); worst O($n^{2}$). Good for small or nearly-sorted arrays. It sorts in place and is stable (keeps the order of equal elements).

Tracing a sort

A common task is to show the array after each outer pass. For [D, T, H, R] with insertion sort: pass 1 (key T) no change; pass 2 (key H) → [D, H, T, R]; pass 3 (key R) → [D, H, R, T].

Writing a sort from scratch. "Write pseudocode to sort DataArray[1:1000] into ascending order" is answered by a complete bubble sort with the early-exit flag, or an insertion sort, declared and indented; either scores full marks if it works for every input:

DECLARE Pass, Index, Temp : INTEGER
DECLARE Swapped : BOOLEAN
Pass ← 1
REPEAT
    Swapped ← FALSE
    FOR Index ← 1 TO 1000 - Pass
        IF DataArray[Index] > DataArray[Index + 1] THEN
            Temp ← DataArray[Index]
            DataArray[Index] ← DataArray[Index + 1]
            DataArray[Index + 1] ← Temp
            Swapped ← TRUE
        ENDIF
    NEXT Index
    Pass ← Pass + 1
UNTIL Swapped = FALSE OR Pass = 1000

For descending order change > to <; to sort records or a 2D array by one field, compare that field but swap the whole record (or every column). Asked to write an insertion sort "that performs the same task" as a given bubble sort, keep the same array name and direction and reproduce the insertion sort above with the comparison reversed if the order is descending.

"อธิบายสองวิธีที่ประสิทธิภาพของการจัดเรียงได้รับผลกระทบจากข้อมูล" (2 คะแนน). (1) จำนวนรายการ: การจัดเรียง $O(n^{2})$ ใช้เวลานานขึ้นสี่เท่าเมื่อมีรายการเป็นสองเท่า (2) ระดับความเป็นระเบียบของข้อมูลเดิม: การจัดเรียงแบบฟองสบู่ที่มีตัวบ่งชี้ หรือการจัดเรียงแบบแทรก จะเสร็จสิ้นในหนึ่งรอบผ่านข้อมูลที่เป็นระเบียบแล้ว ($O(n)$) และใช้งานมากที่สุดกับข้อมูลที่เรียงกลับด้าน; จำนวนการสลับขึ้นอยู่กับจำนวนคู่ที่ไม่เป็นระเบียบ (ยอมรับได้ additionally: ช่วงหรือจำนวนค่าซ้ำ และการที่รายการเป็นเรคอร์ดขนาดใหญ่ที่ต้องเสียค่าใช้จ่ายในการย้าย) การจัดเรียงแบบฟองสบู่และแบบแทรกมี O($n^{2}$) ในกรณี最差และเฉลี่ย และมี O($n$) ในกรณีที่ดีที่สุด; การจัดเรียงแบบเร็วและแบบรวมมี O($n \log n$) ซึ่งเป็นเหตุผลที่ใช้สำหรับข้อมูลขนาดใหญ่

แถวติดตามการจัดเรียงแบบแทรกของ D, T, H, R ผ่านสามรอบ; ส่วนหน้าที่ยังไม่ได้จัดเรียงถูกทาสีและลูกศรแสดงการเลื่อนองค์ประกอบที่ใหญ่กว่าไปทางขวาเพื่อให้คีย์ตกลงตำแหน่ง
การจัดเรียงแบบแทรกของ [D, T, H, R], เลื่อนแต่ละคีย์เข้าสู่ตำแหน่งตามลำดับรอบ
Explore · ⁨สำรวจ⁩

Watch a sort run · ⁨ดูการจัดเรียงทำงาน⁩

Step through a sort and watch the bars settle into order — how a sorting algorithm works pass by pass. · ⁨เดินผ่านกระบวนการเรียงลำดับเพื่อดูแท่งกราฟจัดเรียงเป็นระเบียบ — แสดงการทำงานของอัลกอริทึมการจัดเรียงแบบทีละขั้นตอน⁩

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
in place/ɪn pleɪs/ ในสถานที่
stable/ˈsteɪbl/ เสถียร
19.1

ADTs in algorithms · ⁨ADT ในอัลกอริทึม⁩

English

The Abstract Data Types (ADTs) from Topic 10 appear inside many algorithms: a stack 栈 drives depth-first traversal and undo; a queue 队列 drives breadth-first traversal and print ordering; a linked list 链表 lets data grow and shrink.

ADTs can be built from other ADTs, not just from arrays: a queue from two stacks; a stack from a linked list (push = prepend a head node 节点); a queue from a linked list with head and tail pointers 指针; a binary tree 二叉树 from nodes with two child pointers; a dictionary 字典 stores key→value pairs (often on a hash table). Layering this way separates concerns — the algorithm using the ADT need not know how it is built.

The ADTs the exam asks you to describe and implement

Stack (last in, first out): items are added (pushed) and removed (popped) at the same end, the top; a pointer TopOfStack holds the index of the top item. Implemented with an array and that one pointer: push checks the stack is not full, increments the pointer and stores the item; pop checks it is not empty, returns the top item and decrements the pointer.

Queue (first in, first out): items join at the rear (enqueue) and leave from the front (dequeue); two pointers and a count. In a linear queue the front pointer creeps along the array until the space at the start is wasted; a circular queue 循环队列 wraps both pointers round with MOD, so every cell is reused.

Linked list: a sequence of nodes, each holding a data item and a pointer to the next node; a start pointer gives the first node and a null pointer (0 or $-1$) ends the list. In an array implementation two parallel arrays hold the data and the pointers, and unused cells are chained into a free list 空闲列表 so that an insertion knows where to put the new node.

To insert into an ordered list: take the first free cell (NewNode ← FreeList, FreeList ← Pointer[FreeList]), store the item, then walk the list with a Previous and Current pointer until Data[Current] > Item or the end; set Pointer[NewNode] ← Current and Pointer[Previous] ← NewNode (or Start ← NewNode if it goes first). To delete, re-link the previous node past the deleted one and return the cell to the free list.

Binary tree: a root node, each node holding data, a left pointer to a subtree of smaller values and a right pointer to a subtree of larger values. Implemented as a 2D array (or three 1D arrays) Tree[Index, 0..2] for left pointer, data, right pointer, with a root pointer and a next-free pointer.

To insert: store the item in the next free node with both pointers $-1$; if the tree is empty make it the root; otherwise walk down from the root, going left or right by comparison, until the pointer you would follow is $-1$, and set that pointer to the new node. An ADT from another ADT: a stack is a linked list where push and pop both work at the start; a queue is a linked list with a start and an end pointer; a queue can be made from two stacks (push onto one, pop from the other, moving everything across when the second is empty); a binary tree's nodes are records or objects linked by pointers, so it is built from a linked structure of nodes. Say which operations of the new ADT map onto which operations of the old one.

ไทย

ประเภทข้อมูลนามธรรม (ADTs) จากหัวข้อที่ 10 ปรากฏภายในอัลกอริทึมจำนวนมาก: สตacks ขับเคลื่อนการสำรวจแบบลึกและ undo; คิว ขับเคลื่อนการสำรวจแบบกว้างและลำดับการพิมพ์; ลิงค์ลิสต์ ทำให้ข้อมูลขยายและหดตัวได้

ADT สามารถสร้างจาก ADT อื่นๆ ไม่ใช่แค่จากอาร์เรย์: คิวจาก สตacks สองอัน; สตacks จากลิงค์ลิสต์ (push = เพิ่มหัว โหนด); คิวจากลิงค์ลิสต์ที่มีหัวและท้าย พอยน์เตอร์; ไบนารีทรี จากโหนดที่มีพอยน์เตอร์ลูกสองตัว; ดิชชันนารี เก็บคู่ key→value (มักอยู่บน hash table) การวางชั้นแบบนี้แยกความรับผิดชอบ — อัลกอริทึมที่ใช้ ADT ไม่จำเป็นต้องรู้วิธีการสร้างมัน

ADT ที่ข้อสอบต้องการให้คุณอธิบายและนำไปใช้

Stack (last in, first out): รายการถูกเพิ่ม (pushed) และเอาออก (popped) ที่ปลายเดียวกัน คือ ยอด; พอยน์เตอร์ TopOfStack เก็บดัชนีของรายการยอด Implemented ด้วยอาร์เรย์และพอยน์เตอร์หนึ่งตัว: push ตรวจสอบว่า stack ไม่เต็ม, เพิ่มพอยน์เตอร์และเก็บรายการ; pop ตรวจสอบว่าไม่ว่าง, ย้อนกลับรายการยอดและลดพอยน์เตอร์

FUNCTION Push(Item : INTEGER) RETURNS BOOLEAN
    IF TopOfStack = 9 THEN      // full (array 0 to 9)
        RETURN FALSE
    ENDIF
    TopOfStack ← TopOfStack + 1
    StackData[TopOfStack] ← Item
    RETURN TRUE
ENDFUNCTION
FUNCTION Pop() RETURNS INTEGER
    IF TopOfStack = -1 THEN      // empty
        RETURN -1
    ENDIF
    TopOfStack ← TopOfStack - 1
    RETURN StackData[TopOfStack + 1]
ENDFUNCTION

Queue (first in, first out): รายการเข้าร่วมที่ ท้าย (enqueue) และออกจาก หน้า (dequeue); พอยน์เตอร์สองตัวและนับจำนวน ใน linear queue หน้าพอยน์เตอร์เคลื่อนไปตามอาร์เรย์จนพื้นที่ที่เริ่มต้นเสียเปล่า; circular queue หวนทั้งสองพอยน์เตอร์ด้วย MOD, ดังนั้นทุกเซลล์จะถูกนำกลับมาใช้ใหม่

circular queue ของหกเซลล์อาร์เรย์เก็บสามรายการในเซลล์ 3 ถึง 5, กับหน้าพอยน์เตอร์อยู่ที่ 3 และท้ายอยู่ที่ 5, และลูกศรประแสดงว่ารายการถัดไปจะหวนกลับเข้าไปในเซลล์ 0
circular queue: ท้ายและหน้าพอยน์เตอร์ก้าวไปข้างหน้าด้วย MOD, ดังนั้นเซลล์แรกๆ ของอาร์เรย์จะถูกนำกลับมาใช้ทันทีเมื่อรายการออกจากนั้น
FUNCTION Enqueue(Item : STRING) RETURNS BOOLEAN
    IF Count = 6 THEN      // full
        RETURN FALSE
    ENDIF
    Rear ← (Rear + 1) MOD 6
    QueueArray[Rear] ← Item
    Count ← Count + 1
    RETURN TRUE
ENDFUNCTION
FUNCTION Dequeue() RETURNS STRING
    IF Count = 0 THEN      // empty
        RETURN ""
    ENDIF
    DECLARE Item : STRING
    Item ← QueueArray[Front]
    Front ← (Front + 1) MOD 6
    Count ← Count - 1
    RETURN Item
ENDFUNCTION

Linked list: ลำดับของ โหนด, แต่ละตัวเก็บรายการข้อมูลและ พอยน์เตอร์ไปยังโหนดถัดไป; start pointer ให้โหนดแรกและ null pointer (0 หรือ $-1$) จบลิสต์ ในการ IMPLEMENTATION แบบ array สอง array ข้างกันเก็บข้อมูลและพอยน์เตอร์, และเซลล์ที่ไม่ได้ใช้งานถูกเชื่อมต่อกันเป็น free list เพื่อให้การ insert รู้ว่าจะวางโหนดใหม่ไว้ที่ไหน

สอง array ข้างกัน Data และ Pointer implement ลิงค์ลิสต์ของชื่อ Ann, Ben และ Dan: start pointer คือ 1, พอยน์เตอร์เชื่อม 1 ไป 3 ไป 2 ไป 0, และเซลล์ที่ไม่ได้ใช้งาน 4, 5 และ 6 เป็น free list
ลิงค์ลิสต์ในสอง array: ลำดับของลิสต์อยู่ในพอยน์เตอร์ ไม่ใช่ตำแหน่ง; การใส่ชื่อหมายถึงการดึงเซลล์จาก free list และเชื่อมโยงพอยน์เตอร์สองตัว
FUNCTION FindInList(Target : STRING) RETURNS INTEGER   // index, or 0 if absent
    DECLARE Current : INTEGER
    Current ← Start
    WHILE Current <> 0
        IF Data[Current] = Target THEN
            RETURN Current
        ENDIF
        Current ← Pointer[Current]
    ENDWHILE
    RETURN 0
ENDFUNCTION

เพื่อ insert เข้าใน ordered list: ดึงเซลล์ฟรีแรก (NewNode ← FreeList, FreeList ← Pointer[FreeList]), เก็บรายการ, แล้วเดินลิสต์ด้วย Previous และ Current poynter จน Data[Current] > Item หรือจบ; ตั้งค่า Pointer[NewNode] ← Current และ Pointer[Previous] ← NewNode (หรือ Start ← NewNode ถ้ามันเข้าก่อน) เพื่อ delete, เชื่อมต่อโหนดก่อนหน้าข้ามโหนดที่ลบออกและคืนเซลล์ให้ free list

Binary tree: โหนด ราก, แต่ละโหนดเก็บข้อมูล, left pointerไปยัง subtree ของค่าที่น้อยกว่าและ right pointerไปยัง subtree ของค่าที่มากกว่า Implemented เป็น 2D array (หรือสาม 1D arrays) Tree[Index, 0..2] สำหรับ left pointer, ข้อมูล, right pointer, กับ root pointer และ next-free pointer

FUNCTION FindInTree(Target : INTEGER) RETURNS INTEGER   // index, or -1
    DECLARE Current : INTEGER
    Current ← Root
    WHILE Current <> -1
        IF Tree[Current, 1] = Target THEN
            RETURN Current
        ENDIF
        IF Target < Tree[Current, 1] THEN
            Current ← Tree[Current, 0]      // go left
        ELSE
            Current ← Tree[Current, 2]      // go right
        ENDIF
    ENDWHILE
    RETURN -1
ENDFUNCTION

เพื่อ insert: เก็บรายการในโหนดถัดไปฟรีด้วยทั้งสองพอยน์เตอร์ $-1$; หาก tree ว่างทำให้มันเป็น root; มิฉะนั้นเดินลงจากราก, ไปซ้ายหรือขวาด้วยการเปรียบเทียบ, จนพอยน์เตอร์ที่คุณจะติดตามคือ $-1$, และตั้งค่าพอยน์เตอร์นั้นไปยังโหนดใหม่ An ADT from another ADT: a stack is a linked list where push and pop both work at the start; a queue is a linked list with a start and an end pointer; a queue can be made from two stacks (push onto one, pop from the other, moving everything across when the second is empty); a binary tree's nodes are records or objects linked by pointers, so it is built from a linked structure of nodes. บอกว่า operations ของ ADT ใหม่จับคู่กับ operations ของ ADT เก่าอย่างไร

Binary tree ที่มี root 27, subtree ซ้ายของ 19, 16, 21 และ 17, และ subtree ขวาของ 36, 42, 89 และ 55, กับ root, left และ right pointers, และ leaf node ที่ติดฉลาก *Binary tree: แต่ละโหนดมีโหนดลูกได้สูงสุดสองตัว

Binary search tree ที่มี root 4 (subtree ซ้าย 2 เหนือ 1 และ 3, subtree ขวา 6 เหนือ 5 และ 7); pre-order เยี่ยม 4 2 1 3 6 5 7, in-order 1 2 3 4 5 6 7 (sorted), post-order 1 3 2 5 7 6 4 *สามการสำรวจแบบลึกของ binary tree: pre-order, in-order (sorted order) และ post-order

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
stack/stæk/ stack
queue/kjuː/ queue
node/nəʊd/ โหนด
pointers/ˈpɔɪntəz/ พอยเตอร์
binary tree/ˈbaɪnəri triː/ binary tree structure
dictionary/ˈdɪkʃənəri/ dictionary
circular queue/ˈsɜːkjʊlə kjuː/ คิววงกลม
free list/friː lɪst/ ฟรีลิสต์
19.1

Comparing algorithms · ⁨เปรียบเทียบอัลกอริทึม⁩

English

Time complexity

Time complexity 时间复杂度 is how the running time grows with input size $n$, written in Big-O notation 大O表示法 (the dominant term): O(1) constant, O($\log n$) binary search, O($n$) linear search, O($n \log n$) good sorts, O($n^{2}$) bubble/insertion sort. A smaller order is better at scale, even if another algorithm is faster for small $n$.

To make that concrete: to sort a million items, an $O(n \log n)$ sort finishes in a fraction of a second, while an $O(n^{2})$ sort can take minutes.

Worked example. A sorted list holds $1000$ items. How many comparisons does each search need in the worst case?

A linear search checks items one at a time, so it may need up to $1000$ comparisons — this is $O(n)$. A binary search halves the list each step, so it needs at most $\lceil \log_2 1000 \rceil = 10$ comparisons — this is $O(\log n)$. Doubling the list to $2000$ items adds only one comparison to the binary search, but up to another $1000$ to the linear search — which is why the order of growth, not raw speed, decides the winner at scale.

Describing an order. O(1): the time is constant, independent of the number of items (pushing onto a stack, reading an array element). O($\log n$): the time grows with the logarithm of the number of items, so doubling the data adds only a fixed extra step (binary search). O($n$): the time grows in proportion to the number of items (linear search, one pass through a list). O($n \log n$): a little worse than linear (efficient sorts). O($n^{2}$): the time grows with the square of the number of items, so doubling the data quadruples the time (bubble and insertion sort). "State the Big O of a binary search of Names[0:99]" is answered $O(\log n)$, and "describe its meaning" as above; Big O measures how the time or memory scales, not the actual time.

Space complexity

Space complexity 空间复杂度 is the extra memory needed. Bubble and insertion sort use O(1) extra (in place); merge sort uses O($n$); recursion uses stack memory proportional to its depth. There is often a time–memory trade-off.

Other criteria

Simplicity (easier to code and maintain), stability, and adaptiveness (faster on nearly-sorted data). The right algorithm depends on the data and the constraints.

ไทย

Time complexity

ความซับซ้อนด้านเวลา คือวิธีการที่เวลาในการทำงานเพิ่มขึ้นตามขนาดของข้อมูลเข้า $n$ เขียนในรูปแบบ Big-O notationO (พจน์หลัก): O(1) ค่าคงที่, O($\log n$) การค้นหาแบบทวิภาค, O($n$) การค้นหาแบบเชิงเส้น, O($n \log n$) การจัดเรียงที่ดี, O($n^{2}$) บับเบิล/อินเซอร์ชันโซตซ์ ความซับซ้อนระดับต่ำกว่าจะดีกว่าเมื่อขยายสเกล แม้ว่ามีอัลกอริทึมอื่นจะเร็วกว่าสำหรับขนาดเล็ก ๆ $n$ ก็ตาม

เพื่อให้เห็นภาพชัดเจน: ในการจัดเรียงรายการหนึ่งล้านรายการ อัลกอริทึม $O(n \log n)$ จะเสร็จสิ้นในเสี้ยววินาที ในขณะที่อัลกอริทึม $O(n^{2})$ อาจใช้เวลาเป็นนาที

ตัวอย่างฝึกหัด รายการที่มีลำดับแล้วมี $1000$ รายการ ต้องใช้การเปรียบเทียบกี่ครั้งต่อการค้นหาในแต่ละกรณี worse case?

การค้นหาแบบเชิงเส้นตรวจสอบรายการทีละรายการ ดังนั้นอาจต้องการการเปรียบเทียบสูงสุดถึง $1000$ — นี่คือ $O(n)$ การค้นหาแบบทวิภาคแบ่งรายการครึ่งหนึ่งทุกขั้นตอน ดังนั้นจึงต้องการการเปรียบเทียบไม่เกิน $\lceil \log_2 1000 \rceil = 10$ — นี่คือ $O(\log n)$ การเพิ่มขนาดรายการเป็นสองเท่าเป็น $2000$ รายการ ทำให้การค้นหาแบบทวิภาคเพิ่มเพียง หนึ่ง การเปรียบเทียบ แต่การค้นหาแบบเชิงเส้นอาจเพิ่มอีกสูงสุดถึง $1000$ — นั่นคือเหตุผลที่ว่าความซับซ้อนของการเติบโต ไม่ใช่ความเร็วสัมบูรณ์ ที่ตัดสินผู้ชนะเมื่อขยายสเกล

การอธิบายลำดับความซับซ้อน O(1): เวลาเป็น ค่าคงที่ ไม่ขึ้นอยู่กับจำนวนรายการ (การpushลงบนส택, การอ่านองค์ประกอบในอาร์เรย์) O($\log n$): เวลาเพิ่มขึ้นตาม ลอการิทึม ของจำนวนรายการ ดังนั้นการ เพิ่มขนาด ข้อมูลเป็นสองเท่าจะเพิ่มขั้นตอนเพิ่มเติมที่แน่นอนเพียงเล็กน้อย (การค้นหาแบบทวิภาค) O($n$): เวลาเพิ่มขึ้น ตามสัดส่วน กับจำนวนรายการ (การค้นหาแบบเชิงเส้น, การวนผ่านรายการหนึ่งรอบ) O($n \log n$): แย่กว่าเชิงเส้นเล็กน้อย (การจัดเรียงที่มีประสิทธิภาพ) O($n^{2}$): เวลาเพิ่มขึ้นตาม กำลังสอง ของจำนวนรายการ ดังนั้นการเพิ่มขนาดข้อมูลเป็นสองเท่าจะทำให้เวลาเพิ่มขึ้นสี่เท่า (บับเบิลและอินเซอร์ชันโซตซ์) "ระบุ Big O ของการค้นหาแบบทวิภาคสำหรับ Names[0:99]" ตอบด้วย $O(\log n)$ และ "อธิบายความหมาย" ตามข้างต้น; Big O วัดว่าเวลาหรือหน่วยความจำ ขยายตัวอย่างไร ไม่ใช่เวลาจริง

กราฟแสดงเวลาในการทำงานเทียบกับขนาดข้อมูลเข้า n สำหรับลำดับทั่วไป: O(1) และ O(log n) ค่อนข้างราบเรียบ, O(n) ขึ้นอย่างเบามือ, O(n log n) เหนียวขึ้น, และ O(n²) ขึ้นสูงที่สุด
การเปรียบเทียบลำดับการเติบโตทั่วไป: ลำดับที่น้อยกว่าชนะเมื่อขยายสเกล
กราฟเส้นแสดงเวลาในการทำงานเทียบกับจำนวนองค์ประกอบ n: บับเบิลโซตซ์และอินเซอร์ชันโซตซ์พุ่งสูงขึ้นอย่างรวดเร็วในรูป O(n²) ในขณะที่ควิกโซตซ์คงระดับต่ำในรูป O(n log n)
การเพิ่มขึ้นของเวลาการจัดเรียงตามจำนวนองค์ประกอบ $n$: $O(n^2)$ โซตซ์พุ่งห่างจาก $O(n\log n)$ โซตซ์

ความซับซ้อนด้านพื้นที่

ความซับซ้อนด้านพื้นที่ คือหน่วยความจำเสริมที่ต้องการ บับเบิลและอินเซอร์ชันโซตซ์ใช้ O(1) เสริม (ทำใน-place); เมอร์จโซตซ์ใช้ O($n$); การเรียกซ้ำใช้หน่วยความจำสเกตที่แปรผันตามความลึก มีแลกเปลี่ยนระหว่างเวลาและหน่วยความจำเสมอ

เกณฑ์อื่นๆ

ความง่าย (เขียนโค้ดและดูแลรักษาง่าย), ความเสถียร, และการปรับตัว (เร็วขึ้นกับข้อมูลที่เรียงเกือบสมบูรณ์) อัลกอริทึมที่เหมาะสมขึ้นอยู่กับข้อมูลและข้อจำกัด

Explore · ⁨สำรวจ⁩

How running time grows with n · ⁨ความเร็วในการดำเนินการเพิ่มขึ้นตาม n อย่างไร⁩

Slide n upward and compare the curves: O(1) and O(log n) stay almost flat, O(n) rises steadily, O(n²) explodes. This is why Big-O — not a stopwatch — is how we compare algorithms on large inputs. · ⁨เลื่อนกราฟ n ขึ้นไปและเปรียบเทียบเส้นโค้ง: O(1) และ O(log n) จะราบเรียบเกือบคงที่, O(n) เพิ่มขึ้นอย่างต่อเนื่อง, O(n²) พุ่งสูงขึ้นอย่างรุนแรง นี่คือเหตุผลที่เราใช้ Big-O — ไม่ใช่ cronometer — ในการเปรียบเทียบอัลกอริทึมเมื่อมีอินพุตขนาดใหญ่⁩

Explore · ⁨สำรวจ⁩

Big-O growth · ⁨อัตราการเติบโตแบบ Big-O⁩

Change the input size n and compare how fast each algorithm's work grows — the idea behind time complexity. · ⁨เปลี่ยนขนาดอินพุต n แล้วเปรียบเทียบว่างานของแต่ละอัลกอริทึมเพิ่มขึ้นเร็วแค่ไหน — นี่คือแนวคิดของ ความซับซ้อนด้านเวลา⁩

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
time complexity/taɪm kəmˈpleksɪti/ ความซับซ้อนด้านเวลา
Big-O notation/bɪɡ əʊ nəʊˈteɪʃn/ Notation Big-O
space complexity/speɪs kəmˈpleksɪti/ ความซับซ้อนด้านพื้นที่
19.2

Recursion · ⁨การเรียกซ้ำ⁩

Syllabus · ⁨หลักสูตร⁩
English
Candidates should be able to: Notes and guidance
Show understanding of recursion Essential features of recursion How recursion is expressed in a programming language Write and trace recursive algorithms When the use of recursion is beneficial
Show awareness of what a compiler has to do to translate recursive programming code Use of stacks and unwinding
ไทย
ผู้เข้าสอบควรสามารถ: หมายเหตุและคำแนะนำ
แสดงความเข้าใจเกี่ยวกับ recursion คุณสมบัติสำคัญของ recursion การแสดง recursion ในภาษาการเขียนโปรแกรม เขียนและติดตาม recursive algorithms เมื่อใด是使用 recursion เป็นประโยชน์
แสดงความตระหนักถึงสิ่งที่คอมไพเลอร์ต้องทำเพื่อแปลรหัสโปรแกรมแบบ recursive การใช้ stacks และการ unwinding

Source: Cambridge International syllabus · ⁨แหล่งที่มา: หลักสูตร Cambridge International⁩

English
Recursion: the call stack winds up and unwinds

Recursive algorithms use recursion 递归: the routine calls itself with a smaller version of the same problem, until a base case 基本情形 ends the chain. It has two parts: the base case (small enough to solve directly — without it the recursion never stops) and the recursive case 递归情形 (reduce the input and call itself).

Factorial 阶乘:

Recursion is natural for self-similar problems: trees, divide-and-conquer 分治 (binary search, merge sort), and nested data. When it is a poor fit, a loop is usually cleaner.

"Describe what is meant by recursion" (two marks). A function or procedure that is defined in terms of itself: it calls itself from within its own body, with a smaller version of the problem each time, until a base case is reached. "State three essential features of recursion": (1) a base case (stopping condition) that returns a value without a further call; (2) a general case 一般情形 in which the routine calls itself; (3) each call moves the problem closer to the base case (the parameter is reduced), so that the recursion terminates. Some schemes add: values are returned as the calls unwind.

"Describe when the use of recursion is beneficial, and give an example." When the problem is naturally defined in terms of smaller versions of itself, so that the recursive solution is shorter, clearer and closer to the mathematical definition than a loop would be: a factorial or Fibonacci number, a binary search, traversing a binary tree, merge sort or quicksort, and processing nested structures such as folders within folders. It is a poor choice when the depth is large (the stack may overflow) or when the same sub-problem is computed many times (naive Fibonacci).

Tracing a recursive call

For Factorial(4): the calls go down to Factorial(1)=1, then unwinding multiplies back up: 2*1=2, 3*2=6, 4*6=24. Final result 24. Track each pending call on a stack.

Worked example. The function below is given without an explanation. Trace Unknown(3, 5) and state its output and return value.

Call 1: $X = 3, Y = 5$: $3 < 5$, output 8, call Unknown(4, 4). Call 2: $4 < 4$ is false, return 0. Unwinding: call 1 returns $0 + 1 = 1$. Output 8, return value 1. Write the trace as a table with a row per call (parameters, condition, output, what it returns), and do the returns from the deepest call upwards: that is the unwinding the mark scheme looks for.

Worked example (Fibonacci). Fib(n) returns n when n < 2, otherwise Fib(n - 1) + Fib(n - 2). Find Fib(5).

Fib(5) = Fib(4) + Fib(3); Fib(4) = Fib(3) + Fib(2); Fib(3) = Fib(2) + Fib(1); Fib(2) = Fib(1) + Fib(0) = 1 + 0 = 1. So Fib(3) = 1 + 1 = 2, Fib(4) = 2 + 1 = 3, Fib(5) = 3 + 2 = 5. The base case is reached many times (Fib(2) is computed three times), which is why this version is slow: it makes 15 calls for $n = 5$ and roughly doubles the calls for every increase in $n$.

Converting recursion to iteration. Every recursive routine can be rewritten with a loop, which uses less memory and is faster: keep a running result and loop from the base case upwards. Factorial as a loop:

Asked to change a recursive insertion sort or search into an iterative one, replace the self-call with a loop over the index that the recursion was stepping through, and turn the base case into the loop's exit condition.

Risks

  • infinite recursion if the base case is missed — crashes with a stack overflow 栈溢出.
  • high memory use for deep recursion.
  • slow if it repeats work (naive Fibonacci is exponential — use a loop or memoisation 记忆化).
ไทย
การเรียกซ้ำ: สเกตการเรียก-call stack พับและคลายออก

อัลกอริทึมแบบเรียกซ้ำ ใช้ การเรียกซ้ำ: ฟังก์ชันเรียกตัวเองด้วยปัญหาเดิมแต่ขนาดเล็กกว่า จนกระทั่ง เคสฐาน จะหยุดลูป มันมีสองส่วน: เคสฐาน (เล็กพอที่จะแก้ได้โดยตรง — หากไม่มีมันการเรียกซ้ำจะไม่เคยหยุด) และ เคสเรียกซ้ำ (ลดอินพุตลงแล้วเรียกตัวเอง)

แฟกทอเรียล:

FUNCTION Factorial(n : INTEGER) RETURNS INTEGER
    IF n = 0 OR n = 1 THEN
        RETURN 1
    ELSE
        RETURN n * Factorial(n - 1)
    ENDIF
ENDFUNCTION

การเรียกซ้ำเป็นธรรมชาติสำหรับปัญหาที่มีลักษณะคล้ายกันเอง: ต้นไม้, Divide-and-conquer (การค้นหาแบบทวิภาค, เมอร์จโซตซ์), และข้อมูลแบบซ้อนกัน เมื่อไม่เหมาะสม การใช้ลูปมักจะสะอาดกว่า

"อธิบายความหมายของการเรียกซ้ำ (2 คะแนน)." ฟังก์ชันหรือกระบวนการที่นิยามโดยอ้างอิงถึงตัวมันเอง: มัน เรียกตัวเอง จากภายในตัวมันเอง โดยส่งปัญหาที่มีขนาด เล็กลง ในแต่ละครั้ง จนกว่าจะถึงเคสฐาน "ระบุคุณสมบัติสำคัญสามประการของการเรียกซ้ำ": (1) เคสฐาน (เงื่อนไขหยุด) ที่คืนค่าโดยไม่มีการเรียกซ้ำเพิ่มเติม; (2) เคสทั่วไป ที่ฟังก์ชัน เรียกตัวเอง; (3) ทุกการเรียกทำให้ปัญหาลดระยะทาง ใกล้เคสฐานมากขึ้น (พารามิเตอร์ลดลง) เพื่อให้การเรียกซ้ำสิ้นสุด บางรูปแบบเพิ่ม: ค่าจะถูกคืนกลับมาเมื่อการเรียก คลายออก

"อธิบาย時機ที่ใช้การเรียกซ้ำจะเป็นประโยชน์ และยกตัวอย่าง" เมื่อปัญหามีการ นิยามตามธรรมชาติด้วยเวอร์ชันที่เล็กลงของมัน sehinggaวิธีแก้ด้วยการเรียกซ้ำจะ สั้นกว่า ชัดเจนกว่า และใกล้เคียงกับการนิยามทางคณิตศาสตร์ มากกว่าการใช้ลูป: แฟกทอเรียลหรือเลขฟิบوناชี, การค้นหาแบบทวิภาค, การ traverse ต้นไม้ทวิภาค, เมอร์จโซตซ์หรือควิกโซตซ์, และการประมวลผลโครงสร้างซ้อน เช่นโฟลเดอร์ในโฟลเดอร์ เป็นทางเลือกที่ไม่ดีเมื่อความลึกมีมาก (สเกตอาจล้น) หรือเมื่อปัญหาย่อยเดียวกันถูกคำนวณหลายครั้ง (ฟิบอนาชีแบบเริ่มต้น)

การติดตามการเรียกซ้ำ

สำหรับ Factorial(4): การเรียกจะลงไปที่ Factorial(1)=1, จากนั้น การคืนค่า จะคูณกลับขึ้นไป: 2*1=2, 3*2=6, 4*6=24. ผลลัพธ์สุดท้าย 24. ติดตามการเรียกที่ยังค้างอยู่ในสตacks

ตัวอย่างฝึกหัด ฟังก์ชันด้านล่างให้มาโดยไม่มีการอธิบาย จงติดตาม Unknown(3, 5) และระบุเอาต์พุตและค่าที่คืนกลับมา

FUNCTION Unknown(BYVAL X, BYVAL Y : INTEGER) RETURNS INTEGER
    IF X < Y THEN
        OUTPUT X + Y
        RETURN Unknown(X + 1, Y - 1) + 1
    ELSE
        RETURN 0
    ENDIF
ENDFUNCTION

Call 1: $X = 3, Y = 5$: $3 < 5$, เอาต์พุต 8, เรียก Unknown(4, 4). Call 2: $4 < 4$ เป็นเท็จ, คืนค่า 0. คลายออก: call 1 คืนค่า $0 + 1 = 1$. เอาต์พุต 8, ค่าที่คืน回来 1. เขียนการติดตามเป็นตารางโดยมีแถวต่อหนึ่งการเรียก (พารามิเตอร์, เงื่อนไข, เอาต์พุต,返回值ที่คืน回来), และทำการคืนค่าจาก deepest call ขึ้นไป: นั่นคือการคลายออกที่คะแนนมาตรฐานมองหา

ตัวอย่างฝึกหัด (Fibonacci). Fib(n) คืนค่า n เมื่อ n < 2, มิฉะนั้นคืนค่า Fib(n - 1) + Fib(n - 2). หา Fib(5).

Fib(5) = Fib(4) + Fib(3); Fib(4) = Fib(3) + Fib(2); Fib(3) = Fib(2) + Fib(1); Fib(2) = Fib(1) + Fib(0) = 1 + 0 = 1. ดังนั้น Fib(3) = 1 + 1 = 2, Fib(4) = 2 + 1 = 3, Fib(5) = 3 + 2 = 5. เคสฐานถูกเข้าถึง หลาย ครั้ง (Fib(2) ถูกคำนวณสามครั้ง) ซึ่งเป็นสาเหตุที่ทำให้เวอร์ชันนี้ช้า: มันทำการเรียก 15 ครั้งสำหรับ $n = 5$ และประมาณการ doubles จำนวนการเรียกสำหรับทุกการเพิ่มขึ้นใน $n$.

การเปลี่ยนจากเรคอร์ชันเป็นอิตอเรชัน ฟังก์ชันแบบเรคอร์ชันทุกตัวสามารถเขียนใหม่โดยใช้ลูป ซึ่งใช้หน่วยความจำน้อยลงและทำงานได้เร็วขึ้น: เก็บผลลัพธ์ที่สะสมไว้และวนลูปตั้งแต่เคสพื้นฐานขึ้นไปabove Factorial ในรูปของลูป:

FUNCTION Factorial(N : INTEGER) RETURNS INTEGER
    DECLARE Result, Count : INTEGER
    Result ← 1
    FOR Count ← 2 TO N
        Result ← Result * Count
    NEXT Count
    RETURN Result
ENDFUNCTION

เมื่อถูกขอให้เปลี่ยนอัลกอริทึมการจัดเรียงแบบแทรกหรือการค้นหาแบบเรคอร์ชันให้เป็นแบบอิตারেทีทีทิก, ให้แทนการเรียกตัวเองด้วยลูปที่วนตามดัชนีที่เรคอร์ชันกำลังก้าวผ่าน และแปลงเคสพื้นฐานให้เป็นเงื่อนไขการออกของลูป

Stack ของการเรียก Factorial(4): การเรียกแต่ละครั้งจะดันเฟรมลง menuju base case Factorial(1)=1, จากนั้น stack จะคลายออก (unwind) ส่งค่ากลับ 2 = 2 คูณ 1, 6 = 3 คูณ 2 และ 24 = 4 คูณ 6
Recurion ใช้ call stack: การเรียกจะดัน frames ลงสู่ base case แล้วการส่งค่ากลับจะคลาย Stack ขึ้นไปข้างบน

ความเสี่ยง

  • infinite recursion หากพลาดเคสพื้นฐาน — ระบบจะหยุดทำงานพร้อมข้อความ stack overflow
  • ใช้หน่วยความจำสูงสำหรับการทำ recursive ลึก
  • ช้าหากมีการทำงานซ้ำซ้อน (Fibonacci แบบ naive เป็น exponential — ควรใช้ loop หรือ memoisation)
Explore · ⁨สำรวจ⁩

Recursion unwinds from the leaves up · ⁨Recursion ถอดออกจากใบไม้ขึ้นด้านบน⁩

Step through fib(4) in the order the calls actually finish: the leaves (base cases) resolve first, then each parent combines its children. Notice fib(2) is computed twice — that repeated work is why naive recursion is slow. · ⁨ผ่าน fib(4) ตามลำดับที่เรียกใช้งานเสร็จจริง: ใบ (base cases) แก้ไขก่อน จากนั้นแต่ละแม่รวมลูกหลานของตน หมายเหตุ fib(2) คำนวณสองครั้ง — งานซ้ำนี้คือเหตุผลว่าทำไม naive recursion จึงช้า⁩

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
recursion/rɪˈkɜːʃn/ การเรียกซ้ำ
call stack/kɔːl stæk/ call stack memory
base case/beɪs keɪs/ กรณีฐาน (Base Case)
recursive case/rɪˈkɜːsɪv keɪs/ กรณีเรียกตัวเอง (Recursive Case)
factorial/fækˈtɔːrɪəl/ แฟกทอเรียล
divide-and-conquer/dɪˈvaɪd ænd ˈkɒŋkə/ แบ่งและพิชิต
stack overflow/stæk ˌəʊvəˈfləʊ/ stack overflow error
memoisation/ˌmeməʊaɪˈzeɪʃn/ memoization technique
19.2

What the compiler does for recursive code · ⁨สิ่งที่คอมไพล์เตอร์ทำต่อโค้ดแบบ recursive⁩

English

Recursion needs each call to have its own copy of its parameters 参数 and local variables 局部变量. The compiler keeps these on the call stack 调用栈. For each call it pushes a stack frame 栈帧 holding the parameters, the local variables, and the return address 返回地址 (where to resume in the caller). When the function returns, the return value is handed back, the frame is popped, and control resumes at the return address.

Because each call has its own frame, recursive calls don't trample each other's variables. The stack can grow large for deep recursion, which is why very deep recursion may overflow it. This is the same call-and-return mechanism used for ordinary (non-recursive) calls — there is no special "recursion mechanism".

"Explain why a stack is suitable for implementing recursion" (three marks). Each recursive call must save its return address, its parameters and its local variables, and the calls are completed in the reverse order to that in which they were made (the last call made is the first to finish), which is exactly the last in, first out behaviour of a stack: each new call pushes a frame, and each return pops the most recent frame, restoring the caller's state and telling it where to continue. This is the compiler's job when it translates recursive code: it generates the push of a stack frame on every call and the pop on every return, and the frames are unwound as the results come back.

ไทย

Recursive ต้องการให้ แต่ละการเรียกมีสำเนาของตัวเอง ของ parameters และ local variables คอมไพล์เตอร์จะเก็บสิ่งเหล่านี้ไว้ใน call stack สำหรับแต่ละการเรียก มันจะดัน stack frame ที่เก็บ parameters, local variables และ return address (ตำแหน่งที่จะกลับไปดำเนินการต่อใน caller) เมื่อฟังก์ชันส่งค่ากลับ ค่าที่ได้จะถูกส่งคืน frame ถูกดึงออก และควบคุมงานจะกลับมาดำเนินต่อที่ return address

เนื่องจากแต่ละการเรียกมี frame ของตัวเอง การเรียกแบบ recursive จึงไม่รบกวนตัวแปรของกันและกัน Stack สามารถขยายใหญ่ได้สำหรับการทำ递归ลึก ซึ่งเป็นเหตุผลว่าทำไมการทำ递归ลึกมากอาจทำให้ stack overflowing ได้ นี่คือกลไก call-and-return เดียวกับการเรียกทั่วไป (non-recursive) ไม่มี "recursion mechanism" โดยเฉพาะ

"อธิบายว่าทำไม stack จึงเหมาะสำหรับการ implement递归 (สามคะแนน)**

Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
parameters/pəˈræmɪtəz/ parameters
local variables/ˈləʊkl ˈveərɪəblz/ ตัวแปรท้องถิ่น
stack frame/stæk freɪm/ stack frame
return address/rɪˈtɜːn əˈdres/ ที่อยู่กลับคืน
19.2

Definitions the examiner accepts · ⁨คำนิยามที่ผู้สอบยอมรับ⁩

English

A definition question is marked against fixed wording. Learn these exactly, and give one answer only.

Term Definition
linear search checking each item in turn from the start until the target is found or the end is reached
binary search repeatedly comparing the target with the middle item of a sorted list and discarding the half that cannot contain it
bubble sort repeatedly passing through the list, swapping adjacent items that are in the wrong order, until a pass makes no swaps
insertion sort taking each item in turn and inserting it into its correct place among the items already sorted
abstract data type a collection of data and the operations that can be performed on it, defined independently of how it is stored
stack a last-in-first-out structure with push and pop at the top
queue a first-in-first-out structure with items added at the rear and removed from the front
linked list a sequence of nodes, each holding data and a pointer to the next node, with a start pointer
binary tree nodes each holding data and pointers to a left subtree of smaller values and a right subtree of larger values
Big O notation a way of classifying the time (or memory) an algorithm needs by how it grows with the size of the input
recursion a routine that calls itself with a smaller version of the problem until a base case stops the calls
base case the condition under which a recursive routine returns without calling itself
unwinding the returns of a chain of recursive calls, from the deepest call back to the first, as the stack frames are popped
ไทย

คำถามคำนิยามจะให้คะแนนตามข้อความที่กำหนดไว้你必须 exact. เรียนรู้ให้ถูกต้องและตอบเพียงคำตอบเดียวเท่านั้น

พจน์ นิยาม
linear search ตรวจสอบแต่ละรายการทีละรายการจากต้นจนกว่าจะพบเป้าหมายหรือถึงท้ายรายการ
binary search เปรียบเทียบเป้าหมายกับรายการตรงกลางของชุดข้อมูลที่เรียงลำดับแล้วซ้ำๆ และทิ้งครึ่งที่ไม่อาจมีเป้าหมายอยู่
bubble sort ผ่านรายการซ้ำๆ สลับรายการที่อยู่ติดกันที่มีลำดับผิด จนกว่าการผ่านครั้งหนึ่งจะไม่มีการสลับเลย
insertion sort นำรายการมาทีละรายการแล้วใส่เข้าไปในตำแหน่งที่ถูกต้องในระหว่างรายการที่เรียงลำดับไว้แล้ว
abstract data type ชุดข้อมูลและการดำเนินการที่สามารถทำได้กับมัน โดยนิยามแยกออกจากวิธีการจัดเก็บ
stack โครงสร้างแบบ last-in-first-out ที่มี push และ pop ที่ด้านบน
queue โครงสร้างแบบ first-in-first-out ที่เพิ่มรายการที่ด้านหลังและลบรายการที่ด้านหน้า
linked list ลำดับของโหนด แต่ละโหนดมีข้อมูลและพอยน์เตอร์ไปยังโหนดถัดไป พร้อมพอยน์เตอร์เริ่มต้น
binary tree โหนดแต่ละตัวมีข้อมูลและพอยน์เตอร์ไปยัง subtree ซ้ายที่มีค่าน้อยกว่า และ subtree ขวาที่มีค่ามากกว่า
Big O notation วิธีการจัดหมวดหมู่เวลา (หรือหน่วยความจำ) ที่อัลกอริทึมต้องการโดยดูจากการเติบโตเมื่อขนาดข้อมูลเข้าเพิ่มขึ้น
recursion Routine ที่เรียกตัวเองด้วยปัญหาที่เล็กลงเรื่อยๆ จนกว่า base case จะหยุดการเรียก
base case เงื่อนไขที่ routine แบบ recursive ส่งค่ากลับโดยไม่เรียกตัวเอง
unwinding การส่งค่ากลับของ chain ของ recursive calls จาก call ที่ลึกที่สุดกลับไปยัง call แรก เมื่อ stack frames ถูกดึงออก
19.2

Exam tips · ⁨ข้อแนะนำสำหรับการสอบ⁩

English
  • Searches: linear needs no order and O($n$); binary needs a sorted array, halves each time and is O($\log n$). Know both algorithms by heart, including the bounds and the flag.
  • Sorts: bubble with a swapped flag, insertion with a key that shifts larger items right; both O($n^{2}$) worst, O($n$) on sorted data. Performance depends on the number of items and how ordered they are.
  • ADT implementations are pointer bookkeeping: a top pointer; front, rear and count with MOD; start, pointers and a free list; root with left and right pointers. Always check for full and empty.
  • Big O is about scaling: constant, logarithmic, linear, square. Say "doubling the data adds one comparison" for a binary search.
  • Recursion: base case, general case, progress towards the base case; beneficial when the problem is defined in terms of itself; a stack holds the return addresses and variables because calls return in reverse order. Trace with a table and unwind from the deepest call.

Common mistakes

  • Using a binary search on unsorted data, or on a linked list; and setting Lower ← Mid instead of Mid + 1, which loops for ever.
  • A bubble sort inner loop that runs to the end of the array every pass, or a swap without a temporary variable.
  • A push or enqueue that does not test for full, or a pop or dequeue that does not test for empty.
  • Moving the queue's front pointer without MOD in a circular queue, or treating front = rear as always meaning empty.
  • Inserting into a linked list by shifting the array contents; only the pointers change.
  • A recursive function with no base case, or one whose recursive call does not make the problem smaller.
  • Tracing a recursive call but forgetting to add the pending work on the way back up.
  • Answering "why a stack" with "because it is fast"; the reason is the last-in-first-out order of the returns.
ไทย
  • Searches: linear ไม่จำเป็นต้องมีลำดับและ O($n$); binary ต้องใช้ array ที่เรียงลำดับแล้ว减半每次都 и O($\log n$). ต้องจำอัลกอริทึมทั้งสองนี้ให้ขึ้นใจ รวมถึงขอบเขตและ flag
  • Sorts: bubble มี swapped flag, insertion มี key ที่เลื่อนรายการที่ใหญ่กว่าไปทางขวา; ทั้งคู่ O($n^{2}$) ในกรณี worst, O($n$) บนข้อมูลที่เรียงลำดับแล้ว ประสิทธิภาพขึ้นอยู่กับจำนวนรายการและความเรียงลำดับของข้อมูล
  • ADT implementations คือ pointer bookkeeping: top pointer; front, rear และ count กับ MOD; start, pointers และ free list; root พร้อม left และ right pointers ต้องตรวจสอบเสมอว่าเต็มหรือว่าง
  • Big O เกี่ยวกับการ scaling: constant, logarithmic, linear, square บอกว่า "การเพิ่มข้อมูลเป็นสองเท่า会增加 one comparison" สำหรับ binary search
  • Recursion: base case, general case, การก้าวหน้า toward base case; เป็นประโยชน์เมื่อปัญหานิยามด้วยตัวเอง; stack เก็บ return addresses และ variables เพราะ calls ส่งค่ากลับใน reverse order Trace ด้วยตารางและ unwind จาก call ที่ลึกที่สุด

ข้อผิดพลาดที่พบบ่อย

  • การใช้ binary search บนข้อมูลที่ไม่ได้เรียงลำดับ หรือบน linked list; และการตั้ง Lower ← Mid แทน Mid + 1 ซึ่งจะทำให้ลูปวนตลอดไป
  • Inner loop ของ bubble sort ที่วนไปจนสุด array ทุกครั้ง หรือการ swap โดยไม่มี temporary variable
  • Push หรือ enqueue ที่ไม่ทดสอบว่าเต็ม, หรือ pop หรือ dequeue ที่ไม่ทดสอบว่าว่าง
  • การย้าย front pointer ของ queue โดยไม่มี MOD ใน circular queue, หรือการตีความ front = rear ว่าหมายถึงว่างเสมอ
  • การแทรกใน linked list โดยการเลื่อน contents ของ array; มีการเปลี่ยนเฉพาะ pointers เท่านั้น
  • Recursive function ที่ไม่มี base case, หรือหนึ่ง whose recursive call does not make the problem smaller.
  • การติดตามการเรียกใช้ฟังก์ชันซ้ำ (recursive call) แต่ลืมเพิ่มภาระงานที่ยังค้างอยู่ในการย้อนกลับขึ้น
  • ตอบคำถามว่า "ทำไมต้องใช้ส택" ว่า "เพราะมันเร็ว";เหตุผลที่แท้จริงคือลำดับการคืนค่าแบบ Last-In-First-Out (LIFO)
Vocabulary · ⁨คำศัพท์⁩ Train · ⁨ฝึกฝน⁩
English ไทย
general case/ˈdʒenərəl keɪs/ กรณีทั่วไป

Interactive lessons on this topic · ⁨บทเรียนเชิงโต้ตอบสำหรับหัวข้อนี้⁩

Work through it step by step, with instant-check exercises. · ⁨ทำทีละขั้นตอน พร้อมแบบฝึกหัดตรวจสอบผลทันที⁩

Past Papers · ⁨ข้อสอบย้อนหลัง⁩

More topics in A-Level Computer Science · ⁨Computer Science A-Level⁩ · ⁨หัวข้อเพิ่มเติมใน A-Level Computer Science · ⁨Computer Science A-Level⁩⁩

Log in or create account · ⁨เข้าสู่ระบบหรือสร้างบัญชี⁩

IGCSE, A-Level & AP