0Pricing
Lua Academy · บทเรียน

การเรียกซ้ำใน Lua

สร้างอัลกอริทึมแบบเรียกซ้ำและทำความเข้าใจการเพิ่มประสิทธิภาพการเรียกท้ายฟังก์ชันของ Lua

การเรียกซ้ำใน Lua เป็นบทเรียน Lua Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Lua Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Lua Academy มีบทเรียนทั้งหมด 4 บทเรียน

การเรียกซ้ำคืออะไร

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

local function countdown(n)
  if n <= 0 then
    print("Blast off!")
    return
  end
  print(n)
  countdown(n - 1)
end

countdown(5)
-- 5, 4, 3, 2, 1, Blast off!

แฟกทอเรียล

แฟกทอเรียลเป็นตัวอย่างคลาสสิกของการเรียกซ้ำ: n! = n × (n-1)! โดยมีกรณีฐานเป็น 0! = 1 การเรียกแต่ละครั้งจะลดค่า n ลง 1 จนถึงกรณีฐาน จากนั้นผลลัพธ์จะ "คลายกลับ" ขึ้นมาตามสแตกการเรียก โดยคูณค่าไปด้วย

local function factorial(n)
  if n <= 1 then return 1 end
  return n * factorial(n - 1)
end

print(factorial(1))   -- 1
print(factorial(5))   -- 120
print(factorial(10))  -- 3628800

ฟีโบนัชชี

ลำดับฟีโบนัชชีเป็นอีกตัวอย่างคลาสสิก: fib(n) = fib(n-1) + fib(n-2) การเรียกซ้ำแบบพื้นฐานมีเวลาเพิ่มขึ้นแบบเอ็กซ์โพเนนเชียล เพราะคำนวณค่าเดิมซ้ำหลายครั้ง เราจะเห็นการจดจำผลลัพธ์เป็นวิธีแก้ แต่ขอเริ่มจากรูปแบบพื้นฐานก่อน

local function fib(n)
  if n <= 1 then return n end
  return fib(n - 1) + fib(n - 2)
end

for i = 0, 10 do
  io.write(fib(i) .. " ")
end
print()
-- 0 1 1 2 3 5 8 13 21 34 55

การเรียกซ้ำแบบจดจำผลลัพธ์

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

local memo = {}
local function fib(n)
  if memo[n] then return memo[n] end
  if n <= 1 then return n end
  local result = fib(n-1) + fib(n-2)
  memo[n] = result
  return result
end

print(fib(40))  -- 102334155  (fast!)

การเพิ่มประสิทธิภาพการเรียกแบบหาง

Lua ทำการเพิ่มประสิทธิภาพการเรียกแบบหาง (TCO): เมื่อการกระทำสุดท้ายของฟังก์ชันคือการเรียกฟังก์ชันอื่น Lua จะนำเฟรมสแตกปัจจุบันกลับมาใช้ใหม่ ทำให้ฟังก์ชันที่เรียกซ้ำแบบหางใช้พื้นที่สแตก O(1) การเรียกซ้ำทุกแบบไม่ใช่การเรียกซ้ำแบบหาง — การเรียกนั้นต้องเป็นสิ่งสุดท้ายในฟังก์ชัน

-- Tail-recursive factorial using accumulator
local function factTail(n, acc)
  acc = acc or 1
  if n <= 1 then return acc end
  return factTail(n - 1, n * acc)  -- tail call
end

print(factTail(10))  -- 3628800
-- Can handle very large n without stack overflow

การท่องผ่านต้นไม้

การเรียกซ้ำเหมาะกับการแสดงการท่องผ่านต้นไม้ ต้นไม้คือตารางที่มีฟิลด์ value, left และ right การท่องแบบพรีออร์เดอร์ อินออร์เดอร์ และโพสต์ออร์เดอร์ต่างกันเพียงจังหวะที่ประมวลผลค่าเมื่อเทียบกับการเรียกซ้ำ

local function inorder(node)
  if node == nil then return end
  inorder(node.left)
  io.write(node.value .. " ")
  inorder(node.right)
end

local tree = {
  value=4,
  left={value=2, left={value=1}, right={value=3}},
  right={value=6, left={value=5}, right={value=7}}
}
inorder(tree)  -- 1 2 3 4 5 6 7

การเรียกซ้ำร่วมกัน

ฟังก์ชันสองฟังก์ชันสามารถเรียกหากันได้ (การเรียกซ้ำร่วมกัน) ใน Lua คุณต้องประกาศล่วงหน้า: ประกาศตัวแปรเฉพาะที่ก่อน แล้วจึงกำหนดฟังก์ชัน วิธีนี้ทำให้แต่ละฟังก์ชันอ้างอิงตัวแปรของอีกฟังก์ชันได้ เพราะตัวแปรนั้นอยู่ในขอบเขตแล้ว

local isEven, isOdd

isEven = function(n)
  if n == 0 then return true end
  return isOdd(n - 1)
end

isOdd = function(n)
  if n == 0 then return false end
  return isEven(n - 1)
end

print(isEven(10))  -- true
print(isOdd(7))    -- true

การคัดลอกเชิงลึกแบบเรียกซ้ำ

การเรียกซ้ำเหมาะอย่างยิ่งกับการทำงานกับโครงสร้างซ้อนกัน การคัดลอกตารางแบบเชิงลึกหมายถึงการคัดลอกเนื้อหาและคัดลอกตารางที่ซ้อนกันอยู่แบบเรียกซ้ำด้วย (เพื่อให้การแก้ไขสำเนาไม่ส่งผลต่อต้นฉบับ)

local function deepCopy(orig)
  local copy
  if type(orig) == "table" then
    copy = {}
    for k, v in pairs(orig) do
      copy[deepCopy(k)] = deepCopy(v)
    end
    setmetatable(copy, getmetatable(orig))
  else
    copy = orig
  end
  return copy
end

local a = {1, {2, 3}}
local b = deepCopy(a)
b[2][1] = 99
print(a[2][1])  -- 2 (unchanged)

อัลกอริทึมเติมพื้นที่

การเติมพื้นที่ (ใช้ในโปรแกรมระบายสีและแผนที่เกม) เหมาะกับการเรียกซ้ำโดยธรรมชาติ เริ่มจากช่องหนึ่ง ทำเครื่องหมายช่องนั้น แล้วเติมช่องข้างเคียงที่ยังไม่เคยเยี่ยมชมทีละช่องแบบเรียกซ้ำ การเรียกซ้ำจะสิ้นสุดเมื่อพบขอบเขตหรือช่องที่เยี่ยมชมแล้ว

local grid = {
  {0,0,0,1,0},
  {0,1,0,1,0},
  {0,1,1,1,0},
  {0,0,0,0,0},
}

local function fill(g, r, c)
  if r<1 or r>#g or c<1 or c>#g[r] then return end
  if g[r][c] ~= 0 then return end
  g[r][c] = 2  -- mark visited
  fill(g,r-1,c); fill(g,r+1,c)
  fill(g,r,c-1); fill(g,r,c+1)
end

fill(grid, 1, 1)
print(grid[1][1], grid[2][1])  -- 2  2

การตระหนักถึงความลึกของสแตก

สแตกการเรียกเริ่มต้นของ Lua มีขีดจำกัด (โดยทั่วไปประมาณ 200 ระดับสำหรับการเรียกที่ไม่ใช่แบบหาง) การเรียกซ้ำเชิงลึกที่ไม่ใช่แบบหางจะทำให้เกิดข้อผิดพลาด "สแตกโอเวอร์โฟลว์" วิธีแก้คือเปลี่ยนเป็นการเรียกซ้ำแบบหาง ใช้สแตกที่จัดการเอง (ตาราง) หรือใช้โครูทีนสำหรับงานวนซ้ำขนาดใหญ่

-- Simulate deep recursion danger
local function depth(n)
  if n == 0 then return "done" end
  return depth(n - 1)  -- tail call, safe
end
print(depth(100000))  -- done  (tail call, no overflow)

-- Non-tail: limited depth
local function countDown(n)
  if n == 0 then return 0 end
  return 1 + countDown(n - 1)  -- NOT a tail call
end
-- countDown(10000) would stack overflow

ตรวจสอบอย่างรวดเร็ว

ข้อใดต่อไปนี้เป็นการเรียกแบบหางที่ถูกต้องใน Lua

ทบทวน: การเรียกซ้ำ

สรุป:

  • ฟังก์ชันที่เรียกซ้ำทุกฟังก์ชันต้องมีกรณีฐานและกรณีเรียกซ้ำ
  • การเรียกแบบหาง (การกระทำสุดท้ายคือการเรียก) ได้รับการเพิ่มประสิทธิภาพโดย Lua — ใช้สแตก O(1)
  • การจดจำผลลัพธ์เปลี่ยนการเรียกซ้ำแบบเอ็กซ์โพเนนเชียลให้เป็นเชิงเส้น
  • การเรียกซ้ำร่วมกันต้องมีการประกาศล่วงหน้า
  • การเรียกซ้ำเชิงลึกบนต้นไม้หรือกราฟเป็นเรื่องเหมาะสมตามธรรมชาติ แต่ควรระวังความลึกของสแตกสำหรับการเรียกที่ไม่ใช่แบบหาง

คำถามที่พบบ่อย

บทเรียน “การเรียกซ้ำใน Lua” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเรียกซ้ำใน Lua” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Lua Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Lua Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเรียกซ้ำใน Lua”

สร้างอัลกอริทึมแบบเรียกซ้ำและทำความเข้าใจการเพิ่มประสิทธิภาพการเรียกท้ายฟังก์ชันของ Lua คุณปฏิบัติ Lua Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Lua Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Lua Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การเรียกซ้ำใน Lua” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Lua Academy นี้ได้ไหม

ได้ บทเรียน Lua Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การกำหนดและเรียกใช้ฟังก์ชัน
  2. ค่าที่ส่งคืนหลายค่า
  3. อาร์กิวเมนต์จำนวนแปรผันและตัวดำเนินการ ...
  4. การเรียกซ้ำใน Lua
← กลับไปที่ Lua Academy