diff --git a/README.md b/README.md index 957511155d..d0ead87d6b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ +

diff --git a/codes/lua/chapter_array_and_linkedlist/array.lua b/codes/lua/chapter_array_and_linkedlist/array.lua new file mode 100644 index 0000000000..519dee5b02 --- /dev/null +++ b/codes/lua/chapter_array_and_linkedlist/array.lua @@ -0,0 +1,131 @@ +-- @script array.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 随机访问元素 +--- @param nums table 数组 +--- @return number 随机访问到的元素 +local function random_access(nums) + -- 在区间 [1, #nums] 中随机抽取一个数字 + local random_index = math.random(1, #nums) + -- 获取并返回随机元素 + local random_num = nums[random_index] + return random_num +end + +--- 扩展数组长度 +--- 请注意,Lua 的 table 是动态的,可以直接扩展 +--- 为了方便学习,本函数将 table 看作长度不可变的数组 +--- @param nums table 数组 +--- @param enlarge number 扩展的长度 +--- @return table 扩展后的新数组 +local function extend(nums, enlarge) + -- 初始化一个扩展长度后的数组 + local res = {} + -- 将原数组中的所有元素复制到新数组 + for i = 1, #nums do + res[i] = nums[i] + end + -- 填充新增的位置为0 + for i = #nums + 1, #nums + enlarge do + res[i] = 0 + end + -- 返回扩展后的新数组 + return res +end + +--- 在数组的索引 index 处插入元素 num +--- @param nums table 数组 +--- @param num number 要插入的元素 +--- @param index number 插入位置的索引 +local function insert(nums, num, index) + -- 把索引 index 以及之后的所有元素向后移动一位 + for i = #nums, index, -1 do + nums[i + 1] = nums[i] + end + -- 将 num 赋给 index 处的元素 + nums[index] = num +end + +--- 删除索引 index 处的元素 +--- @param nums table 数组 +--- @param index number 要删除元素的索引 +local function remove(nums, index) + -- 把索引 index 之后的所有元素向前移动一位 + for i = index, #nums - 1 do + nums[i] = nums[i + 1] + end + -- 将最后一个元素设为nil + nums[#nums] = nil +end + +--- 遍历数组 +--- @param nums table 数组 +local function traverse(nums) + local count = 0 + -- 通过索引遍历数组 + for i = 1, #nums do + count = count + nums[i] + end + -- 直接遍历数组元素 + for _, num in ipairs(nums) do + count = count + num + end + -- 同时遍历数据索引和元素(在 Lua 中 ipairs 已经提供了这个功能) + for i, num in ipairs(nums) do + count = count + nums[i] + count = count + num + end +end + +--- 在数组中查找指定元素 +--- @param nums table 数组 +--- @param target number 要查找的元素 +--- @return number 元素的索引,未找到则返回 -1 +local function find(nums, target) + for i = 1, #nums do + if nums[i] == target then + return i + end + end + return -1 +end + +-- Driver Code +local function main() + -- 初始化数组 + local arr = { 0, 0, 0, 0, 0 } + print("数组 arr = [" .. table.concat(arr, ", ") .. "]") + local nums = { 1, 3, 2, 5, 4 } + print("数组 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 随机访问 + local random_num = random_access(nums) + print("在 nums 中获取随机元素 " .. random_num) + + -- 长度扩展 + nums = extend(nums, 3) + print("将数组长度扩展至 8 ,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 插入元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 3 处对应 lua 的索引 4 + insert(nums, 6, 4) + print("在索引 4 处插入数字 6 ,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 删除元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 2 处对应 lua 的索引 3 + remove(nums, 3) + print("删除索引 3 处的元素,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 遍历数组 + traverse(nums) + + -- 查找元素 + -- 注意:lua 数组索引从 1 开始,所以元素 3 的索引为 2 + local index = find(nums, 3) + print("在 nums 中查找元素 3 ,得到索引 = " .. index) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_array_and_linkedlist/linked_list.lua b/codes/lua/chapter_array_and_linkedlist/linked_list.lua new file mode 100644 index 0000000000..511dacc1f1 --- /dev/null +++ b/codes/lua/chapter_array_and_linkedlist/linked_list.lua @@ -0,0 +1,117 @@ +-- @script linked_list.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local ListNode = list_node.ListNode + +--- 在链表的节点 n0 之后插入节点 P +--- @param n0 ListNode 要插入位置的前一个节点 +--- @param P ListNode 要插入的节点 +local function insert(n0, P) + local n1 = n0.next + P.next = n1 + n0.next = P +end + +--- 删除链表的节点 n0 之后的首个节点 +--- @param n0 ListNode 要删除节点的前一个节点 +local function remove(n0) + -- n0 -> P -> n1 + local P = n0.next + if not P then + return + end + local n1 = P.next + n0.next = n1 +end + +--- 访问链表中索引为 index 的节点 +--- @param head ListNode 链表头节点 +--- @param index integer 要访问的索引 +--- @return ListNode|nil 找到的节点或nil +local function access(head, index) + for _ = 1, index do + if not head then + return nil + end + head = head.next + end + return head +end + +--- 在链表中查找值为 target 的首个节点 +--- @param head ListNode 链表头节点 +--- @param target integer 要查找的目标值 +--- @return number 节点索引,未找到返回-1 +local function find(head, target) + local index = 0 + while head do + if head.val == target then + return index + end + head = head.next + index = index + 1 + end + return -1 +end + +--- 打印链表 +--- @param head ListNode 链表头节点 +local function printLinkedList(head) + local current = head + local output = {} + while current do + table.insert(output, current.val) + current = current.next + end + print(table.concat(output, " -> ")) +end + +-- Driver Code +local function main() + -- 初始化链表 + -- 初始化各个节点 + local n0 = ListNode.new(1) + local n1 = ListNode.new(3) + local n2 = ListNode.new(2) + local n3 = ListNode.new(5) + local n4 = ListNode.new(4) + -- 构建节点之间的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初始化的链表为") + printLinkedList(n0) + + -- 插入节点 + local p = ListNode.new(0) + insert(n0, p) + print("插入节点后的链表为") + printLinkedList(n0) + + -- 删除节点 + remove(n0) + print("删除节点后的链表为") + printLinkedList(n0) + + -- 访问节点 + local node = access(n0, 3) + if node then + print("链表中索引 3 处的节点的值 = " .. node.val) + else + print("链表中索引 3 处的节点不存在") + end + + -- 查找节点 + local index = find(n0, 2) + print("链表中值为 2 的节点的索引 = " .. index) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_array_and_linkedlist/list.lua b/codes/lua/chapter_array_and_linkedlist/list.lua new file mode 100644 index 0000000000..dca44fc7e7 --- /dev/null +++ b/codes/lua/chapter_array_and_linkedlist/list.lua @@ -0,0 +1,67 @@ +-- @script: array.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- Driver Code +local function main() + -- 初始化列表 + local nums = { 1, 3, 2, 5, 4 } + print("\n列表 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 访问元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 1 处对应 lua 的索引 2 + local x = nums[2] + print("\n访问索引 2 处的元素,得到 x = " .. x) + + -- 更新元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 1 处对应 lua 的索引 2 + nums[2] = 0 + print("\n将索引 2 处的元素更新为 0 ,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 清空列表 + nums = {} + print("\n清空列表后 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 在尾部添加元素 + table.insert(nums, 1) + table.insert(nums, 3) + table.insert(nums, 2) + table.insert(nums, 5) + table.insert(nums, 4) + print("\n添加元素后 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 在中间插入元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 3 处对应 lua 的索引 4 + table.insert(nums, 4, 6) -- 在索引 4 处插入 6 + print("\n在索引 4 处插入数字 6 ,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 删除元素 + -- 注意:lua 数组索引从 1 开始,所以其他语言的索引 3 处对应 lua 的索引 4 + table.remove(nums, 4) -- 删除索引 4 处的元素 + print("\n删除索引 4 处的元素,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 通过索引遍历列表 + local count = 0 + for i = 1, #nums do + count = count + nums[i] + end + -- 直接遍历列表元素 + for _, num in ipairs(nums) do + count = count + num + end + + -- 拼接两个列表 + local nums1 = { 6, 8, 7, 10, 9 } + for _, num in ipairs(nums1) do + table.insert(nums, num) + end + print("\n将列表 nums1 拼接到 nums 之后,得到 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 排序列表 + table.sort(nums) + print("\n排序列表后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_array_and_linkedlist/my_list.lua b/codes/lua/chapter_array_and_linkedlist/my_list.lua new file mode 100644 index 0000000000..68db967850 --- /dev/null +++ b/codes/lua/chapter_array_and_linkedlist/my_list.lua @@ -0,0 +1,188 @@ +-- @script my_list.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class MyList +--- 列表类(动态数组实现) +--- @field _capacity integer 列表容量 +--- @field _arr table 列表数组 +--- @field _size integer 列表长度(当前元素数量) +--- @field _extend_ratio integer 每次列表扩容的倍数 +local MyList = {} +MyList.__index = MyList + +--- 构造方法 +--- @return MyList 新列表实例 +function MyList:new() + local obj = {} + setmetatable(obj, { + __index = self + }) + + -- 列表容量 + obj._capacity = 10 + -- 数组(存储列表元素) + obj._arr = {} + for i = 1, obj._capacity do + obj._arr[i] = 0 + end + -- 列表长度(当前元素数量) + obj._size = 0 + -- 每次列表扩容的倍数 + obj._extend_ratio = 2 + + return obj +end + +--- 获取列表长度(当前元素数量) +--- @return integer +function MyList:size() + return self._size +end + +--- 获取列表容量 +--- @return integer +function MyList:capacity() + return self._capacity +end + +--- 访问元素 +--- @param index integer +--- @return integer +function MyList:get(index) + -- 索引如果越界,则抛出异常,下同 + if index < 0 or index >= self._size then + error("索引越界") + end + return self._arr[index + 1] -- Lua 数组索引从1开始 +end + +--- 更新元素 +--- @param num integer +--- @param index integer +function MyList:set(num, index) + if index < 0 or index >= self._size then + error("索引越界") + end + self._arr[index + 1] = num -- Lua 数组索引从1开始 +end + +--- 在尾部添加元素 +--- @param num integer +function MyList:add(num) + -- 元素数量超出容量时,触发扩容机制 + if self:size() == self:capacity() then + self:extend_capacity() + end + self._arr[self._size + 1] = num + self._size = self._size + 1 +end + +--- 在中间插入元素 +--- @param num integer +--- @param index integer +function MyList:insert(num, index) + if index < 0 or index >= self._size then + error("索引越界") + end + -- 元素数量超出容量时,触发扩容机制 + if self._size == self:capacity() then + self:extend_capacity() + end + -- 将索引 index 以及之后的元素都向后移动一位 + for j = self._size, index + 1, -1 do -- Lua 数组索引从1开始 + self._arr[j + 1] = self._arr[j] + end + self._arr[index + 1] = num -- Lua 数组索引从1开始 + -- 更新元素数量 + self._size = self._size + 1 +end + +--- 删除元素 +--- @param index integer +--- @return integer +function MyList:remove(index) + if index < 0 or index >= self._size then + error("索引越界") + end + local num = self._arr[index + 1] -- Lua 数组索引从1开始 + -- 将索引 index 之后的元素都向前移动一位 + for j = index + 1, self._size - 1 do -- Lua 数组索引从1开始 + self._arr[j] = self._arr[j + 1] + end + -- 更新元素数量 + self._size = self._size - 1 + -- 返回被删除的元素 + return num +end + +--- 列表扩容 +function MyList:extend_capacity() + -- 新建一个长度为原数组 _extend_ratio 倍的新数组,并将原数组复制到新数组 + local new_capacity = self:capacity() * self._extend_ratio + local new_arr = {} + + -- 复制原数组元素 + for i = 1, self:capacity() do + new_arr[i] = self._arr[i] + end + -- 填充新增容量为0 + for i = self:capacity() + 1, new_capacity do + new_arr[i] = 0 + end + + self._arr = new_arr + -- 更新列表容量 + self._capacity = new_capacity +end + +--- 返回有效长度的列表 +--- @return table +function MyList:to_array() + local result = {} + for i = 1, self._size do + result[i] = self._arr[i] + end + return result +end + +-- Driver Code +local function main() + -- 初始化列表 + local nums = MyList:new() + -- 在尾部添加元素 + nums:add(1) + nums:add(3) + nums:add(2) + nums:add(5) + nums:add(4) + print(string.format("列表 nums = [%s] ,容量 = %d ,长度 = %d", table.concat(nums:to_array(), ", "), + nums:capacity(), nums:size())) + + -- 在中间插入元素 + nums:insert(6, 3) + print("在索引 3 处插入数字 6 ,得到 nums = [" .. table.concat(nums:to_array(), ", ") .. "]") + + -- 删除元素 + nums:remove(3) + print("删除索引 3 处的元素,得到 nums = [" .. table.concat(nums:to_array(), ", ") .. "]") + + -- 访问元素 + local num = nums:get(1) + print("访问索引 1 处的元素,得到 num = " .. num) + + -- 更新元素 + nums:set(0, 1) + print("将索引 1 处的元素更新为 0 ,得到 nums = [" .. table.concat(nums:to_array(), ", ") .. "]") + + -- 测试扩容机制 + for i = 0, 9 do + -- 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums:add(i) + end + print(string.format("扩容后的列表 [%s] ,容量 = %d ,长度 = %d", table.concat(nums:to_array(), ", "), + nums:capacity(), nums:size())) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/n_queens.lua b/codes/lua/chapter_backtracking/n_queens.lua new file mode 100644 index 0000000000..5a70ebf7db --- /dev/null +++ b/codes/lua/chapter_backtracking/n_queens.lua @@ -0,0 +1,102 @@ +-- @script n_queens.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:n 皇后 +--- @param row integer 当前处理的行 +--- @param n integer 棋盘大小 +--- @param state table 当前棋盘状态 +--- @param res table 存储所有解的列表 +--- @param cols table 列占用标记 +--- @param diags1 table 主对角线占用标记 +--- @param diags2 table 次对角线占用标记 +local function backtrack(row, n, state, res, cols, diags1, diags2) + -- 当放置完所有行时,记录解 + if row == n then + local solution = {} + for i = 1, n do + solution[i] = {} + for j = 1, n do + solution[i][j] = state[i][j] + end + end + table.insert(res, solution) + return + end + + -- 遍历所有列 + for col = 1, n do + -- 计算该格子对应的主对角线和次对角线 + local diag1 = row - col + n + local diag2 = row + col - 1 + + -- 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if not cols[col] and not diags1[diag1] and not diags2[diag2] then + -- 尝试:将皇后放置在该格子 + state[row + 1][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + + -- 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + + -- 回退:将该格子恢复为空位 + state[row + 1][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + end + end +end + +--- 求解 n 皇后问题 +--- @param n integer 棋盘大小 +--- @return table 所有解的列表 +local function n_queens(n) + -- 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + local state = {} + for i = 1, n do + state[i] = {} + for j = 1, n do + state[i][j] = "#" + end + end + + local cols = {} -- 记录列是否有皇后 + local diags1 = {} -- 记录主对角线上是否有皇后 + local diags2 = {} -- 记录次对角线上是否有皇后 + + for i = 1, n do + cols[i] = false + end + + for i = 1, 2 * n - 1 do + diags1[i] = false + diags2[i] = false + end + + local res = {} + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +end + +-- Driver Code +local function main() + local n = 4 + local res = n_queens(n) + + print(string.format("输入棋盘长宽为 %d", n)) + print(string.format("皇后放置方案共有 %d 种", #res)) + + for i, state in ipairs(res) do + print("--------------------") + for j = 1, #state do + print("['" .. table.concat(state[j], "', '") .. "']") + end + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/permutations_i.lua b/codes/lua/chapter_backtracking/permutations_i.lua new file mode 100644 index 0000000000..2e602909d3 --- /dev/null +++ b/codes/lua/chapter_backtracking/permutations_i.lua @@ -0,0 +1,70 @@ +-- @script permutations_i.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:全排列 I +--- @param state table 当前状态 +--- @param choices table 所有可选元素 +--- @param selected table 元素是否已被选择 +--- @param res table 存储所有结果的表 +local function backtrack(state, choices, selected, res) + -- 当状态长度等于元素数量时,记录解 + if #state == #choices then + -- 创建当前状态的副本并添加到结果中 + local state_copy = {} + for i, v in ipairs(state) do + state_copy[i] = v + end + table.insert(res, state_copy) + return + end + + -- 遍历所有选择 + for i, choice in ipairs(choices) do + -- 剪枝:不允许重复选择元素 + if not selected[i] then + -- 尝试:做出选择,更新状态 + selected[i] = true + table.insert(state, choice) + + -- 进行下一轮选择 + backtrack(state, choices, selected, res) + + -- 回退:撤销选择,恢复到之前的状态 + selected[i] = false + table.remove(state) + end + end +end + +--- 全排列 I +--- @param nums table 输入数组 +--- @return table 所有排列的结果 +local function permutations_i(nums) + local res = {} + local selected = {} + + -- 初始化selected表 + for i = 1, #nums do + selected[i] = false + end + + backtrack({}, nums, selected, res) + return res +end + +-- Driver Code +local function main() + local nums = { 1, 2, 3 } + local res = permutations_i(nums) + + print(string.format("输入数组 nums = [%s]", table.concat(nums, ", "))) + print("所有排列 res: [") + for _, permutation in ipairs(res) do + print(string.format(" [%s],", table.concat(permutation, ", "))) + end + print("]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/permutations_ii.lua b/codes/lua/chapter_backtracking/permutations_ii.lua new file mode 100644 index 0000000000..fffd30e58c --- /dev/null +++ b/codes/lua/chapter_backtracking/permutations_ii.lua @@ -0,0 +1,74 @@ +-- @script permutations_ii.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:全排列 II +--- @function backtrack +--- @param state table 当前状态(已选择元素) +--- @param choices table 所有可选元素 +--- @param selected table 标记元素是否已被选择 +--- @param res table 存储所有结果的表 +local function backtrack(state, choices, selected, res) + -- 当状态长度等于元素数量时,记录解 + if #state == #choices then + -- 创建当前状态的副本并添加到结果中 + local state_copy = {} + for i, v in ipairs(state) do + state_copy[i] = v + end + table.insert(res, state_copy) + return + end + + -- 遍历所有选择 + local duplicated = {} -- 用于记录当前层级已经选择过的元素值 + for i, choice in ipairs(choices) do + -- 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if not selected[i] and not duplicated[choice] then + -- 尝试:做出选择,更新状态 + duplicated[choice] = true -- 记录选择过的元素值 + selected[i] = true + table.insert(state, choice) + + -- 进行下一轮选择 + backtrack(state, choices, selected, res) + + -- 回退:撤销选择,恢复到之前的状态 + selected[i] = false + table.remove(state) + end + end +end + +--- 全排列 II +--- @param nums table 输入数组 +--- @return table 包含所有不重复排列的二维数组 +local function permutations_ii(nums) + local res = {} + local selected = {} + + -- 初始化selected表 + for i = 1, #nums do + selected[i] = false + end + + backtrack({}, nums, selected, res) + return res +end + +-- Driver Code +local function main() + local nums = { 1, 2, 2 } + + local res = permutations_ii(nums) + + print(string.format("输入数组 nums = [%s]", table.concat(nums, ", "))) + print("所有排列 res = [") + for _, permutation in ipairs(res) do + print(string.format(" [%s],", table.concat(permutation, ", "))) + end + print("]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/preorder_traversal_i_compact.lua b/codes/lua/chapter_backtracking/preorder_traversal_i_compact.lua new file mode 100644 index 0000000000..ffa0985f15 --- /dev/null +++ b/codes/lua/chapter_backtracking/preorder_traversal_i_compact.lua @@ -0,0 +1,50 @@ +-- @script preorder_traversal_i_compact.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- 前序遍历:例题一 +--- @param root TreeNode|nil 根节点 +--- @param res table 存储结果的表 +local function pre_order(root, res) + if not root then + return + end + + -- 如果节点值为7,则记录到结果表中 + if root.val == 7 then + table.insert(res, root) + end + + pre_order(root.left, res) + pre_order(root.right, res) +end + +-- Driver Code +local function main() + -- 通过列表构建二叉树 + local root = list_to_tree({ 1, 7, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树") + print_util.print_tree(root, nil, false) + + -- 前序遍历查找值为7的节点 + local res = {} + pre_order(root, res) + + print("\n输出所有值为 7 的节点") + local vals = {} + for _, node in ipairs(res) do + table.insert(vals, node.val) + end + print("[" .. table.concat(vals, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/preorder_traversal_ii_compact.lua b/codes/lua/chapter_backtracking/preorder_traversal_ii_compact.lua new file mode 100644 index 0000000000..b7733790e2 --- /dev/null +++ b/codes/lua/chapter_backtracking/preorder_traversal_ii_compact.lua @@ -0,0 +1,66 @@ +-- @script preorder_traversal_ii_compact.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- 前序遍历:例题二 +--- @param root TreeNode|nil +--- @param path table +--- @param res table +local function pre_order(root, path, res) + if not root then + return + end + + -- 尝试:将当前节点加入路径 + table.insert(path, root) + + -- 找到目标节点,记录路径 + if root.val == 7 then + -- 复制当前路径到结果中 + local path_copy = {} + for _, node in ipairs(path) do + table.insert(path_copy, node) + end + table.insert(res, path_copy) + end + + -- 递归遍历左右子树 + pre_order(root.left, path, res) + pre_order(root.right, path, res) + + -- 回退:移除当前节点 + table.remove(path) +end + +-- Driver Code +local function main() + -- 构建二叉树 + local root = list_to_tree({ 1, 7, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树") + print_util.print_tree(root, nil, false) + + -- 前序遍历查找路径 + local path = {} + local res = {} + pre_order(root, path, res) + + print("\n输出所有根节点到节点 7 的路径") + for _, path_nodes in ipairs(res) do + local path_vals = {} + for _, node in ipairs(path_nodes) do + table.insert(path_vals, node.val) + end + print("[" .. table.concat(path_vals, ", ") .. "]") + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/preorder_traversal_iii_compact.lua b/codes/lua/chapter_backtracking/preorder_traversal_iii_compact.lua new file mode 100644 index 0000000000..cbb1aa0842 --- /dev/null +++ b/codes/lua/chapter_backtracking/preorder_traversal_iii_compact.lua @@ -0,0 +1,67 @@ +-- @script preorder_traversal_iii_compact.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- 前序遍历:例题三 +--- @param root TreeNode|nil 当前遍历的节点 +--- @param path table 当前路径 +--- @param res table 存储所有符合条件的路径 +local function pre_order(root, path, res) + -- 剪枝:遇到空节点或值为3的节点则返回 + if not root or root.val == 3 then + return + end + + -- 尝试:将当前节点加入路径 + table.insert(path, root) + + -- 记录解:如果当前节点值为7,则记录当前路径 + if root.val == 7 then + -- 创建路径的副本 + local path_copy = {} + for _, node in ipairs(path) do + table.insert(path_copy, node) + end + table.insert(res, path_copy) + end + + -- 递归遍历左右子树 + pre_order(root.left, path, res) + pre_order(root.right, path, res) + + -- 回退:从路径中移除当前节点 + table.remove(path) +end + +-- Driver Code +local function main() + -- 构建二叉树 + local root = list_to_tree({ 1, 7, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树") + print_util.print_tree(root, nil, false) + + -- 前序遍历查找路径 + local path = {} + local res = {} + pre_order(root, path, res) + + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for _, path_nodes in ipairs(res) do + local path_vals = {} + for _, node in ipairs(path_nodes) do + table.insert(path_vals, node.val) + end + print("[" .. table.concat(path_vals, ", ") .. "]") + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/preorder_traversal_iii_template.lua b/codes/lua/chapter_backtracking/preorder_traversal_iii_template.lua new file mode 100644 index 0000000000..b8c0e9b1dd --- /dev/null +++ b/codes/lua/chapter_backtracking/preorder_traversal_iii_template.lua @@ -0,0 +1,98 @@ +-- @script preorder_traversal_iii_template.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- 判断当前状态是否为解 +--- @param state table 当前路径状态 +--- @return boolean +local function is_solution(state) + return #state > 0 and state[#state].val == 7 +end + +--- 记录解 +--- @param state table 当前路径状态 +--- @param res table 结果集合 +local function record_solution(state, res) + local solution = {} + for _, node in ipairs(state) do + table.insert(solution, node) + end + table.insert(res, solution) +end + +--- 判断在当前状态下,该选择是否合法 +--- @param state table 当前路径状态 +--- @param choice TreeNode|nil 候选节点 +--- @return boolean +local function is_valid(state, choice) + return choice ~= nil and choice.val ~= 3 +end + +--- 更新状态 +--- @param state table 当前路径状态 +--- @param choice TreeNode 选择的节点 +local function make_choice(state, choice) + table.insert(state, choice) +end + +--- 恢复状态 +--- @param state table 当前路径状态 +--- @param choice TreeNode 选择的节点 +local function undo_choice(state, choice) + table.remove(state) +end + +--- 回溯算法:例题三 +--- @param state table 当前路径状态 +--- @param choices table 候选节点列表 +--- @param res table 结果集合 +local function backtrack(state, choices, res) + -- 检查是否为解 + if is_solution(state) then + record_solution(state, res) + end + + -- 遍历所有选择 + for _, choice in ipairs(choices) do + -- 剪枝:检查选择是否合法 + if is_valid(state, choice) then + -- 尝试:做出选择,更新状态 + make_choice(state, choice) + -- 进行下一轮选择 + backtrack(state, { choice.left, choice.right }, res) + -- 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end +end + +-- Driver Code +local function main() + local root = list_to_tree({ 1, 7, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树") + print_util.print_tree(root, nil, false) + + -- 回溯算法 + local res = {} + backtrack({}, { root }, res) + + print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") + for _, path in ipairs(res) do + local path_vals = {} + for _, node in ipairs(path) do + table.insert(path_vals, node.val) + end + print("[" .. table.concat(path_vals, ", ") .. "]") + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/subset_sum_i.lua b/codes/lua/chapter_backtracking/subset_sum_i.lua new file mode 100644 index 0000000000..94572b84aa --- /dev/null +++ b/codes/lua/chapter_backtracking/subset_sum_i.lua @@ -0,0 +1,70 @@ +-- @script subset_sum_i.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:子集和 I +--- @param state table 当前状态(子集) +--- @param target integer 目标和 +--- @param choices table 选择列表 +--- @param start integer 遍历起始索引 +--- @param res table 结果列表 +local function backtrack(state, target, choices, start, res) + -- 子集和等于 target 时,记录解 + if target == 0 then + -- 深拷贝当前状态到结果中 + local copy = {} + for i = 1, #state do + copy[i] = state[i] + end + table.insert(res, copy) + return + end + + -- 遍历所有选择 + -- 剪枝二:从 start 开始遍历,避免生成重复子集 + for i = start, #choices do + -- 剪枝一:若子集和超过 target,则直接结束循环 + -- 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 then + break + end + + -- 尝试:做出选择,更新 target, start + table.insert(state, choices[i]) + -- 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + -- 回退:撤销选择,恢复到之前的状态 + table.remove(state) + end +end + +--- 求解子集和 I +--- @param nums table 输入数组 +--- @param target integer 目标和 +--- @return table 所有和等于 target 的子集 +local function subset_sum_i(nums, target) + local state = {} -- 状态(子集) + table.sort(nums) -- 对 nums 进行排序 + local start = 1 -- 遍历起始点(Lua索引从1开始) + local res = {} -- 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res +end + +-- Driver Code +local function main() + local nums = { 3, 4, 5 } + local target = 9 + local res = subset_sum_i(nums, target) + + print(string.format("输入数组 nums = [%s], target = %d", + table.concat(nums, ", "), target)) + print(string.format("所有和等于 %d 的子集 res = [", target)) + for _, subset in ipairs(res) do + print(string.format(" [%s],", table.concat(subset, ", "))) + end + print("]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/subset_sum_i_naive.lua b/codes/lua/chapter_backtracking/subset_sum_i_naive.lua new file mode 100644 index 0000000000..36036f35dd --- /dev/null +++ b/codes/lua/chapter_backtracking/subset_sum_i_naive.lua @@ -0,0 +1,70 @@ +-- @script subset_sum_i_naive.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:子集和 I +--- @param state table 当前状态(子集) +--- @param target integer 目标值 +--- @param total integer 当前子集和 +--- @param choices table 可选数字列表 +--- @param res table 结果列表 +local function backtrack(state, target, total, choices, res) + -- 子集和等于 target 时,记录解 + if total == target then + local solution = {} + for i = 1, #state do + solution[i] = state[i] + end + table.insert(res, solution) + return + end + + -- 遍历所有选择 + for i = 1, #choices do + -- 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target then + -- Lua 没有 continue 关键字,改用if-else结构 + else + -- 尝试:做出选择,更新元素和 total + table.insert(state, choices[i]) + + -- 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + + -- 回退:撤销选择,恢复到之前的状态 + table.remove(state) + end + end +end + +--- 求解子集和 I(包含重复子集) +--- @param nums table 输入数组 +--- @param target integer 目标值 +--- @return table 所有和等于 target 的子集 +local function subset_sum_i_naive(nums, target) + local state = {} -- 状态(子集) + local total = 0 -- 子集和 + local res = {} -- 结果列表(子集列表) + backtrack(state, target, total, nums, res) + return res +end + +-- Driver Code +local function main() + local nums = { 3, 4, 5 } + local target = 9 + local res = subset_sum_i_naive(nums, target) + + print(string.format("输入数组 nums = [%s], target = %d", table.concat(nums, ", "), target)) + print(string.format("所有和等于 %d 的子集 res: [", target)) + + for _, subset in ipairs(res) do + print(string.format(" [%s],", table.concat(subset, ", "))) + end + print("]") + + print("请注意,该方法输出的结果包含重复集合") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_backtracking/subset_sum_ii.lua b/codes/lua/chapter_backtracking/subset_sum_ii.lua new file mode 100644 index 0000000000..8ca8f7930a --- /dev/null +++ b/codes/lua/chapter_backtracking/subset_sum_ii.lua @@ -0,0 +1,75 @@ +-- @script subset_sum_ii.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法:子集和 II +--- @param state table 当前状态(子集) +--- @param target number 目标和 +--- @param choices table 候选数字列表 +--- @param start number 遍历起始位置 +--- @param res table 结果列表 +local function backtrack(state, target, choices, start, res) + -- 子集和等于 target 时,记录解 + if target == 0 then + -- 创建当前状态的副本并添加到结果中 + local state_copy = {} + for i = 1, #state do + state_copy[i] = state[i] + end + table.insert(res, state_copy) + return + end + + -- 遍历所有选择 + -- 剪枝二:从 start 开始遍历,避免生成重复子集 + -- 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i = start, #choices do + -- 剪枝一:若子集和超过 target ,则直接结束循环 + -- 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 then + break + end + + -- 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start and choices[i] == choices[i - 1] then + -- Lua 没有 continue 关键字,改用if-else结构 + else + -- 尝试:做出选择,更新 target, start + table.insert(state, choices[i]) + -- 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + -- 回退:撤销选择,恢复到之前的状态 + table.remove(state) + end + end +end + +--- 求解子集和 II +--- @param nums table 输入数字列表 +--- @param target number 目标和 +--- @return table 所有和等于目标的子集列表 +local function subset_sum_ii(nums, target) + local state = {} -- 状态(子集) + table.sort(nums) -- 对 nums 进行排序 + local start = 1 -- 遍历起始点(Lua索引从1开始) + local res = {} -- 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res +end + +-- Driver Code +local function main() + local nums = { 4, 4, 5 } + local target = 9 + local res = subset_sum_ii(nums, target) + + print(string.format("输入数组 nums = [%s], target = %d", table.concat(nums, ", "), target)) + print(string.format("所有和等于 %d 的子集 res = [", target)) + for _, subset in ipairs(res) do + print(string.format(" [%s],", table.concat(subset, ", "))) + end + print("]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_computational_complexity/iteration.lua b/codes/lua/chapter_computational_complexity/iteration.lua new file mode 100644 index 0000000000..9c0fc05dcb --- /dev/null +++ b/codes/lua/chapter_computational_complexity/iteration.lua @@ -0,0 +1,80 @@ +-- @script iteration.lua +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- for 循环 +--- @param n integer 循环次数 +--- @return integer 求和结果 +local function for_loop(n) + local res = 0 + -- 循环求和 1, 2, ..., n-1, n + for i = 1, n do + res = res + i + end + return res +end + +--- while 循环 +--- @param n integer 循环次数 +--- @return integer 求和结果 +local function while_loop(n) + local res = 0 + local i = 1 -- 初始化条件变量 + -- 循环求和 1, 2, ..., n-1, n + while i <= n do + res = res + i + i = i + 1 -- 更新条件变量 + end + return res +end + +--- while 循环(两次更新) +--- @param n integer 循环次数 +--- @return integer 求和结果 +local function while_loop_ii(n) + local res = 0 + local i = 1 -- 初始化条件变量 + -- 循环求和 1, 4, 10, ... + while i <= n do + res = res + i + -- 更新条件变量 + i = i + 1 + i = i * 2 + end + return res +end + +--- 双层 for 循环 +--- @param n integer 循环次数 +--- @return string 遍历结果字符串 +local function nested_for_loop(n) + local res = "" + -- 循环 i = 1, 2, ..., n-1, n + for i = 1, n do + -- 循环 j = 1, 2, ..., n-1, n + for j = 1, n do + res = res .. "(" .. i .. ", " .. j .. "), " + end + end + return res +end + +-- Driver Code +local function main() + local n = 5 + local res = for_loop(n) + print(string.format("\nfor 循环的求和结果 res = %d", res)) + + res = while_loop(n) + print(string.format("\nwhile 循环的求和结果 res = %d", res)) + + res = while_loop_ii(n) + print(string.format("\nwhile 循环(两次更新)求和结果 res = %d", res)) + + res = nested_for_loop(n) + print(string.format("\n双层 for 循环的遍历结果 %s", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_computational_complexity/recursion.lua b/codes/lua/chapter_computational_complexity/recursion.lua new file mode 100644 index 0000000000..8000cfc27e --- /dev/null +++ b/codes/lua/chapter_computational_complexity/recursion.lua @@ -0,0 +1,86 @@ +-- @script recursion.lua +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 递归求和函数 +--- @param n integer 输入的数字 +--- @return integer 1+2+...+n 的结果 +local function recur(n) + -- 终止条件 + if n == 1 then + return 1 + end + -- 递:递归调用 + local res = recur(n - 1) + -- 归:返回结果 + return n + res +end + +--- 使用迭代模拟递归 +--- @param n integer 输入的数字 +--- @return integer 1+2+...+n 的结果 +local function for_loop_recur(n) + -- 使用一个显式的栈来模拟系统调用栈 + local stack = {} + local res = 0 + -- 递:递归调用 + for i = n, 1, -1 do + -- 通过"入栈操作"模拟"递" + table.insert(stack, i) + end + -- 归:返回结果 + while #stack > 0 do + -- 通过"出栈操作"模拟"归" + res = res + table.remove(stack) + end + -- res = 1+2+3+...+n + return res +end + +--- 尾递归函数 +--- @param n integer 输入的数字 +--- @param res integer 累积结果 +--- @return integer 1+2+...+n 的结果 +local function tail_recur(n, res) + -- 终止条件 + if n == 0 then + return res + end + -- 尾递归调用 + return tail_recur(n - 1, res + n) +end + +--- 斐波那契数列:递归 +--- @param n integer 斐波那契数列的项数 +--- @return integer 第n项斐波那契数 +local function fib(n) + -- 终止条件 f(1) = 0, f(2) = 1 + if n == 1 or n == 2 then + return n - 1 + end + -- 递归调用 f(n) = f(n-1) + f(n-2) + local res = fib(n - 1) + fib(n - 2) + -- 返回结果 f(n) + return res +end + +-- Driver Code +local function main() + local n = 5 + + local res = recur(n) + print(string.format("\n递归函数的求和结果 res = %d", res)) + + res = for_loop_recur(n) + print(string.format("\n使用迭代模拟递归求和结果 res = %d", res)) + + res = tail_recur(n, 0) + print(string.format("\n尾递归函数的求和结果 res = %d", res)) + + res = fib(n) + print(string.format("\n斐波那契数列的第 %d 项为 %d", n, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_computational_complexity/space_complexity.lua b/codes/lua/chapter_computational_complexity/space_complexity.lua new file mode 100644 index 0000000000..a303b686bf --- /dev/null +++ b/codes/lua/chapter_computational_complexity/space_complexity.lua @@ -0,0 +1,131 @@ +-- @script space_complexity.lua +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local tree_node = require("tree_node") +local print_util = require("print_util") +local ListNode = list_node.ListNode +local TreeNode = tree_node.TreeNode + +--- 占位函数 +--- @return integer 返回0 +local function _function() + -- 执行某些操作 + return 0 +end + +--- 常数阶空间复杂度 +--- @param n integer 输入大小 +local function constant(n) + -- 常量、变量、对象占用 O(1) 空间 + local a = 0 + local nums = {} + for i = 1, 10000 do + nums[i] = 0 + end + local node = ListNode.new(0) + + -- 循环中的变量占用 O(1) 空间 + for i = 1, n do + local c = 0 + end + + -- 循环中的函数占用 O(1) 空间 + for i = 1, n do + _function() + end +end + +--- 线性阶空间复杂度 +--- @param n integer 输入大小 +local function linear(n) + -- 长度为 n 的列表占用 O(n) 空间 + local nums = {} + for i = 1, n do + nums[i] = 0 + end + + -- 长度为 n 的哈希表占用 O(n) 空间 + local hmap = {} + for i = 1, n do + hmap[i] = tostring(i) + end +end + +--- 线性阶空间复杂度(递归实现) +--- @param n integer 输入大小 +local function linear_recur(n) + print("递归 n = " .. n) + if n == 1 then + return + end + linear_recur(n - 1) +end + +--- 平方阶空间复杂度 +--- @param n integer 输入大小 +local function quadratic(n) + -- 二维列表占用 O(n^2) 空间 + local num_matrix = {} + for i = 1, n do + num_matrix[i] = {} + for j = 1, n do + num_matrix[i][j] = 0 + end + end +end + +--- 平方阶空间复杂度(递归实现) +--- @param n integer 输入大小 +--- @return integer 返回值 +local function quadratic_recur(n) + if n <= 0 then + return 0 + end + -- 数组 nums 长度为 n, n-1, ..., 2, 1 + local nums = {} + for i = 1, n do + nums[i] = 0 + end + return quadratic_recur(n - 1) +end + +--- 指数阶空间复杂度(建立满二叉树) +--- @param n integer 树的高度 +--- @return TreeNode|nil 树的根节点 +local function build_tree(n) + if n == 0 then + return nil + end + local root = TreeNode.new(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root +end + +-- Driver Code +local function main() + local n = 5 + + -- 常数阶 + constant(n) + + -- 线性阶 + linear(n) + linear_recur(n) + + -- 平方阶 + quadratic(n) + quadratic_recur(n) + + -- 指数阶 + local root = build_tree(n) + print_util.print_tree(root, nil, false) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_computational_complexity/time_complexity.lua b/codes/lua/chapter_computational_complexity/time_complexity.lua new file mode 100644 index 0000000000..0346844bb0 --- /dev/null +++ b/codes/lua/chapter_computational_complexity/time_complexity.lua @@ -0,0 +1,204 @@ +-- @script time_complexity.lua +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 常数阶 +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function constant(n) + local count = 0 + local size = 100000 + for _ = 1, size do + count = count + 1 + end + return count +end + +--- 线性阶 +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function linear(n) + local count = 0 + for _ = 1, n do + count = count + 1 + end + return count +end + +--- 线性阶(遍历数组) +--- @param nums table 数组 +--- @return integer 操作数量 +local function array_traversal(nums) + local count = 0 + -- 循环次数与数组长度成正比 + for _, num in ipairs(nums) do + count = count + 1 + end + return count +end + +--- 平方阶 +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function quadratic(n) + local count = 0 + -- 循环次数与数据大小 n 成平方关系 + for i = 1, n do + for j = 1, n do + count = count + 1 + end + end + return count +end + +--- 平方阶(冒泡排序) +--- @param nums table 数组 +--- @return integer 操作数量 +local function bubble_sort(nums) + local count = 0 -- 计数器 + -- 外循环:未排序区间为 [1, i] + for i = #nums, 2, -1 do + -- 内循环:将未排序区间 [1, i] 中的最大元素交换至该区间的最右端 + for j = 1, i - 1 do + if nums[j] > nums[j + 1] then + -- 交换 nums[j] 与 nums[j + 1] + local tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count = count + 3 -- 元素交换包含 3 个单元操作 + end + end + end + return count +end + +--- 指数阶(循环实现) +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function exponential(n) + local count = 0 + local base = 1 + -- 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for _ = 1, n do + for _ = 1, base do + count = count + 1 + end + base = base * 2 + end + -- count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +end + +--- 指数阶(递归实现) +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function exp_recur(n) + if n == 1 then + return 1 + end + return exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +--- 对数阶(循环实现) +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function logarithmic(n) + local count = 0 + while n > 1 do + n = n / 2 + count = count + 1 + end + return count +end + +--- 对数阶(递归实现) +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function log_recur(n) + if n <= 1 then + return 0 + end + return log_recur(n / 2) + 1 +end + +--- 线性对数阶 +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function linear_log_recur(n) + if n <= 1 then + return 1 + end + -- 一分为二,子问题的规模减小一半 + local count = linear_log_recur(math.floor(n / 2)) + linear_log_recur(math.floor(n / 2)) + -- 当前子问题包含 n 个操作 + for _ = 1, n do + count = count + 1 + end + return count +end + +--- 阶乘阶(递归实现) +--- @param n integer 数据大小 +--- @return integer 操作数量 +local function factorial_recur(n) + if n == 0 then + return 1 + end + local count = 0 + -- 从 1 个分裂出 n 个 + for _ = 1, n do + count = count + factorial_recur(n - 1) + end + return count +end + +-- Driver Code +local function main() + -- 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + local n = 8 + print("输入数据大小 n = " .. n) + + local count = constant(n) + print("常数阶的操作数量 = " .. count) + + count = linear(n) + print("线性阶的操作数量 = " .. count) + + -- 创建数组 [0, 0, ..., 0] + local nums = {} + for i = 1, n do + nums[i] = 0 + end + count = array_traversal(nums) + print("线性阶(遍历数组)的操作数量 = " .. count) + + count = quadratic(n) + print("平方阶的操作数量 = " .. count) + + -- 创建数组 [n, n-1, ..., 2, 1] + nums = {} + for i = 1, n do + nums[i] = n - i + 1 + end + count = bubble_sort(nums) + print("平方阶(冒泡排序)的操作数量 = " .. count) + + count = exponential(n) + print("指数阶(循环实现)的操作数量 = " .. count) + count = exp_recur(n) + print("指数阶(递归实现)的操作数量 = " .. count) + + count = logarithmic(n) + print("对数阶(循环实现)的操作数量 = " .. count) + count = log_recur(n) + print("对数阶(递归实现)的操作数量 = " .. count) + + count = linear_log_recur(n) + print("线性对数阶(递归实现)的操作数量 = " .. count) + + count = factorial_recur(n) + print("阶乘阶(递归实现)的操作数量 = " .. count) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_computational_complexity/worst_best_time_complexity.lua b/codes/lua/chapter_computational_complexity/worst_best_time_complexity.lua new file mode 100644 index 0000000000..90eb9a4227 --- /dev/null +++ b/codes/lua/chapter_computational_complexity/worst_best_time_complexity.lua @@ -0,0 +1,53 @@ +-- @script worst_best_time_complexity.lua +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 +--- @param n integer 数组长度 +--- @return table 打乱后的数组 +local function random_numbers(n) + -- 生成数组 nums =: 1, 2, 3, ..., n + local nums = {} + for i = 1, n do + nums[i] = i + end + + -- 随机打乱数组元素 + for i = n, 2, -1 do + local j = math.random(i) + nums[i], nums[j] = nums[j], nums[i] + end + + return nums +end + +--- 查找数组 nums 中数字 1 所在索引 +--- @param nums table 待查找数组 +--- @return integer 数字1的索引,未找到返回-1 +local function find_one(nums) + for i = 1, #nums do + -- 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + -- 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if nums[i] == 1 then + return i + end + end + return -1 +end + +-- Driver Code +local function main() + math.randomseed(os.time()) -- 初始化随机数种子 + + for _ = 1, 10 do + local n = 100 + local nums = random_numbers(n) + local index = find_one(nums) + print("\n数组 [ 1, 2, ..., n ] 被打乱后 = [" .. table.concat(nums, ", ") .. "]") + print("数字 1 的索引为 " .. index) + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_divide_and_conquer/binary_search_recur.lua b/codes/lua/chapter_divide_and_conquer/binary_search_recur.lua new file mode 100644 index 0000000000..42e653017e --- /dev/null +++ b/codes/lua/chapter_divide_and_conquer/binary_search_recur.lua @@ -0,0 +1,55 @@ +-- @script binary_search_recur.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 二分查找:问题 f(i, j) +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @param i integer 区间起始索引 +--- @param j integer 区间结束索引 +--- @return integer 目标元素的索引,如果未找到则返回-1 +--- @warning Lua table 索引从 1 开始 +local function dfs(nums, target, i, j) + -- 若区间为空,代表无目标元素,则返回 -1 + if i > j then + return -1 + end + + -- 计算中点索引 m + local m = math.floor((i + j) / 2) + + if nums[m] < target then + -- 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j) + elseif nums[m] > target then + -- 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1) + else + -- 找到目标元素,返回其索引 + return m + end +end + +--- 二分查找 +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @return integer 目标元素的索引,如果未找到则返回-1 +--- @warning Lua table 索引从 1 开始 +local function search(nums, target) + local n = #nums + -- 求解问题 f(1, n) + return dfs(nums, target, 1, n) +end + +-- Driver Code +local function main() + local target = 6 + local nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 } + + -- 二分查找(双闭区间) + local index = search(nums, target) -- 注意:Lua 索引从 1 开始 + print("目标元素 6 的索引 = " .. tostring(index)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_divide_and_conquer/build_tree.lua b/codes/lua/chapter_divide_and_conquer/build_tree.lua new file mode 100644 index 0000000000..26b30d277f --- /dev/null +++ b/codes/lua/chapter_divide_and_conquer/build_tree.lua @@ -0,0 +1,70 @@ +-- @script build_tree.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local TreeNode = tree_node.TreeNode + +--- 使用分治法构建二叉树 +--- @param preorder table 前序遍历序列 +--- @param inorder_map table 中序遍历值到索引的映射 +--- @param i integer 当前根节点在前序遍历中的索引 +--- @param l integer 当前子树在中序遍历中的左边界 +--- @param r integer 当前子树在中序遍历中的右边界 +--- @return TreeNode|nil 构建的二叉树根节点 +local function dfs(preorder, inorder_map, i, l, r) + -- 子树区间为空时终止 + if r < l then + return nil + end + + -- 初始化根节点 + local root = TreeNode.new(preorder[i]) + + -- 查询 m,从而划分左右子树 + local m = inorder_map[preorder[i]] + + -- 子问题:构建左子树 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + + -- 子问题:构建右子树 + root.right = dfs(preorder, inorder_map, i + 1 + (m - l), m + 1, r) + + -- 返回根节点 + return root +end + +--- 根据前序遍历和中序遍历构建二叉树 +--- @param preorder table 前序遍历序列 +--- @param inorder table 中序遍历序列 +--- @return TreeNode|nil 构建的二叉树根节点 +local function build_tree(preorder, inorder) + -- 初始化哈希表,存储 inorder 元素到索引的映射 + local inorder_map = {} + for idx, val in ipairs(inorder) do + inorder_map[val] = idx + end + + local root = dfs(preorder, inorder_map, 1, 1, #inorder) + return root +end + +-- Driver Code +local function main() + local preorder = { 3, 9, 2, 1, 7 } + local inorder = { 9, 3, 1, 2, 7 } + + print("前序遍历 = [" .. table.concat(preorder, ", ") .. "]") + print("中序遍历 = [" .. table.concat(inorder, ", ") .. "]") + + local root = build_tree(preorder, inorder) + print("构建的二叉树为:") + print_util.print_tree(root, nil, false) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_divide_and_conquer/hanota.lua b/codes/lua/chapter_divide_and_conquer/hanota.lua new file mode 100644 index 0000000000..489932f961 --- /dev/null +++ b/codes/lua/chapter_divide_and_conquer/hanota.lua @@ -0,0 +1,66 @@ +-- @script hanota.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 移动一个圆盘 +--- @param src table 源柱子 +--- @param tar table 目标柱子 +local function move(src, tar) + -- 从 src 顶部拿出一个圆盘 + local pan = table.remove(src) + -- 将圆盘放入 tar 顶部 + table.insert(tar, pan) +end + +--- 求解汉诺塔问题 f(i) +--- @param i number 要移动的圆盘数量 +--- @param src table 源柱子 +--- @param buf table 缓冲柱子 +--- @param tar table 目标柱子 +local function dfs(i, src, buf, tar) + -- 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 then + move(src, tar) + return + end + + -- 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + -- 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + -- 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) +end + +--- 求解汉诺塔问题 +--- @param A table 起始柱子A +--- @param B table 辅助柱子B +--- @param C table 目标柱子C +local function solve_hanota(A, B, C) + local n = #A + -- 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C) +end + +-- Driver Code +local function main() + -- 列表尾部是柱子顶部 + local A = { 5, 4, 3, 2, 1 } + local B = {} + local C = {} + + print("初始状态下:") + print("A = [" .. table.concat(A, ", ") .. "]") + print("B = [" .. table.concat(B, ", ") .. "]") + print("C = [" .. table.concat(C, ", ") .. "]") + + solve_hanota(A, B, C) + + print("圆盘移动完成后:") + print("A = [" .. table.concat(A, ", ") .. "]") + print("B = [" .. table.concat(B, ", ") .. "]") + print("C = [" .. table.concat(C, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/climbing_stairs_backtrack.lua b/codes/lua/chapter_dynamic_programming/climbing_stairs_backtrack.lua new file mode 100644 index 0000000000..bae18bccda --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/climbing_stairs_backtrack.lua @@ -0,0 +1,48 @@ +-- @script climbing_stairs_backtrack.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 回溯算法 +--- @param choices table 可选择的步长列表 +--- @param state integer 当前所在阶梯 +--- @param n integer 目标阶梯数 +--- @param res table 结果计数器(使用表传递以支持修改) +local function backtrack(choices, state, n, res) + -- 当爬到第 n 阶时,方案数量加 1 + if state == n then + res[1] = res[1] + 1 + return + end + + -- 遍历所有选择 + for _, choice in ipairs(choices) do + -- 剪枝:不允许越过第 n 阶 + if state + choice <= n then + -- 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + -- 回退(在Lua中不需要显式回退,参数是按值传递的) + end + end +end + +--- 爬楼梯:回溯算法 +--- @param n integer 楼梯阶数 +--- @return integer 爬楼梯的方案数量 +local function climbing_stairs_backtrack(n) + local choices = { 1, 2 } -- 可选择向上爬 1 阶或 2 阶 + local state = 0 -- 从第 0 阶开始爬 + local res = { 0 } -- 使用 res[1] 记录方案数量 + + backtrack(choices, state, n, res) + return res[1] +end + +-- Driver Code +local function main() + local n = 9 + local res = climbing_stairs_backtrack(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/climbing_stairs_constraint_dp.lua b/codes/lua/chapter_dynamic_programming/climbing_stairs_constraint_dp.lua new file mode 100644 index 0000000000..06ea17f206 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/climbing_stairs_constraint_dp.lua @@ -0,0 +1,46 @@ +-- @script climbing_stairs_constraint_dp.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 带约束爬楼梯:动态规划 +--- @param n integer 楼梯的阶数 +--- @return integer 爬楼梯的方案总数 +local function climbing_stairs_constraint_dp(n) + -- 基础情况处理 + if n == 1 or n == 2 then + return 1 + end + + -- 初始化动态规划表 + -- dp[i][1] 表示到达第i阶且最后一步是1阶的方案数 + -- dp[i][2] 表示到达第i阶且最后一步是2阶的方案数 + local dp = {} + for i = 1, n + 1 do + dp[i] = { 0, 0 } + end + + -- 设置初始状态 + dp[1][1], dp[1][2] = 1, 0 -- 第1阶:只能1步到达 + dp[2][1], dp[2][2] = 0, 1 -- 第2阶:只能2步到达 + + -- 状态转移:从较小问题逐步求解较大问题 + for i = 3, n do + -- 最后一步是1阶:前一步必须是2阶(不能连续两次1阶) + dp[i][1] = dp[i - 1][2] + -- 最后一步是2阶:可以从i-2阶通过1阶或2阶到达 + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + -- 返回总方案数:到达第n阶的所有可能方式 + return dp[n][1] + dp[n][2] +end + +-- Driver Code +local function main() + local n = 9 + local res = climbing_stairs_constraint_dp(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) +end + +-- 执行主程序 +main() diff --git a/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs.lua b/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs.lua new file mode 100644 index 0000000000..369c128812 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs.lua @@ -0,0 +1,34 @@ +-- @script climbing_stairs_dfs.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 搜索 +--- @param i integer 当前台阶数 +--- @return integer 到达当前台阶的方案数 +local function dfs(i) + -- 已知 dp[1] 和 dp[2],直接返回 + if i == 1 or i == 2 then + return i + end + + -- dp[i] = dp[i-1] + dp[i-2] + local count = dfs(i - 1) + dfs(i - 2) + return count +end + +--- 爬楼梯:搜索 +--- @param n integer 楼梯阶数 +--- @return integer 爬楼梯的方案总数 +local function climbing_stairs_dfs(n) + return dfs(n) +end + +-- Driver Code +local function main() + local n = 9 + local res = climbing_stairs_dfs(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs_mem.lua b/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs_mem.lua new file mode 100644 index 0000000000..66f6b34abf --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/climbing_stairs_dfs_mem.lua @@ -0,0 +1,50 @@ +-- @script climbing_stairs_dfs_mem.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 记忆化搜索 +--- @param i integer 当前楼梯阶数 +--- @param mem table 记忆化表格 +--- @return integer 爬到第 i 阶的方案总数 +local function dfs(i, mem) + -- 基本情况:第 1 阶有 1 种方案,第 2 阶有 2 种方案 + if i == 1 or i == 2 then + return i + end + + -- 如果已经计算过,直接返回结果 + if mem[i] ~= -1 then + return mem[i] + end + + -- 递归计算:dp[i] = dp[i-1] + dp[i-2] + local count = dfs(i - 1, mem) + dfs(i - 2, mem) + + -- 记录结果到记忆化表格 + mem[i] = count + + return count +end + +--- 爬楼梯:记忆化搜索 +--- @param n integer 楼梯总阶数 +--- @return integer 爬 n 阶楼梯的方案总数 +local function climbing_stairs_dfs_mem(n) + -- mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + local mem = {} + for i = 0, n do + mem[i] = -1 + end + + return dfs(n, mem) +end + +-- Driver Code +local function main() + local n = 9 + local res = climbing_stairs_dfs_mem(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/climbing_stairs_dp.lua b/codes/lua/chapter_dynamic_programming/climbing_stairs_dp.lua new file mode 100644 index 0000000000..859acf6923 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/climbing_stairs_dp.lua @@ -0,0 +1,60 @@ +-- @script climbing_stairs_dp.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 爬楼梯:动态规划 +--- @param n integer 楼梯阶数 +--- @return integer 爬楼梯的方案数 +local function climbing_stairs_dp(n) + if n == 1 or n == 2 then + return n + end + + -- 初始化 dp 表,用于存储子问题的解 + local dp = {} + for i = 1, n + 1 do + dp[i] = 0 + end + + -- 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + + -- 状态转移:从较小子问题逐步求解较大子问题 + for i = 3, n do + dp[i] = dp[i - 1] + dp[i - 2] + end + + return dp[n] +end + +--- 爬楼梯:空间优化后的动态规划 +--- @param n integer 楼梯阶数 +--- @return integer 爬楼梯的方案数 +local function climbing_stairs_dp_comp(n) + if n == 1 or n == 2 then + return n + end + + local a, b = 1, 2 + + for _ = 3, n do + a, b = b, a + b + end + + return b +end + +-- Driver Code +local function main() + local n = 9 + + local res = climbing_stairs_dp(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) + + res = climbing_stairs_dp_comp(n) + print(string.format("爬 %d 阶楼梯共有 %d 种方案", n, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/coin_change.lua b/codes/lua/chapter_dynamic_programming/coin_change.lua new file mode 100644 index 0000000000..0a84a6c2ba --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/coin_change.lua @@ -0,0 +1,88 @@ +-- @script coin_change.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 零钱兑换:动态规划 +--- @param coins table 可用的硬币面值列表 +--- @param amt integer 目标金额 +--- @return integer 凑齐目标金额所需的最少硬币数,如果无法凑齐则返回-1 +local function coin_change_dp(coins, amt) + local n = #coins + local MAX = amt + 1 + + -- 初始化 dp 表 + local dp = {} + for i = 1, n + 1 do + dp[i] = {} + for j = 1, amt + 1 do + dp[i][j] = 0 + end + end + + -- 状态转移:首行首列 + for a = 2, amt + 1 do + dp[1][a] = MAX + end + + -- 状态转移:其余行和列 + for i = 2, n + 1 do + for a = 2, amt + 1 do + if coins[i - 1] > a - 1 then + -- 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else + -- 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + end + end + end + + return dp[n + 1][amt + 1] ~= MAX and dp[n + 1][amt + 1] or -1 +end + +--- 零钱兑换:空间优化后的动态规划 +--- @param coins table 可用的硬币面值列表 +--- @param amt integer 目标金额 +--- @return integer 凑齐目标金额所需的最少硬币数,如果无法凑齐则返回-1 +local function coin_change_dp_comp(coins, amt) + local n = #coins + local MAX = amt + 1 + + -- 初始化 dp 表 + local dp = {} + for i = 1, amt + 1 do + dp[i] = MAX + end + dp[1] = 0 -- 金额0需要0个硬币 + + -- 状态转移 + for i = 1, n do + -- 正序遍历 + for a = 2, amt + 1 do + if coins[i] <= a - 1 then + -- 不选和选硬币 i 这两种方案的较小值 + dp[a] = math.min(dp[a], dp[a - coins[i]] + 1) + end + -- 若超过目标金额,dp[a]保持不变(相当于不选硬币i) + end + end + + return dp[amt + 1] ~= MAX and dp[amt + 1] or -1 +end + +-- Driver Code +local function main() + local coins = { 1, 2, 5 } + local amt = 4 + + -- 动态规划 + local res = coin_change_dp(coins, amt) + print(string.format("凑到目标金额所需的最少硬币数量为 %d", res)) + + -- 空间优化后的动态规划 + res = coin_change_dp_comp(coins, amt) + print(string.format("凑到目标金额所需的最少硬币数量为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/coin_change_ii.lua b/codes/lua/chapter_dynamic_programming/coin_change_ii.lua new file mode 100644 index 0000000000..0875267140 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/coin_change_ii.lua @@ -0,0 +1,89 @@ +-- @script coin_change_ii.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 零钱兑换 II:动态规划 +--- @param coins table 可用的硬币面值列表 +--- @param amt integer 目标金额 +--- @return integer 组合数量 +local function coin_change_ii_dp(coins, amt) + local n = #coins + -- 初始化 dp 表 + local dp = {} + for i = 1, n + 1 do + dp[i] = {} + for j = 1, amt + 1 do + dp[i][j] = 0 + end + end + + -- 初始化首列 + for i = 1, n + 1 do + dp[i][1] = 1 + end + + -- 状态转移 + for i = 2, n + 1 do + for a = 2, amt + 1 do + local current_coin = coins[i - 1] + local current_amount = a - 1 -- 转换为实际金额(索引从1开始) + + if current_coin > current_amount then + -- 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else + -- 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - current_coin] + end + end + end + + return dp[n + 1][amt + 1] +end + +--- 零钱兑换 II:空间优化后的动态规划 +--- @param coins table 可用的硬币面值列表 +--- @param amt integer 目标金额 +--- @return integer 组合数量 +local function coin_change_ii_dp_comp(coins, amt) + local n = #coins + -- 初始化 dp 表 + local dp = {} + for i = 1, amt + 1 do + dp[i] = 0 + end + dp[1] = 1 -- 对应金额0的情况 + + -- 状态转移 + for i = 1, n do + for a = 2, amt + 1 do + local current_coin = coins[i] + local current_amount = a - 1 -- 转换为实际金额(索引从1开始) + + if current_coin <= current_amount then + -- 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - current_coin] + end + -- 如果硬币金额大于当前金额,dp[a]保持不变 + end + end + + return dp[amt + 1] +end + +-- Driver Code +local function main() + local coins = { 1, 2, 5 } + local amt = 5 + + -- 动态规划 + local res = coin_change_ii_dp(coins, amt) + print(string.format("凑出目标金额的硬币组合数量为 %d", res)) + + -- 空间优化后的动态规划 + res = coin_change_ii_dp_comp(coins, amt) + print(string.format("凑出目标金额的硬币组合数量为 %d", res)) +end + +-- 执行主程序 +main() diff --git a/codes/lua/chapter_dynamic_programming/edit_distance.lua b/codes/lua/chapter_dynamic_programming/edit_distance.lua new file mode 100644 index 0000000000..a6455b3472 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/edit_distance.lua @@ -0,0 +1,193 @@ +-- @script edit_distance.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 编辑距离:暴力搜索 +--- @param s string 源字符串 +--- @param t string 目标字符串 +--- @param i integer 源字符串当前处理位置 +--- @param j integer 目标字符串当前处理位置 +--- @return integer 编辑距离 +local function edit_distance_dfs(s, t, i, j) + -- 若 s 和 t 都为空,则返回 0 + if i == 0 and j == 0 then + return 0 + end + + -- 若 s 为空,则返回 t 长度 + if i == 0 then + return j + end + + -- 若 t 为空,则返回 s 长度 + if j == 0 then + return i + end + + -- 若两字符相等,则直接跳过此两字符 + if s:sub(i, i) == t:sub(j, j) then + return edit_distance_dfs(s, t, i - 1, j - 1) + end + + -- 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + local insert_cost = edit_distance_dfs(s, t, i, j - 1) + local delete_cost = edit_distance_dfs(s, t, i - 1, j) + local replace_cost = edit_distance_dfs(s, t, i - 1, j - 1) + + -- 返回最少编辑步数 + return math.min(insert_cost, delete_cost, replace_cost) + 1 +end + +--- 编辑距离:记忆化搜索 +--- @param s string 源字符串 +--- @param t string 目标字符串 +--- @param mem table 记忆化表格 +--- @param i integer 源字符串当前处理位置 +--- @param j integer 目标字符串当前处理位置 +--- @return integer 编辑距离 +local function edit_distance_dfs_mem(s, t, mem, i, j) + -- 若 s 和 t 都为空,则返回 0 + if i == 0 and j == 0 then + return 0 + end + + -- 若 s 为空,则返回 t 长度 + if i == 0 then + return j + end + + -- 若 t 为空,则返回 s 长度 + if j == 0 then + return i + end + + -- 若已有记录,则直接返回之 + if mem[i][j] ~= -1 then + return mem[i][j] + end + + -- 若两字符相等,则直接跳过此两字符 + if s:sub(i, i) == t:sub(j, j) then + mem[i][j] = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + return mem[i][j] + end + + -- 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + local insert_cost = edit_distance_dfs_mem(s, t, mem, i, j - 1) + local delete_cost = edit_distance_dfs_mem(s, t, mem, i - 1, j) + local replace_cost = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + + -- 记录并返回最少编辑步数 + mem[i][j] = math.min(insert_cost, delete_cost, replace_cost) + 1 + return mem[i][j] +end + +--- 编辑距离:动态规划 +--- @param s string 源字符串 +--- @param t string 目标字符串 +--- @return integer 编辑距离 +local function edit_distance_dp(s, t) + local n, m = #s, #t + local dp = {} + + -- 初始化DP表 + for i = 0, n do + dp[i] = {} + for j = 0, m do + dp[i][j] = 0 + end + end + + -- 状态转移:首行首列 + for i = 1, n do + dp[i][0] = i + end + + for j = 1, m do + dp[0][j] = j + end + + -- 状态转移:其余行和列 + for i = 1, n do + for j = 1, m do + if s:sub(i, i) == t:sub(j, j) then + -- 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + else + -- 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + end + end + end + + return dp[n][m] +end + +--- 编辑距离:空间优化后的动态规划 +--- @param s string 源字符串 +--- @param t string 目标字符串 +--- @return integer 编辑距离 +local function edit_distance_dp_comp(s, t) + local n, m = #s, #t + local dp = {} + + -- 初始化DP数组 + for j = 0, m do + dp[j] = j + end + + -- 状态转移:其余行 + for i = 1, n do + -- 状态转移:首列 + local leftup = dp[0] -- 暂存 dp[i-1, j-1] + dp[0] = i + + -- 状态转移:其余列 + for j = 1, m do + local temp = dp[j] + if s:sub(i, i) == t:sub(j, j) then + -- 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + else + -- 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = math.min(dp[j - 1], dp[j], leftup) + 1 + end + leftup = temp -- 更新为下一轮的 dp[i-1, j-1] + end + end + + return dp[m] +end + +-- Driver Code +local function main() + local s = "bag" + local t = "pack" + local n, m = #s, #t + + -- 暴力搜索 + local res = edit_distance_dfs(s, t, n, m) + print(string.format("将 %s 更改为 %s 最少需要编辑 %d 步", s, t, res)) + + -- 记忆化搜索 + local mem = {} + for i = 0, n do + mem[i] = {} + for j = 0, m do + mem[i][j] = -1 + end + end + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(string.format("将 %s 更改为 %s 最少需要编辑 %d 步", s, t, res)) + + -- 动态规划 + res = edit_distance_dp(s, t) + print(string.format("将 %s 更改为 %s 最少需要编辑 %d 步", s, t, res)) + + -- 空间优化后的动态规划 + res = edit_distance_dp_comp(s, t) + print(string.format("将 %s 更改为 %s 最少需要编辑 %d 步", s, t, res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/knapsack.lua b/codes/lua/chapter_dynamic_programming/knapsack.lua new file mode 100644 index 0000000000..b000d3ab78 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/knapsack.lua @@ -0,0 +1,154 @@ +-- @script knapsack.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 0-1 背包:暴力搜索 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param i integer 当前考虑的物品索引 +--- @param c integer 背包剩余容量 +--- @return integer 最大价值 +local function knapsack_dfs(wgt, val, i, c) + -- 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 or c == 0 then + return 0 + end + + -- 若超过背包容量,则只能选择不放入背包 + if wgt[i] > c then + return knapsack_dfs(wgt, val, i - 1, c) + end + + -- 计算不放入和放入物品 i 的最大价值 + local no = knapsack_dfs(wgt, val, i - 1, c) + local yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i]) + val[i] + + -- 返回两种方案中价值更大的那一个 + return math.max(no, yes) +end + +--- 0-1 背包:记忆化搜索 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param mem table 记忆化表格 +--- @param i integer 当前考虑的物品索引 +--- @param c integer 背包剩余容量 +--- @return integer 最大价值 +local function knapsack_dfs_mem(wgt, val, mem, i, c) + -- 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 or c == 0 then + return 0 + end + + -- 若已有记录,则直接返回 + if mem[i][c] ~= -1 then + return mem[i][c] + end + + -- 若超过背包容量,则只能选择不放入背包 + if wgt[i] > c then + mem[i][c] = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + return mem[i][c] + end + + -- 计算不放入和放入物品 i 的最大价值 + local no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + local yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i]) + val[i] + + -- 记录并返回两种方案中价值更大的那一个 + mem[i][c] = math.max(no, yes) + return mem[i][c] +end + +--- 0-1 背包:动态规划 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param cap integer 背包容量 +--- @return integer 最大价值 +local function knapsack_dp(wgt, val, cap) + local n = #wgt + -- 初始化 dp 表 + local dp = {} + for i = 0, n do + dp[i] = {} + for j = 0, cap do + dp[i][j] = 0 + end + end + + -- 状态转移 + for i = 1, n do + for c = 1, cap do + if wgt[i] > c then + -- 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else + -- 不选和选物品 i 这两种方案的较大值 + dp[i][c] = math.max(dp[i - 1][c], dp[i - 1][c - wgt[i]] + val[i]) + end + end + end + + return dp[n][cap] +end + +--- 0-1 背包:空间优化后的动态规划 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param cap integer 背包容量 +--- @return integer 最大价值 +local function knapsack_dp_comp(wgt, val, cap) + local n = #wgt + -- 初始化 dp 表 + local dp = {} + for i = 0, cap do + dp[i] = 0 + end + + -- 状态转移 + for i = 1, n do + -- 倒序遍历 + for c = cap, 1, -1 do + if wgt[i] <= c then + -- 不选和选物品 i 这两种方案的较大值 + dp[c] = math.max(dp[c], dp[c - wgt[i]] + val[i]) + end + end + end + + return dp[cap] +end + +-- Driver Code +local function main() + local wgt = { 10, 20, 30, 40, 50 } + local val = { 50, 120, 150, 210, 240 } + local cap = 50 + local n = #wgt + + -- 暴力搜索 + local res = knapsack_dfs(wgt, val, n, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) + + -- 记忆化搜索 + local mem = {} + for i = 0, n do + mem[i] = {} + for j = 0, cap do + mem[i][j] = -1 + end + end + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) + + -- 动态规划 + res = knapsack_dp(wgt, val, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) + + -- 空间优化后的动态规划 + res = knapsack_dp_comp(wgt, val, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) +end + +-- 执行测试代码 +main() diff --git a/codes/lua/chapter_dynamic_programming/min_cost_climbing_stairs_dp.lua b/codes/lua/chapter_dynamic_programming/min_cost_climbing_stairs_dp.lua new file mode 100644 index 0000000000..12b15bc15d --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/min_cost_climbing_stairs_dp.lua @@ -0,0 +1,63 @@ +-- @script min_cost_climbing_stairs_dp.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 爬楼梯最小代价:动态规划 +--- @param cost table 楼梯代价列表,第一个元素为起始位置代价 +--- @return integer 最小代价 +local function min_cost_climbing_stairs_dp(cost) + local n = #cost + if n == 1 or n == 2 then + return cost[n] + end + + -- 初始化 dp 表,用于存储子问题的解 + local dp = {} + for i = 1, n do + dp[i] = 0 + end + + -- 初始状态:预设最小子问题的解 + dp[1] = cost[1] + dp[2] = cost[2] + + -- 状态转移:从较小子问题逐步求解较大子问题 + for i = 3, n do + dp[i] = math.min(dp[i - 1], dp[i - 2]) + cost[i] + end + + return dp[n] +end + +--- 爬楼梯最小代价:空间优化后的动态规划 +--- @param cost table 楼梯代价列表,第一个元素为起始位置代价 +--- @return integer 最小代价 +local function min_cost_climbing_stairs_dp_comp(cost) + local n = #cost + if n == 1 or n == 2 then + return cost[n] + end + + local a, b = cost[1], cost[2] + + for i = 3, n do + a, b = b, math.min(a, b) + cost[i] + end + + return b +end + +-- Driver Code +local function main() + local cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 } + print(string.format("输入楼梯的代价列表为 [%s]", table.concat(cost, ", "))) + + local res = min_cost_climbing_stairs_dp(cost) + print(string.format("爬完楼梯的最低代价为 %d", res)) + + res = min_cost_climbing_stairs_dp_comp(cost) + print(string.format("爬完楼梯的最低代价为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/min_path_sum.lua b/codes/lua/chapter_dynamic_programming/min_path_sum.lua new file mode 100644 index 0000000000..ab724b12b6 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/min_path_sum.lua @@ -0,0 +1,147 @@ +-- @script min_path_sum.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 最小路径和:暴力搜索 +--- @param grid table 二维网格 +--- @param i integer 当前行索引 +--- @param j integer 当前列索引 +--- @return integer 最小路径和 +local function min_path_sum_dfs(grid, i, j) + -- 若为左上角单元格,则终止搜索 + if i == 0 and j == 0 then + return grid[1][1] -- Lua数组索引从1开始 + end + -- 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0 then + return math.huge + end + -- 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + local up = min_path_sum_dfs(grid, i - 1, j) + local left = min_path_sum_dfs(grid, i, j - 1) + -- 返回从左上角到 (i, j) 的最小路径代价 + return math.min(left, up) + grid[i + 1][j + 1] -- 调整索引 +end + +--- 最小路径和:记忆化搜索 +--- @param grid table 二维网格 +--- @param mem table 记忆化数组 +--- @param i integer 当前行索引 +--- @param j integer 当前列索引 +--- @return integer 最小路径和 +local function min_path_sum_dfs_mem(grid, mem, i, j) + -- 若为左上角单元格,则终止搜索 + if i == 0 and j == 0 then + return grid[1][1] + end + -- 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0 then + return math.huge + end + -- 若已有记录,则直接返回 + if mem[i + 1][j + 1] ~= -1 then + return mem[i + 1][j + 1] + end + -- 左边和上边单元格的最小路径代价 + local up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + local left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + -- 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i + 1][j + 1] = math.min(left, up) + grid[i + 1][j + 1] + return mem[i + 1][j + 1] +end + +--- 最小路径和:动态规划 +--- @param grid table 二维网格 +--- @return integer 最小路径和 +local function min_path_sum_dp(grid) + local n, m = #grid, #grid[1] + -- 初始化 dp 表 + local dp = {} + for i = 1, n do + dp[i] = {} + for j = 1, m do + dp[i][j] = 0 + end + end + dp[1][1] = grid[1][1] + -- 状态转移:首行 + for j = 2, m do + dp[1][j] = dp[1][j - 1] + grid[1][j] + end + -- 状态转移:首列 + for i = 2, n do + dp[i][1] = dp[i - 1][1] + grid[i][1] + end + -- 状态转移:其余行和列 + for i = 2, n do + for j = 2, m do + dp[i][j] = math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + end + end + return dp[n][m] +end + +--- 最小路径和:空间优化后的动态规划 +--- @param grid table 二维网格 +--- @return integer 最小路径和 +local function min_path_sum_dp_comp(grid) + local n, m = #grid, #grid[1] + -- 初始化 dp 表 + local dp = {} + for j = 1, m do + dp[j] = 0 + end + -- 状态转移:首行 + dp[1] = grid[1][1] + for j = 2, m do + dp[j] = dp[j - 1] + grid[1][j] + end + -- 状态转移:其余行 + for i = 2, n do + -- 状态转移:首列 + dp[1] = dp[1] + grid[i][1] + -- 状态转移:其余列 + for j = 2, m do + dp[j] = math.min(dp[j - 1], dp[j]) + grid[i][j] + end + end + return dp[m] +end + +-- Driver Code +local function main() + local grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + } + local n, m = #grid, #grid[1] + + -- 暴力搜索 + local res = min_path_sum_dfs(grid, n - 1, m - 1) + print(string.format("从左上角到右下角的最小路径和为 %d", res)) + + -- 记忆化搜索 + local mem = {} + for i = 1, n do + mem[i] = {} + for j = 1, m do + mem[i][j] = -1 + end + end + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(string.format("从左上角到右下角的最小路径和为 %d", res)) + + -- 动态规划 + res = min_path_sum_dp(grid) + print(string.format("从左上角到右下角的最小路径和为 %d", res)) + + -- 空间优化后的动态规划 + res = min_path_sum_dp_comp(grid) + print(string.format("从左上角到右下角的最小路径和为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_dynamic_programming/unbounded_knapsack.lua b/codes/lua/chapter_dynamic_programming/unbounded_knapsack.lua new file mode 100644 index 0000000000..8cd1c52b97 --- /dev/null +++ b/codes/lua/chapter_dynamic_programming/unbounded_knapsack.lua @@ -0,0 +1,88 @@ +-- @script unbounded_knapsack.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 完全背包:动态规划 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param cap number 背包容量 +--- @return number 最大价值 +local function unbounded_knapsack_dp(wgt, val, cap) + local n = #wgt + -- 初始化 dp 表 + local dp = {} + for i = 1, n + 1 do + dp[i] = {} + for j = 1, cap + 1 do + dp[i][j] = 0 + end + end + + -- 状态转移 + for i = 1, n do + for c = 1, cap do + if wgt[i] > c then + -- 若超过背包容量,则不选物品 i + dp[i + 1][c + 1] = dp[i][c + 1] + else + -- 不选和选物品 i 这两种方案的较大值 + dp[i + 1][c + 1] = math.max( + dp[i][c + 1], + dp[i + 1][c + 1 - wgt[i]] + val[i] + ) + end + end + end + + return dp[n + 1][cap + 1] +end + +--- 完全背包:空间优化后的动态规划 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param cap number 背包容量 +--- @return number 最大价值 +local function unbounded_knapsack_dp_comp(wgt, val, cap) + local n = #wgt + -- 初始化 dp 表 + local dp = {} + for i = 1, cap + 1 do + dp[i] = 0 + end + + -- 状态转移 + for i = 1, n do + -- 正序遍历 + for c = 1, cap do + if wgt[i] <= c then + -- 不选和选物品 i 这两种方案的较大值 + dp[c + 1] = math.max( + dp[c + 1], + dp[c + 1 - wgt[i]] + val[i] + ) + end + -- 如果超过背包容量,dp[c]保持不变,无需额外操作 + end + end + + return dp[cap + 1] +end + +-- Driver Code +local function main() + local wgt = { 1, 2, 3 } + local val = { 5, 11, 15 } + local cap = 4 + + -- 动态规划 + local res = unbounded_knapsack_dp(wgt, val, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) + + -- 空间优化后的动态规划 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(string.format("不超过背包容量的最大物品价值为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_graph/graph_adjacency_list.lua b/codes/lua/chapter_graph/graph_adjacency_list.lua new file mode 100644 index 0000000000..f05f8f08eb --- /dev/null +++ b/codes/lua/chapter_graph/graph_adjacency_list.lua @@ -0,0 +1,173 @@ +-- @script graph_adjacency_list.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local vertex = require("vertex") +local Vertex = vertex.Vertex +local vals_to_vets = vertex.vals_to_vets + +--- @class GraphAdjList +--- 基于邻接表实现的无向图类 +--- @field adj_list table 邻接表 +local GraphAdjList = {} +GraphAdjList.__index = GraphAdjList + +--- 构造方法 +--- @param edges table 边列表,每一条边包含两个顶点 +--- @return GraphAdjList 实例 +function GraphAdjList.new(edges) + local obj = setmetatable({}, GraphAdjList) + + -- 邻接表,key:顶点,value:该顶点的所有邻接顶点 + obj.adj_list = {} + + -- 添加所有顶点和边 + for _, edge in ipairs(edges) do + obj:add_vertex(edge[1]) + obj:add_vertex(edge[2]) + obj:add_edge(edge[1], edge[2]) + end + + return obj +end + +--- 获取顶点数量 +--- @return integer 顶点数量 +function GraphAdjList:size() + local count = 0 + for _ in pairs(self.adj_list) do + count = count + 1 + end + return count +end + +--- 添加边 +--- @param vet1 Vertex 顶点1 +--- @param vet2 Vertex 顶点2 +--- @raise 当顶点不存在或顶点相同时抛出错误 +function GraphAdjList:add_edge(vet1, vet2) + if not self.adj_list[vet1] or not self.adj_list[vet2] or vet1 == vet2 then + error("无效的边") + end + + -- 添加边 vet1 - vet2 + table.insert(self.adj_list[vet1], vet2) + table.insert(self.adj_list[vet2], vet1) +end + +--- 删除边 +--- @param vet1 Vertex 顶点1 +--- @param vet2 Vertex 顶点2 +--- @raise 当顶点不存在或顶点相同时抛出错误 +function GraphAdjList:remove_edge(vet1, vet2) + if not self.adj_list[vet1] or not self.adj_list[vet2] or vet1 == vet2 then + error("无效的边") + end + + -- 删除边 vet1 - vet2 + self:_remove_from_list(self.adj_list[vet1], vet2) + self:_remove_from_list(self.adj_list[vet2], vet1) +end + +--- 添加顶点 +--- @param vet Vertex 要添加的顶点 +function GraphAdjList:add_vertex(vet) + if self.adj_list[vet] then + return + end + + -- 在邻接表中添加一个新链表 + self.adj_list[vet] = {} +end + +--- 删除顶点 +--- @param vet Vertex 要删除的顶点 +--- @raise 当顶点不存在时抛出错误 +function GraphAdjList:remove_vertex(vet) + if not self.adj_list[vet] then + error("顶点不存在") + end + + -- 在邻接表中删除顶点 vet 对应的链表 + self.adj_list[vet] = nil + + -- 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex, adj_vertices in pairs(self.adj_list) do + self:_remove_from_list(adj_vertices, vet) + end +end + +--- 打印邻接表 +function GraphAdjList:print() + print("邻接表 =") + for vertex, adj_vertices in pairs(self.adj_list) do + local tmp = {} + for _, v in ipairs(adj_vertices) do + table.insert(tmp, v.val) + end + print(string.format("%d: [%s],", vertex.val, table.concat(tmp, ", "))) + end +end + +-- 私有方法:从列表中删除指定元素 +function GraphAdjList:_remove_from_list(list, item) + for i = #list, 1, -1 do + if list[i] == item then + table.remove(list, i) + break + end + end +end + +-- Driver Code +local function main() + -- 初始化无向图 + local v = vals_to_vets({ 1, 3, 2, 5, 4 }) + local edges = { + { v[1], v[2] }, + { v[1], v[4] }, + { v[2], v[3] }, + { v[3], v[4] }, + { v[3], v[5] }, + { v[4], v[5] }, + } + local graph = GraphAdjList.new(edges) + print("\n初始化后,图为") + graph:print() + + -- 添加边 + -- 顶点 1, 2 即 v[1], v[3] + graph:add_edge(v[1], v[3]) + print("\n添加边 1-2 后,图为") + graph:print() + + -- 删除边 + -- 顶点 1, 3 即 v[1], v[2] + graph:remove_edge(v[1], v[2]) + print("\n删除边 1-3 后,图为") + graph:print() + + -- 添加顶点 + local v5 = Vertex.new(6) + graph:add_vertex(v5) + print("\n添加顶点 6 后,图为") + graph:print() + + -- 删除顶点 + -- 顶点 3 即 v[2] + graph:remove_vertex(v[2]) + print("\n删除顶点 3 后,图为") + graph:print() +end + + +if ... then + -- 被 require 作为模块加载 + return GraphAdjList +else + -- 执行主函数 + main() +end diff --git a/codes/lua/chapter_graph/graph_adjacency_matrix.lua b/codes/lua/chapter_graph/graph_adjacency_matrix.lua new file mode 100644 index 0000000000..f87153fd3c --- /dev/null +++ b/codes/lua/chapter_graph/graph_adjacency_matrix.lua @@ -0,0 +1,161 @@ +-- @script graph_adjacency_matrix.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") + +--- @class GraphAdjMat +--- 基于邻接矩阵实现的无向图类 +--- @field vertices table 顶点列表,元素代表"顶点值" +--- @field adj_mat table 邻接矩阵 +local GraphAdjMat = {} +GraphAdjMat.__index = GraphAdjMat + +--- 构造方法 +--- @param vertices table 顶点列表,元素代表"顶点值" +--- @param edges table 边列表,元素代表顶点索引,即对应vertices元素索引 +--- @return GraphAdjMat +function GraphAdjMat.new(vertices, edges) + local obj = setmetatable({}, GraphAdjMat) + + -- 顶点列表,元素代表"顶点值",索引代表"顶点索引" + obj.vertices = {} + -- 邻接矩阵,行列索引对应"顶点索引" + obj.adj_mat = {} + + -- 添加顶点 + for _, val in ipairs(vertices) do + obj:add_vertex(val) + end + + -- 添加边 + for _, e in ipairs(edges) do + obj:add_edge(e[1], e[2]) + end + + return obj +end + +--- 获取顶点数量 +--- @return integer +function GraphAdjMat:size() + return #self.vertices +end + +--- 添加顶点 +--- @param val number 顶点值 +function GraphAdjMat:add_vertex(val) + local n = self:size() + + -- 向顶点列表中添加新顶点的值 + table.insert(self.vertices, val) + + -- 在邻接矩阵中添加一行 + local new_row = {} + for i = 1, n do + new_row[i] = 0 + end + table.insert(self.adj_mat, new_row) + + -- 在邻接矩阵中添加一列 + for _, row in ipairs(self.adj_mat) do + row[n + 1] = 0 + end +end + +--- 删除顶点 +--- @param index integer 顶点索引 +function GraphAdjMat:remove_vertex(index) + if index > self:size() or index < 1 then + error("Index out of range") + end + + -- 在顶点列表中移除索引index的顶点 + table.remove(self.vertices, index) + + -- 在邻接矩阵中删除索引index的行 + table.remove(self.adj_mat, index) + + -- 在邻接矩阵中删除索引index的列 + for _, row in ipairs(self.adj_mat) do + table.remove(row, index) + end +end + +--- 添加边 +--- @param i integer 顶点i的索引,对应vertices元素索引 +--- @param j integer 顶点j的索引,对应vertices元素索引 +function GraphAdjMat:add_edge(i, j) + -- 参数i, j对应vertices元素索引 + -- 索引越界与相等处理 + local n = self:size() + if i < 1 or j < 1 or i > n or j > n or i == j then + error("Index out of range or i equals j") + end + + -- 在无向图中,邻接矩阵关于主对角线对称,即满足(i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 +end + +--- 删除边 +--- @param i integer 顶点i的索引,对应vertices元素索引 +--- @param j integer 顶点j的索引,对应vertices元素索引 +function GraphAdjMat:remove_edge(i, j) + -- 参数i, j对应vertices元素索引 + -- 索引越界与相等处理 + local n = self:size() + if i < 1 or j < 1 or i > n or j > n or i == j then + error("Index out of range or i equals j") + end + + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 +end + +--- 打印邻接矩阵 +function GraphAdjMat:print() + print("顶点列表 = [" .. table.concat(self.vertices, ", ") .. "]") + print("邻接矩阵 =") + print_util.print_matrix(self.adj_mat) +end + +-- Driver Code +local function main() + -- 初始化无向图 + -- 请注意,edges元素代表顶点索引,即对应vertices元素索引 + local vertices = { 1, 3, 2, 5, 4 } + local edges = { { 1, 2 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 3, 5 }, { 4, 5 } } + local graph = GraphAdjMat.new(vertices, edges) + print("\n初始化后,图为") + graph:print() + + -- 添加边 + -- 顶点1, 2的索引分别为1, 3 + graph:add_edge(1, 3) + print("\n添加边 1-2 后,图为") + graph:print() + + -- 删除边 + -- 顶点1, 3的索引分别为1, 2 + graph:remove_edge(1, 2) + print("\n删除边 1-3 后,图为") + graph:print() + + -- 添加顶点 + graph:add_vertex(6) + print("\n添加顶点 6 后,图为") + graph:print() + + -- 删除顶点 + -- 顶点3的索引为2 + graph:remove_vertex(2) + print("\n删除顶点 3 后,图为") + graph:print() +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_graph/graph_bfs.lua b/codes/lua/chapter_graph/graph_bfs.lua new file mode 100644 index 0000000000..2af7a3bfca --- /dev/null +++ b/codes/lua/chapter_graph/graph_bfs.lua @@ -0,0 +1,72 @@ +-- @script graph_bfs.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" +package.path = package.path .. ";codes/lua/chapter_graph/graph_adjacency_list.lua" + +local GraphAdjList = require("graph_adjacency_list") +local vertex = require("vertex") +local vals_to_vets = vertex.vals_to_vets +local vets_to_vals = vertex.vets_to_vals + +--- 广度优先遍历 +--- @param graph GraphAdjList 图 +--- @param start_vet table 起始顶点 +--- @return table 顶点遍历序列 +local function graph_bfs(graph, start_vet) + -- 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + -- 顶点遍历序列 + local res = {} + -- 哈希集合,用于记录已被访问过的顶点 + local visited = {} + visited[start_vet] = true + -- 队列用于实现 BFS + local que = { start_vet } + -- 以顶点 vet 为起点,循环直至访问完所有顶点 + while #que > 0 do + local vet = table.remove(que, 1) -- 队首顶点出队 + table.insert(res, vet) -- 记录访问顶点 + -- 遍历该顶点的所有邻接顶点 + for _, adj_vet in ipairs(graph.adj_list[vet]) do + if not visited[adj_vet] then + table.insert(que, adj_vet) -- 只入队未访问的顶点 + visited[adj_vet] = true -- 标记该顶点已被访问 + end + end + end + -- 返回顶点遍历序列 + return res +end + +-- Driver Code +local function main() + -- 初始化无向图 + local v = vals_to_vets({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) + local edges = { + { v[1], v[2] }, + { v[1], v[4] }, + { v[2], v[3] }, + { v[2], v[5] }, + { v[3], v[6] }, + { v[4], v[5] }, + { v[4], v[7] }, + { v[5], v[6] }, + { v[5], v[8] }, + { v[6], v[9] }, + { v[7], v[8] }, + { v[8], v[9] }, + } + local graph = GraphAdjList.new(edges) + print("\n初始化后,图为") + graph:print() + + -- 广度优先遍历 + local res = graph_bfs(graph, v[1]) + print("\n广度优先遍历(BFS)顶点序列为") + print("[" .. table.concat(vets_to_vals(res), ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_graph/graph_dfs.lua b/codes/lua/chapter_graph/graph_dfs.lua new file mode 100644 index 0000000000..7e971a8eb0 --- /dev/null +++ b/codes/lua/chapter_graph/graph_dfs.lua @@ -0,0 +1,70 @@ +-- @script graph_dfs.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" +package.path = package.path .. ";codes/lua/chapter_graph/graph_adjacency_list.lua" + +local GraphAdjList = require("graph_adjacency_list") +local vertex = require("vertex") +local vets_to_vals = vertex.vets_to_vals +local vals_to_vets = vertex.vals_to_vets + +--- 深度优先遍历辅助函数 +--- @param graph GraphAdjList 图 +--- @param visited table 已访问顶点集合 +--- @param res table 顶点遍历序列 +--- @param vet Vertex 当前顶点 +local function dfs(graph, visited, res, vet) + table.insert(res, vet) -- 记录访问顶点 + visited[vet] = true -- 标记该顶点已被访问 + + -- 遍历该顶点的所有邻接顶点 + for _, adjVet in ipairs(graph.adj_list[vet] or {}) do + if not visited[adjVet] then + -- 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + end + end +end + +--- 深度优先遍历 +--- @param graph GraphAdjList 图 +--- @param start_vet table 起始顶点 +--- @return table 顶点遍历序列 +local function dfs_traversal(graph, start_vet) + -- 顶点遍历序列 + local res = {} + -- 哈希表,用于记录已被访问过的顶点 + local visited = {} + + dfs(graph, visited, res, start_vet) + return res +end + +-- Driver Code +local function main() + -- 初始化无向图 + local v = vals_to_vets({ 0, 1, 2, 3, 4, 5, 6 }) + local edges = { + { v[1], v[2] }, + { v[1], v[4] }, + { v[2], v[3] }, + { v[3], v[6] }, + { v[5], v[6] }, + { v[6], v[7] }, + } + + local graph = GraphAdjList.new(edges) + print("\n初始化后,图为") + graph:print() + + -- 深度优先遍历 + local res = dfs_traversal(graph, v[1]) + print("\n深度优先遍历(DFS)顶点序列为") + print("[" .. table.concat(vets_to_vals(res), ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_greedy/coin_change_greedy.lua b/codes/lua/chapter_greedy/coin_change_greedy.lua new file mode 100644 index 0000000000..002a38e083 --- /dev/null +++ b/codes/lua/chapter_greedy/coin_change_greedy.lua @@ -0,0 +1,56 @@ +-- @script coin_change_greedy.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 零钱兑换:贪心 +--- @param coins table 可用的硬币面值列表 +--- @param amt integer 目标金额 +--- @return integer 最少硬币数量,如果无法凑出则返回-1 +local function coin_change_greedy(coins, amt) + local i = #coins -- 从最大面额开始 + local count = 0 + + -- 循环进行贪心选择,直到无剩余金额 + while amt > 0 do + -- 找到小于且最接近剩余金额的硬币 + while i > 1 and coins[i] > amt do + i = i - 1 + end + + -- 选择当前可用的最大面额硬币 + amt = amt - coins[i] + count = count + 1 + end + + -- 若未找到可行方案,则返回 -1 + return amt == 0 and count or -1 +end + +-- Driver Code +local function main() + -- 测试用例1:贪心能够保证找到全局最优解 + local coins1 = { 1, 5, 10, 20, 50, 100 } + local amt1 = 186 + local res1 = coin_change_greedy(coins1, amt1) + print(string.format("\ncoins = [%s], amt = %d", table.concat(coins1, ", "), amt1)) + print(string.format("凑到 %d 所需的最少硬币数量为 %d", amt1, res1)) + + -- 测试用例2:贪心无法保证找到全局最优解 + local coins2 = { 1, 20, 50 } + local amt2 = 60 + local res2 = coin_change_greedy(coins2, amt2) + print(string.format("\ncoins = [%s], amt = %d", table.concat(coins2, ", "), amt2)) + print(string.format("凑到 %d 所需的最少硬币数量为 %d", amt2, res2)) + print("实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + -- 测试用例3:贪心无法保证找到全局最优解 + local coins3 = { 1, 49, 50 } + local amt3 = 98 + local res3 = coin_change_greedy(coins3, amt3) + print(string.format("\ncoins = [%s], amt = %d", table.concat(coins3, ", "), amt3)) + print(string.format("凑到 %d 所需的最少硬币数量为 %d", amt3, res3)) + print("实际上需要的最少数量为 2 ,即 49 + 49") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_greedy/fractional_knapsack.lua b/codes/lua/chapter_greedy/fractional_knapsack.lua new file mode 100644 index 0000000000..569ef2cf27 --- /dev/null +++ b/codes/lua/chapter_greedy/fractional_knapsack.lua @@ -0,0 +1,70 @@ +-- @script fractional_knapsack.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class Item +--- 物品类 +--- @field w number 物品重量 +--- @field v number 物品价值 +local Item = {} +Item.__index = Item + +--- 构造函数 +--- @param w number 物品重量 +--- @param v number 物品价值 +--- @return Item 物品实例 +function Item.new(w, v) + local instance = setmetatable({}, Item) + instance.w = w + instance.v = v + return instance +end + +--- 分数背包贪心算法 +--- @param wgt table 物品重量列表 +--- @param val table 物品价值列表 +--- @param cap number 背包容量 +--- @return number 最大价值 +local function fractional_knapsack(wgt, val, cap) + -- 创建物品列表 + local items = {} + for i = 1, #wgt do + table.insert(items, Item.new(wgt[i], val[i])) + end + + -- 按单位价值从高到低排序 + table.sort(items, function(a, b) + return (a.v / a.w) > (b.v / b.w) + end) + + local res = 0 + + -- 贪心选择 + for _, item in ipairs(items) do + if item.w <= cap then + -- 剩余容量充足,装入整个物品 + res = res + item.v + cap = cap - item.w + else + -- 剩余容量不足,装入部分物品 + res = res + (item.v / item.w) * cap + break + end + end + + return res +end + +-- Driver Code +local function main() + local wgt = { 10, 20, 30, 40, 50 } + local val = { 50, 120, 150, 210, 240 } + local cap = 50 + + -- 贪心算法 + local res = fractional_knapsack(wgt, val, cap) + print(string.format("不超过背包容量的最大物品价值为 %.2f", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_greedy/max_capacity.lua b/codes/lua/chapter_greedy/max_capacity.lua new file mode 100644 index 0000000000..3ab0b08cc2 --- /dev/null +++ b/codes/lua/chapter_greedy/max_capacity.lua @@ -0,0 +1,39 @@ +-- @script max_capacity.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 最大容量:贪心 +--- @param ht table 高度数组 +--- @return integer 最大容量 +local function max_capacity(ht) + -- 初始化 i, j,使其分列数组两端 + local i = 1 + local j = #ht + -- 初始最大容量为 0 + local res = 0 + -- 循环贪心选择,直至两板相遇 + while i < j do + -- 更新最大容量 + local cap = math.min(ht[i], ht[j]) * (j - i) + res = math.max(res, cap) + -- 向内移动短板 + if ht[i] < ht[j] then + i = i + 1 + else + j = j - 1 + end + end + return res +end + +-- Driver Code +local function main() + local ht = { 3, 8, 5, 2, 7, 7, 3, 4 } + + -- 贪心算法 + local res = max_capacity(ht) + print(string.format("最大容量为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_greedy/max_product_cutting.lua b/codes/lua/chapter_greedy/max_product_cutting.lua new file mode 100644 index 0000000000..e9d8681403 --- /dev/null +++ b/codes/lua/chapter_greedy/max_product_cutting.lua @@ -0,0 +1,40 @@ +-- @script max_product_cutting.lua +-- @date 2025-11-16 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 最大切分乘积:贪心 +--- @param n integer 要切分的整数 +--- @return integer 最大乘积结果 +local function max_product_cutting(n) + -- 当 n <= 3 时,必须切分出一个 1 + if n <= 3 then + return 1 * (n - 1) + end + + -- 贪心地切分出 3,a 为 3 的个数,b 为余数 + local a = n // 3 + local b = n % 3 + + if b == 1 then + -- 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return 3 ^ (a - 1) * 2 * 2 + elseif b == 2 then + -- 当余数为 2 时,不做处理 + return 3 ^ a * 2 + else + -- 当余数为 0 时,不做处理 + return 3 ^ a + end +end + +-- Driver Code +local function main() + local n = 58 + + -- 贪心算法 + local res = max_product_cutting(n) + print(string.format("最大切分乘积为 %d", res)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_hashing/array_hash_map.lua b/codes/lua/chapter_hashing/array_hash_map.lua new file mode 100644 index 0000000000..e332c9fbc5 --- /dev/null +++ b/codes/lua/chapter_hashing/array_hash_map.lua @@ -0,0 +1,155 @@ +-- @script: array_hash_map.lua +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local Pair = require("pair") + +--- @class ArrayHashMap +--- 基于数组实现的哈希表 +--- @field buckets table 桶数组 +local ArrayHashMap = {} + +---构造方法 +---@return ArrayHashMap +function ArrayHashMap:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + -- 初始化数组,包含100个桶 + obj.buckets = {} + for i = 1, 100 do + obj.buckets[i] = "" + end + return obj +end + +--- 哈希函数 +--- @param key integer 键 +--- @return integer +function ArrayHashMap:hash_func(key) + local index = key % 100 + 1 -- Lua数组索引从1开始 + return index +end + +--- 查询操作 +--- @param key integer 键 +--- @return string|nil +function ArrayHashMap:get(key) + local index = self:hash_func(key) + local pair = self.buckets[index] + if pair == "" then + return nil + end + return pair.val +end + +--- 添加和更新操作 +--- @param key integer 键 +--- @param val string 值 +function ArrayHashMap:put(key, val) + local pair = Pair:new(key, val) + local index = self:hash_func(key) + self.buckets[index] = pair +end + +--- 删除操作 +--- @param key integer 键 +function ArrayHashMap:remove(key) + local index = self:hash_func(key) + -- 置为"",代表删除 + self.buckets[index] = "" +end + +--- 获取所有键值对 +--- @return table +function ArrayHashMap:entry_set() + local result = {} + for _, pair in ipairs(self.buckets) do + if pair ~= "" then + table.insert(result, pair) + end + end + return result +end + +--- 获取所有键 +--- @return table +function ArrayHashMap:key_set() + local result = {} + for _, pair in ipairs(self.buckets) do + if pair ~= "" then + table.insert(result, pair.key) + end + end + return result +end + +--- 获取所有值 +--- @return table +function ArrayHashMap:value_set() + local result = {} + for _, pair in ipairs(self.buckets) do + if pair ~= "" then + table.insert(result, pair.val) + end + end + return result +end + +--- 打印哈希表 +function ArrayHashMap:print() + for _, pair in ipairs(self.buckets) do + if pair ~= "" then + print(pair.key .. " -> " .. pair.val) + end + end +end + +-- Driver Code +local function main() + -- 初始化哈希表 + local hmap = ArrayHashMap:new() + + -- 添加操作 + -- 在哈希表中添加键值对 (key, value) + hmap:put(12836, "小哈") + hmap:put(15937, "小啰") + hmap:put(16750, "小算") + hmap:put(13276, "小法") + hmap:put(10583, "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + hmap:print() + + -- 查询操作 + -- 向哈希表中输入键 key ,得到值 value + local name = hmap:get(15937) + print("\n输入学号 15937 ,查询到姓名 " .. name) + + -- 删除操作 + -- 在哈希表中删除键值对 (key, value) + hmap:remove(10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + hmap:print() + + -- 遍历哈希表 + print("\n遍历键值对 Key->Value") + for _, pair in ipairs(hmap:entry_set()) do + print(pair.key .. " -> " .. pair.val) + end + + print("\n单独遍历键 Key") + for _, key in ipairs(hmap:key_set()) do + print(key) + end + + print("\n单独遍历值 Value") + for _, val in ipairs(hmap:value_set()) do + print(val) + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_hashing/built_in_hash.lua b/codes/lua/chapter_hashing/built_in_hash.lua new file mode 100644 index 0000000000..d8a2ba1b22 --- /dev/null +++ b/codes/lua/chapter_hashing/built_in_hash.lua @@ -0,0 +1 @@ +-- Lua 未提供内置 hash code 函数 \ No newline at end of file diff --git a/codes/lua/chapter_hashing/hash_map.lua b/codes/lua/chapter_hashing/hash_map.lua new file mode 100644 index 0000000000..f1eaa8a6a1 --- /dev/null +++ b/codes/lua/chapter_hashing/hash_map.lua @@ -0,0 +1,72 @@ +-- @script: hash_map.lua +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") + +--- 根据 key 删除 table 中的元素 +--- @param tbl table +--- @param key any +--- @return table +local function remove_by_key(tbl, key) + -- Lua table 不支持按 key 删除元素,因此需要重构整张 table + local new_table = {} + for _key, _value in pairs(tbl) do + if _key ~= key then + new_table[_key] = _value + end + end + return new_table +end + +-- Driver Code +local function main() + -- 初始化哈希表 + -- Lua 内置的表(table)本身就是哈希表 + local hmap = {} + + -- 添加操作 + -- 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + print("\n添加完成后,哈希表为\nKey -> Value") + print_util.print_dict(hmap) + + -- 查询操作 + -- 向哈希表中输入键 key ,得到值 value + local name = hmap[15937] + print("\n输入学号 15937 ,查询到姓名 " .. name) + + -- 删除操作 + -- 在哈希表中删除键值对 (key, value) + hmap = remove_by_key(hmap, 10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + print_util.print_dict(hmap) + + -- 遍历哈希表 - 键值对 + print("\n遍历键值对 Key->Value") + for key, value in pairs(hmap) do + print(key .. " -> " .. value) + end + + -- 单独遍历键 + print("\n单独遍历键 Key") + for key in pairs(hmap) do + print(key) + end + + -- 单独遍历值 + print("\n单独遍历值 Value") + for _, value in pairs(hmap) do + print(value) + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_hashing/hash_map_chaining.lua b/codes/lua/chapter_hashing/hash_map_chaining.lua new file mode 100644 index 0000000000..da4dcd2a05 --- /dev/null +++ b/codes/lua/chapter_hashing/hash_map_chaining.lua @@ -0,0 +1,171 @@ +-- @script: hash_map_chaining.lua +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local Pair = require("pair") + +--- @class HashMapChaining +--- 链式地址哈希表 +--- @field size integer 键值对数量 +--- @field capacity integer 哈希表容量 +--- @field load_thres number 触发扩容的负载因子阈值 +--- @field extend_ratio integer 扩容倍数 +local HashMapChaining = {} +HashMapChaining.__index = HashMapChaining + +--- 构造方法 +--- @return HashMapChaining +function HashMapChaining.new() + local obj = {} + setmetatable(obj, HashMapChaining) + obj.size = 0 -- 键值对数量 + obj.capacity = 4 -- 哈希表容量 + obj.load_thres = 2.0 / 3.0 -- 触发扩容的负载因子阈值 + obj.extend_ratio = 2 -- 扩容倍数 + obj.buckets = {} -- 桶数组 + + -- 初始化桶数组 + for i = 1, obj.capacity do + obj.buckets[i] = "" + end + + return obj +end + +--- 哈希函数 +--- @param key integer 键 +--- @return integer +function HashMapChaining:hash_func(key) + return key % self.capacity + 1 -- Lua 数组索引从1开始 +end + +--- 负载因子 +--- @return number +function HashMapChaining:load_factor() + return self.size / self.capacity +end + +--- 查询操作 +--- @param key integer 键 +--- @return string|nil +function HashMapChaining:get(key) + local index = self:hash_func(key) + local bucket = self.buckets[index] + + -- 遍历桶,若找到 key ,则返回对应 val + for _, pair in ipairs(bucket) do + if pair.key == key then + return pair.val + end + end + + -- 若未找到 key ,则返回 nil + return nil +end + +--- 添加操作 +--- @param key integer 键 +--- @param val string 值 +function HashMapChaining:put(key, val) + -- 当负载因子超过阈值时,执行扩容 + if self:load_factor() > self.load_thres then + self:extend() + end + + local index = self:hash_func(key) + local bucket = self.buckets[index] + + -- 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for _, pair in ipairs(bucket) do + if pair.key == key then + pair.val = val + return + end + end + + -- 若无该 key ,则将键值对添加至尾部 + local pair = Pair:new(key, val) + table.insert(bucket, pair) + self.size = self.size + 1 +end + +--- 删除操作 +--- @param key integer 键 +function HashMapChaining:remove(key) + local index = self:hash_func(key) + local bucket = self.buckets[index] + + -- 遍历桶,从中删除键值对 + for i, pair in ipairs(bucket) do + if pair.key == key then + table.remove(bucket, i) + self.size = self.size - 1 + break + end + end +end + +--- 扩容哈希表 +function HashMapChaining:extend() + -- 暂存原哈希表 + local old_buckets = self.buckets + + -- 初始化扩容后的新哈希表 + self.capacity = self.capacity * self.extend_ratio + self.buckets = {} + for i = 1, self.capacity do + self.buckets[i] = "" + end + self.size = 0 + + -- 将键值对从原哈希表搬运至新哈希表 + for _, bucket in ipairs(old_buckets) do + for _, pair in ipairs(bucket) do + self:put(pair.key, pair.val) + end + end +end + +--- 打印哈希表 +function HashMapChaining:print() + for _, bucket in ipairs(self.buckets) do + local res = {} + for _, pair in ipairs(bucket) do + table.insert(res, tostring(pair.key) .. " -> " .. pair.val) + end + print("[" .. table.concat(res, ", ") .. "]") + end +end + +-- Driver Code +local function main() + -- 初始化哈希表 + local hashmap = HashMapChaining.new() + + -- 添加操作 + -- 在哈希表中添加键值对 (key, value) + hashmap:put(12836, "小哈") + hashmap:put(15937, "小啰") + hashmap:put(16750, "小算") + hashmap:put(13276, "小法") + hashmap:put(10583, "小鸭") + print("\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap:print() + + -- 查询操作 + -- 向哈希表中输入键 key ,得到值 value + local name = hashmap:get(13276) + print("\n输入学号 13276 ,查询到姓名 " .. name) + + -- 删除操作 + -- 在哈希表中删除键值对 (key, value) + hashmap:remove(12836) + print("\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap:print() +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_hashing/hash_map_open_addressing.lua b/codes/lua/chapter_hashing/hash_map_open_addressing.lua new file mode 100644 index 0000000000..e63f1821a1 --- /dev/null +++ b/codes/lua/chapter_hashing/hash_map_open_addressing.lua @@ -0,0 +1,205 @@ +-- @script: hash_map_open_addressing.lua +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local Pair = require("pair") + +--- @class HashMapOpenAddressing +--- 开放寻址哈希表 +--- @field size integer 键值对数量 +--- @field capacity integer 哈希表容量 +--- @field load_thres number 触发扩容的负载因子阈值 +--- @field extend_ratio integer 扩容倍数 +--- @field buckets table 桶数组 +--- @field TOMBSTONE Pair 删除标记 +local HashMapOpenAddressing = {} +HashMapOpenAddressing.__index = HashMapOpenAddressing + +--- 构造方法 +--- @return HashMapOpenAddressing +function HashMapOpenAddressing.new() + local obj = setmetatable({}, HashMapOpenAddressing) + obj.size = 0 -- 键值对数量 + obj.capacity = 4 -- 哈希表容量 + obj.load_thres = 2.0 / 3.0 -- 触发扩容的负载因子阈值 + obj.extend_ratio = 2 -- 扩容倍数 + obj.buckets = {} -- 桶数组 + obj.TOMBSTONE = Pair:new(-1, "-1") -- 删除标记 + + -- 初始化桶数组 + for i = 1, obj.capacity do + obj.buckets[i] = "" + end + + return obj +end + +--- 哈希函数 +--- @param key integer 键 +--- @return integer +function HashMapOpenAddressing:hash_func(key) + return (key % self.capacity) + 1 -- Lua 数组索引从 1 开始 +end + +--- 计算负载因子 +--- @return number +function HashMapOpenAddressing:load_factor() + return self.size / self.capacity +end + +--- 搜索 key 对应的桶索引 +--- @param key integer 要搜索的键 +--- @return integer +function HashMapOpenAddressing:find_bucket(key) + local index = self:hash_func(key) + local first_tombstone = -1 + + -- 线性探测,当遇到空桶时跳出 + while self.buckets[index] ~= "" do + -- 若遇到 key,返回对应的桶索引 + if self.buckets[index].key == key then + -- 若之前遇到了删除标记,则将键值对移动至该索引处 + if first_tombstone ~= -1 then + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone -- 返回移动后的桶索引 + end + return index -- 返回桶索引 + end + + -- 记录遇到的首个删除标记 + if first_tombstone == -1 and self.buckets[index] == self.TOMBSTONE then + first_tombstone = index + end + + -- 计算桶索引,越过尾部则返回头部 + index = (index % self.capacity) + 1 + end + + -- 若 key 不存在,则返回添加点的索引 + return first_tombstone == -1 and index or first_tombstone +end + +--- 查询操作 +--- @param key integer 要查询的键 +--- @return string|nil +function HashMapOpenAddressing:get(key) + -- 搜索 key 对应的桶索引 + local index = self:find_bucket(key) + + -- 若找到键值对,则返回对应 val + if self.buckets[index] ~= "" and self.buckets[index] ~= self.TOMBSTONE then + return self.buckets[index].val + end + + -- 若键值对不存在,则返回 nil + return nil +end + +--- 添加操作 +--- @param key integer 键 +--- @param val string 值 +function HashMapOpenAddressing:put(key, val) + -- 当负载因子超过阈值时,执行扩容 + if self:load_factor() > self.load_thres then + self:extend() + end + + -- 搜索 key 对应的桶索引 + local index = self:find_bucket(key) + + -- 若找到键值对,则覆盖 val 并返回 + if self.buckets[index] ~= "" and self.buckets[index] ~= self.TOMBSTONE then + self.buckets[index].val = val + return + end + + -- 若键值对不存在,则添加该键值对 + self.buckets[index] = Pair:new(key, val) + self.size = self.size + 1 +end + +--- 删除操作 +--- @param key integer 要删除的键 +function HashMapOpenAddressing:remove(key) + -- 搜索 key 对应的桶索引 + local index = self:find_bucket(key) + + -- 若找到键值对,则用删除标记覆盖它 + if self.buckets[index] ~= "" and self.buckets[index] ~= self.TOMBSTONE then + self.buckets[index] = self.TOMBSTONE + self.size = self.size - 1 + end +end + +--- 扩容哈希表 +function HashMapOpenAddressing:extend() + -- 暂存原哈希表 + local buckets_tmp = {} + for i, v in ipairs(self.buckets) do + buckets_tmp[i] = v + end + + -- 初始化扩容后的新哈希表 + self.capacity = self.capacity * self.extend_ratio + self.buckets = {} + self.size = 0 + + -- 初始化新桶数组 + for i = 1, self.capacity do + self.buckets[i] = "" + end + + -- 将键值对从原哈希表搬运至新哈希表 + for _, pair in ipairs(buckets_tmp) do + if pair ~= "" and pair ~= self.TOMBSTONE then + self:put(pair.key, pair.val) + end + end +end + +--- 打印哈希表 +function HashMapOpenAddressing:print() + for i, pair in ipairs(self.buckets) do + if pair == "" then + print("None") + elseif pair == self.TOMBSTONE then + print("TOMBSTONE") + else + print(pair.key .. " -> " .. pair.val) + end + end +end + +-- Driver Code +local function main() + -- 初始化哈希表 + local hashmap = HashMapOpenAddressing.new() + + -- 添加操作 + -- 在哈希表中添加键值对 (key, val) + hashmap:put(12836, "小哈") + hashmap:put(15937, "小啰") + hashmap:put(16750, "小算") + hashmap:put(13276, "小法") + hashmap:put(10583, "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + hashmap:print() + + -- 查询操作 + -- 向哈希表中输入键 key ,得到值 val + local name = hashmap:get(13276) + print("\n输入学号 13276 ,查询到姓名 " .. name) + + -- 删除操作 + -- 在哈希表中删除键值对 (key, val) + hashmap:remove(16750) + print("\n删除 16750 后,哈希表为\nKey -> Value") + hashmap:print() +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_hashing/simple_hash.lua b/codes/lua/chapter_hashing/simple_hash.lua new file mode 100644 index 0000000000..76e11c7c9e --- /dev/null +++ b/codes/lua/chapter_hashing/simple_hash.lua @@ -0,0 +1,79 @@ +-- @script: simple_hash.lua +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 加法哈希 +--- @param key string 输入字符串 +--- @return integer +local function add_hash(key) + local hash = 0 + local modulus = 1000000007 + for i = 1, #key do + local c = string.byte(key, i) + hash = hash + c + end + return hash % modulus +end + +--- 乘法哈希 +--- @param key string 输入字符串 +--- @return integer +local function mul_hash(key) + local hash = 0 + local modulus = 1000000007 + for i = 1, #key do + local c = string.byte(key, i) + hash = 31 * hash + c + end + return hash % modulus +end + +--- 异或哈希 +--- @param key string 输入字符串 +--- @return integer +--- @warning Lua 5.3及以上版本才支持位运算符 +local function xor_hash(key) + local hash = 0 + local modulus = 1000000007 + for i = 1, #key do + local c = string.byte(key, i) + hash = hash ~ c + end + return hash % modulus +end + +--- 旋转哈希 +--- @param key string 输入字符串 +--- @return integer +--- @warning Lua 5.3及以上版本才支持位运算符 +local function rot_hash(key) + local hash = 0 + local modulus = 1000000007 + for i = 1, #key do + local c = string.byte(key, i) + hash = ((hash << 4) ~ (hash >> 28) ~ c) & 0x7FFFFFFF + end + return hash % modulus +end + + +-- Driver Code +local function main() + local key = "Hello 算法" + + local hash = add_hash(key) + print(string.format("加法哈希值为 %d", hash)) + + hash = mul_hash(key) + print(string.format("乘法哈希值为 %d", hash)) + + hash = xor_hash(key) + print(string.format("异或哈希值为 %d", hash)) + + hash = rot_hash(key) + print(string.format("旋转哈希值为 %d", hash)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_heap/heap.lua b/codes/lua/chapter_heap/heap.lua new file mode 100644 index 0000000000..2a14a37e80 --- /dev/null +++ b/codes/lua/chapter_heap/heap.lua @@ -0,0 +1,94 @@ +-- @script heap.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +-- Lua 没有内置堆模块,我手动实现了一个 +local heapq = require("heapq") + +--- 元素入堆测试 +--- @param heap table 堆数组 +--- @param val integer 要插入的值 +--- @param flag integer 堆类型标志 (1: 小顶堆, -1: 大顶堆) +local function test_push(heap, val, flag) + flag = flag or 1 + heapq.heappush(heap, flag * val) + + print(string.format("\n元素 %d 入堆后", val)) + -- 打印堆时恢复原始值 + local display_heap = {} + for _, v in ipairs(heap) do + table.insert(display_heap, flag * v) + end + print_util.print_heap(display_heap) +end + +--- 堆顶元素出堆测试 +--- @param heap table 堆数组 +--- @param flag integer 堆类型标志 (1: 小顶堆, -1: 大顶堆) +local function test_pop(heap, flag) + flag = flag or 1 + local val = flag * heapq.heappop(heap) + + print(string.format("\n堆顶元素 %d 出堆后", val)) + -- 打印堆时恢复原始值 + local display_heap = {} + for _, v in ipairs(heap) do + table.insert(display_heap, flag * v) + end + print_util.print_heap(display_heap) +end + + +-- Driver Code +local function main() + -- 初始化小顶堆 + local min_heap, flag = {}, 1 + + -- 初始化大顶堆 + local max_heap, flag = {}, -1 + + print("\n以下测试样例为大顶堆") + -- 这里手动实现的 heapq 模块与 Python 保持一致,默认实现小顶堆 + -- 考虑将"元素取负"后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + -- 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + -- 元素入堆 + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + -- 获取堆顶元素 + local peek = flag * max_heap[1] + print(string.format("\n堆顶元素为 %d", peek)) + + -- 堆顶元素出堆 + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + -- 获取堆大小 + local size = #max_heap + print(string.format("\n堆元素数量为 %d", size)) + + -- 判断堆是否为空 + local is_empty = #max_heap == 0 + print(string.format("\n堆是否为空 %s", tostring(is_empty))) + + -- 输入列表并建堆 + -- 时间复杂度为 O(n) ,而非 O(nlogn) + min_heap = { 1, 3, 2, 5, 4 } + heapq.heapify(min_heap) + print("\n输入列表并建立小顶堆后") + print_util.print_heap(min_heap) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_heap/my_heap.lua b/codes/lua/chapter_heap/my_heap.lua new file mode 100644 index 0000000000..e3c5a132e0 --- /dev/null +++ b/codes/lua/chapter_heap/my_heap.lua @@ -0,0 +1,193 @@ +-- @script my_heap.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") + +--- @class MaxHeap +--- 大顶堆实现 +--- @field max_heap table 堆数组 +local MaxHeap = {} +MaxHeap.__index = MaxHeap + +--- 构造方法,根据输入列表建堆 +--- @param nums table 输入数字列表 +--- @return MaxHeap +function MaxHeap.new(nums) + local obj = setmetatable({}, MaxHeap) + -- 将列表元素原封不动添加进堆 + obj.max_heap = {} + for i, v in ipairs(nums) do + obj.max_heap[i] = v + end + + -- 堆化除叶节点以外的其他所有节点 + local last_non_leaf = math.floor((#obj.max_heap - 1) / 2) + for i = last_non_leaf, 1, -1 do + obj:sift_down(i) + end + + return obj +end + +--- 获取左子节点的索引 +--- @param i integer 当前节点索引 +--- @return integer +function MaxHeap:left(i) + return 2 * i +end + +--- 获取右子节点的索引 +--- @param i integer 当前节点索引 +--- @return integer +function MaxHeap:right(i) + return 2 * i + 1 +end + +--- 获取父节点的索引 +--- @param i integer 当前节点索引 +--- @return integer +function MaxHeap:parent(i) + return math.floor(i / 2) +end + +--- 交换元素 +--- @param i integer 索引1 +--- @param j integer 索引2 +function MaxHeap:swap(i, j) + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] +end + +--- 获取堆大小 +--- @return integer +function MaxHeap:size() + return #self.max_heap +end + +--- 判断堆是否为空 +--- @return boolean +function MaxHeap:is_empty() + return self:size() == 0 +end + +--- 访问堆顶元素 +--- @return integer +function MaxHeap:peek() + if self:is_empty() then + error("堆为空") + end + return self.max_heap[1] +end + +--- 元素入堆 +--- @param val integer 要插入的值 +function MaxHeap:push(val) + -- 添加节点 + table.insert(self.max_heap, val) + -- 从底至顶堆化 + self:sift_up(self:size()) +end + +--- 从节点 i 开始,从底至顶堆化 +--- @param i integer 节点索引 +function MaxHeap:sift_up(i) + while true do + -- 获取节点 i 的父节点 + local p = self:parent(i) + -- 当"越过根节点"或"节点无须修复"时,结束堆化 + if p < 1 or self.max_heap[i] <= self.max_heap[p] then + break + end + -- 交换两节点 + self:swap(i, p) + -- 循环向上堆化 + i = p + end +end + +--- 元素出堆 +--- @return integer +function MaxHeap:pop() + -- 判空处理 + if self:is_empty() then + error("堆为空") + end + -- 交换根节点与最右叶节点(交换首元素与尾元素) + self:swap(1, self:size()) + -- 删除节点 + local val = table.remove(self.max_heap) + -- 从顶至底堆化 + if not self:is_empty() then + self:sift_down(1) + end + -- 返回堆顶元素 + return val +end + +--- 从节点 i 开始,从顶至底堆化 +--- @param i integer 节点索引 +function MaxHeap:sift_down(i) + while true do + -- 判断节点 i, l, r 中值最大的节点,记为 ma + local l = self:left(i) + local r = self:right(i) + local ma = i + + if l <= self:size() and self.max_heap[l] > self.max_heap[ma] then + ma = l + end + if r <= self:size() and self.max_heap[r] > self.max_heap[ma] then + ma = r + end + -- 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i then + break + end + -- 交换两节点 + self:swap(i, ma) + -- 循环向下堆化 + i = ma + end +end + +--- 打印堆(二叉树) +function MaxHeap:print() + print_util.print_heap(self.max_heap) +end + +-- 测试代码 +local function main() + -- 初始化大顶堆 + local max_heap = MaxHeap.new({ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }) + print("\n输入列表并建堆后") + max_heap:print() + + -- 获取堆顶元素 + local peek = max_heap:peek() + print("\n堆顶元素为 " .. peek) + + -- 元素入堆 + local val = 7 + max_heap:push(val) + print("\n元素 " .. val .. " 入堆后") + max_heap:print() + + -- 堆顶元素出堆 + peek = max_heap:pop() + print("\n堆顶元素 " .. peek .. " 出堆后") + max_heap:print() + + -- 获取堆大小 + local size = max_heap:size() + print("\n堆元素数量为 " .. size) + + -- 判断堆是否为空 + local is_empty = max_heap:is_empty() + print("\n堆是否为空 " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_heap/top_k.lua b/codes/lua/chapter_heap/top_k.lua new file mode 100644 index 0000000000..4e35204798 --- /dev/null +++ b/codes/lua/chapter_heap/top_k.lua @@ -0,0 +1,47 @@ +-- @script top_k.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +-- Lua 没有内置堆模块,我手动实现了一个 +local heapq = require("heapq") + +--- 基于堆查找数组中最大的 k 个元素 +--- @param nums table 输入数组 +--- @param k integer 要查找的元素个数 +--- @return table 最大的 k 个元素组成的数组 +local function top_k_heap(nums, k) + -- 初始化小顶堆 + local heap = {} + -- 将数组的前 k 个元素入堆 + for i = 1, k do + heapq.heappush(heap, nums[i]) + end + + -- 从第 k+1 个元素开始,保持堆的长度为 k + for i = k + 1, #nums do + -- 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap[1] then + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + end + end + + return heap +end + +-- Driver Code +local function main() + local nums = { 1, 7, 6, 3, 2 } + local k = 3 + + local res = top_k_heap(nums, k) + print(string.format("最大的 %d 个元素为", k)) + print_util.print_heap(res) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_searching/binary_search.lua b/codes/lua/chapter_searching/binary_search.lua new file mode 100644 index 0000000000..635fa10de5 --- /dev/null +++ b/codes/lua/chapter_searching/binary_search.lua @@ -0,0 +1,67 @@ +-- @script binary_search.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +--- 二分查找(双闭区间) +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @return integer 目标值在数组中的索引,如果不存在则返回-1 +--- @warning 注意 Lua table 索引从 1 开始 +local function binary_search(nums, target) + -- 初始化双闭区间 [0, n-1],即 i, j 分别指向数组首元素、尾元素 + local i, j = 1, #nums + -- 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j do + -- 计算中点索引 m,防止大数越界 + local m = math.floor(i + (j - i) / 2) + if nums[m] < target then + i = m + 1 -- 此情况说明 target 在区间 [m+1, j] 中 + elseif nums[m] > target then + j = m - 1 -- 此情况说明 target 在区间 [i, m-1] 中 + else + return m -- 找到目标元素,返回其索引 + end + end + return -1 -- 未找到目标元素,返回 -1 +end + +--- 二分查找(左闭右开区间) +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @return integer 目标值在数组中的索引,如果不存在则返回-1 +--- @warning 注意 Lua table 索引从 1 开始 +local function binary_search_lcro(nums, target) + -- 初始化左闭右开区间 [1, n+1),即 i, j 分别指向数组首元素、尾元素+1 + local i, j = 1, #nums + 1 + -- 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j do + -- 计算中点索引 m,防止大数越界 + local m = math.floor(i + (j - i) / 2) + if nums[m] < target then + i = m + 1 -- 此情况说明 target 在区间 [m+1, j) 中 + elseif nums[m] > target then + j = m -- 此情况说明 target 在区间 [i, m) 中 + else + return m -- 找到目标元素,返回其索引 + end + end + return -1 -- 未找到目标元素,返回 -1 +end + +-- Driver Code +local function main() + local target = 6 + local nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 } + + -- 二分查找(双闭区间) + local index = binary_search(nums, target) + print("目标元素 6 的索引 = " .. index) + + -- 二分查找(左闭右开区间) + index = binary_search_lcro(nums, target) + print("目标元素 6 的索引 = " .. index) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_searching/binary_search_edge.lua b/codes/lua/chapter_searching/binary_search_edge.lua new file mode 100644 index 0000000000..cd6d482a2f --- /dev/null +++ b/codes/lua/chapter_searching/binary_search_edge.lua @@ -0,0 +1,61 @@ +-- @script binary_search_edge.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/chapter_searching/binary_search_insertion.lua" + +local binary_search_insertion = require("binary_search_insertion") + +--- 二分查找最左一个 target +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @return integer 目标值的最左索引,未找到返回 -1 +--- @warning Lua table 索引从 1 开始 +local function binary_search_left_edge(nums, target) + -- 等价于查找 target 的插入点 + local i = binary_search_insertion(nums, target) + -- 未找到 target ,返回 -1 + if i == #nums + 1 or nums[i] ~= target then + return -1 + end + -- 找到 target ,返回索引 i + return i +end + +--- 二分查找最右一个 target +--- @param nums table 有序数组 +--- @param target integer 目标值 +--- @return integer 目标值的最右索引,未找到返回 -1 +--- @warning Lua table 索引从 1 开始 +local function binary_search_right_edge(nums, target) + -- 转化为查找最左一个 target + 1 + local i = binary_search_insertion(nums, target + 1) + -- j 指向最右一个 target ,i 指向首个大于 target 的元素 + local j = i - 1 + -- 未找到 target ,返回 -1 + if j == 0 or nums[j] ~= target then + return -1 + end + -- 找到 target ,返回索引 j + return j +end + +-- Driver Code +local function main() + -- 包含重复元素的数组 + local nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 } + print(string.format("\n数组 nums = [%s]", table.concat(nums, ", "))) + + -- 二分查找左边界和右边界 + local targets = { 6, 7 } + for _, target in ipairs(targets) do + local index = binary_search_left_edge(nums, target) + print(string.format("最左一个元素 %d 的索引为 %d", target, index)) + index = binary_search_right_edge(nums, target) + print(string.format("最右一个元素 %d 的索引为 %d", target, index)) + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_searching/binary_search_insertion.lua b/codes/lua/chapter_searching/binary_search_insertion.lua new file mode 100644 index 0000000000..e1d392efc7 --- /dev/null +++ b/codes/lua/chapter_searching/binary_search_insertion.lua @@ -0,0 +1,77 @@ +-- @script binary_search_insertion.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 二分查找插入点(无重复元素) +--- @param nums table 有序数组(无重复元素) +--- @param target integer 目标值 +--- @return integer 插入点索引 +--- @warning Lua table 索引从 1 开始 +local function binary_search_insertion_simple(nums, target) + local i, j = 1, #nums -- 初始化双闭区间 [1, n] + while i <= j do + local m = math.floor((i + j) / 2) -- 计算中点索引 m + if nums[m] < target then + i = m + 1 -- target 在区间 [m+1, j] 中 + elseif nums[m] > target then + j = m - 1 -- target 在区间 [i, m-1] 中 + else + return m -- 找到 target ,返回插入点 m + end + end + -- 未找到 target ,返回插入点 i + return i +end + +--- 二分查找插入点(存在重复元素) +--- @param nums table 有序数组(可能包含重复元素) +--- @param target integer 目标值 +--- @return integer 插入点索引 +--- @warning Lua table 索引从 1 开始 +local function binary_search_insertion(nums, target) + local i, j = 1, #nums -- 初始化双闭区间 [1, n] + while i <= j do + local m = math.floor((i + j) / 2) -- 计算中点索引 m + if nums[m] < target then + i = m + 1 -- target 在区间 [m+1, j] 中 + elseif nums[m] > target then + j = m - 1 -- target 在区间 [i, m-1] 中 + else + j = m - 1 -- 首个小于 target 的元素在区间 [i, m-1] 中 + end + end + -- 返回插入点 i + return i +end + +-- Driver Code +local function main() + -- 无重复元素的数组 + local nums1 = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 } + print(string.format("\n数组 nums = [%s]", table.concat(nums1, ", "))) + + -- 二分查找插入点 + for _, target in ipairs({ 6, 9 }) do + local index = binary_search_insertion_simple(nums1, target) + print(string.format("元素 %d 的插入点的索引为 %d", target, index)) + end + + -- 包含重复元素的数组 + local nums2 = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 } + print(string.format("\n数组 nums = [%s]", table.concat(nums2, ", "))) + + -- 二分查找插入点 + for _, target in ipairs({ 2, 6, 20 }) do + local index = binary_search_insertion(nums2, target) + print(string.format("元素 %d 的插入点的索引为 %d", target, index)) + end +end + + +if ... then + -- 被 require 作为模块加载 + return binary_search_insertion +else + -- 执行主函数 + main() +end diff --git a/codes/lua/chapter_searching/hashing_search.lua b/codes/lua/chapter_searching/hashing_search.lua new file mode 100644 index 0000000000..61fa54d192 --- /dev/null +++ b/codes/lua/chapter_searching/hashing_search.lua @@ -0,0 +1,61 @@ +-- @script hashing_search.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local list_to_linked_list = list_node.list_to_linked_list + + +--- 哈希查找(数组) +--- @param hmap table 哈希表,key为目标元素,value为索引 +--- @param target integer 目标值 +--- @return integer 目标元素的索引,不存在则返回-1 +--- @warning Lua table 索引从 1 开始 +local function hashing_search_array(hmap, target) + return hmap[target] or -1 +end + +--- 哈希查找(链表) +--- @param hmap table 哈希表,key为目标元素,value为节点对象 +--- @param target integer 目标值 +--- @return ListNode|nil 目标节点对象,不存在则返回nil +local function hashing_search_linkedlist(hmap, target) + return hmap[target] +end + +-- Driver Code +local function main() + local target = 3 + + -- 哈希查找(数组) + local nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 } + + -- 初始化哈希表 + local map0 = {} + for i, num in ipairs(nums) do + map0[num] = i -- key: 元素,value: 索引 + end + + local index = hashing_search_array(map0, target) + print("目标元素 3 的索引 =", index) + + -- 哈希查找(链表) + local head = list_to_linked_list(nums) + + -- 初始化哈希表 + local map1 = {} + local current = head + while current do + map1[current.val] = current -- key: 节点值,value: 节点 + current = current.next + end + + local node = hashing_search_linkedlist(map1, target) + print("目标节点值 3 的对应节点对象为 " .. tostring(node)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_searching/linear_search.lua b/codes/lua/chapter_searching/linear_search.lua new file mode 100644 index 0000000000..b3ea878b0a --- /dev/null +++ b/codes/lua/chapter_searching/linear_search.lua @@ -0,0 +1,58 @@ +-- @script linear_search.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local list_to_linked_list = list_node.list_to_linked_list + +--- 线性查找(数组) +--- @param nums table 数组 +--- @param target integer 目标值 +--- @return integer 找到返回索引,未找到返回-1 +--- @warning Lua table 索引从 1 开始 +local function linear_search_array(nums, target) + -- 遍历数组 + for i = 1, #nums do + if nums[i] == target then -- 找到目标元素,返回其索引 + return i -- 注意:Lua索引从1开始 + end + end + return -1 -- 未找到目标元素,返回-1 +end + +--- 线性查找(链表) +--- @param head ListNode|nil 链表头节点 +--- @param target integer 目标值 +--- @return ListNode|nil 找到返回节点,未找到返回nil +local function linear_search_linkedlist(head, target) + -- 遍历链表 + local current = head + while current do + if current.val == target then -- 找到目标节点,返回之 + return current + end + current = current.next + end + return nil -- 未找到目标节点,返回nil +end + +-- Driver Code +local function main() + local target = 3 + + -- 在数组中执行线性查找 + local nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 } + local index = linear_search_array(nums, target) + print("目标元素 3 的索引 = " .. index) + + -- 在链表中执行线性查找 + local head = list_to_linked_list(nums) + local node = linear_search_linkedlist(head, target) + print("目标节点值 3 的对应节点对象为: " .. tostring(node)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_searching/two_sum.lua b/codes/lua/chapter_searching/two_sum.lua new file mode 100644 index 0000000000..e38e7a71a1 --- /dev/null +++ b/codes/lua/chapter_searching/two_sum.lua @@ -0,0 +1,59 @@ +-- @script two_sum.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 方法一:暴力枚举 +--- 通过两层循环遍历所有可能的组合 +--- @param nums table 输入的数字数组 +--- @param target integer 目标值 +--- @return table 两个数字的索引组成的数组,如果不存在则返回空表 +--- @warning Lua table 索引从 1 开始 +local function two_sum_brute_force(nums, target) + -- 两层循环,时间复杂度为 O(n^2) + for i = 1, #nums - 1 do + for j = i + 1, #nums do + if nums[i] + nums[j] == target then + return { i, j } -- 注意:Lua索引从 1 开始 + end + end + end + return {} +end + +--- 方法二:辅助哈希表 +--- 使用哈希表存储遍历过的数字及其索引 +--- @param nums table 输入的数字数组 +--- @param target integer 目标值 +--- @return table 两个数字的索引组成的数组,如果不存在则返回空表 +local function two_sum_hash_table(nums, target) + -- 辅助哈希表,空间复杂度为 O(n) + local dic = {} + -- 单层循环,时间复杂度为 O(n) + for i = 1, #nums do + local complement = target - nums[i] + if dic[complement] then + return { dic[complement], i } -- 注意:Lua索引从 1 开始 + end + dic[nums[i]] = i + end + return {} +end + +-- Driver Code +local function main() + -- ======= Test Case ======= + local nums = { 2, 7, 11, 15 } + local target = 13 + + -- ====== Driver Code ====== + -- 方法一 + local res = two_sum_brute_force(nums, target) + print("方法一 res = [" .. table.concat(res, ", ") .. "]") + + -- 方法二 + res = two_sum_hash_table(nums, target) + print("方法二 res = [" .. table.concat(res, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/bubble_sort.lua b/codes/lua/chapter_sorting/bubble_sort.lua new file mode 100644 index 0000000000..ec8a0efcf9 --- /dev/null +++ b/codes/lua/chapter_sorting/bubble_sort.lua @@ -0,0 +1,54 @@ +-- @script bubble_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 冒泡排序 +--- @param nums table 待排序的数组 +local function bubble_sort(nums) + local n = #nums + -- 外循环:未排序区间为 [1, i] + for i = n, 2, -1 do + -- 内循环:将未排序区间 [1, i] 中的最大元素交换至该区间的最右端 + for j = 1, i - 1 do + if nums[j] > nums[j + 1] then + -- 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + end + end + end +end + +--- 冒泡排序(标志优化) +--- @param nums table 待排序的数组 +local function bubble_sort_with_flag(nums) + local n = #nums + -- 外循环:未排序区间为 [1, i] + for i = n, 2, -1 do + local flag = false -- 初始化标志位 + -- 内循环:将未排序区间 [1, i] 中的最大元素交换至该区间的最右端 + for j = 1, i - 1 do + if nums[j] > nums[j + 1] then + -- 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = true -- 记录交换元素 + end + end + if not flag then + break -- 此轮"冒泡"未交换任何元素,直接跳出 + end + end +end + +-- Driver Code +local function main() + local nums = { 4, 1, 3, 1, 5, 2 } + bubble_sort(nums) + print("冒泡排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") + + local nums1 = { 4, 1, 3, 1, 5, 2 } + bubble_sort_with_flag(nums1) + print("冒泡排序完成后 nums = [" .. table.concat(nums1, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/bucket_sort.lua b/codes/lua/chapter_sorting/bucket_sort.lua new file mode 100644 index 0000000000..280865a1bf --- /dev/null +++ b/codes/lua/chapter_sorting/bucket_sort.lua @@ -0,0 +1,49 @@ +-- @script bucket_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 桶排序 +--- @param nums table 待排序的数组(就地修改) +local function bucket_sort(nums) + -- 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + local k = math.floor(#nums / 2) + local buckets = {} + for i = 1, k do + buckets[i] = {} + end + + -- 1. 将数组元素分配到各个桶中 + for _, num in ipairs(nums) do + -- 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [1, k] + local idx = math.floor(num * k) + 1 + -- 确保索引在有效范围内 + idx = math.max(1, math.min(idx, k)) + -- 将 num 添加进桶 + table.insert(buckets[idx], num) + end + + -- 2. 对各个桶执行排序 + for _, bucket in ipairs(buckets) do + table.sort(bucket) + end + + -- 3. 遍历桶合并结果 + local i = 1 + for _, bucket in ipairs(buckets) do + for _, num in ipairs(bucket) do + nums[i] = num + i = i + 1 + end + end +end + +-- Driver Code +local function main() + -- 设输入数据为浮点数,范围为 [0, 1) + local nums = { 0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37 } + bucket_sort(nums) + print("桶排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/counting_sort.lua b/codes/lua/chapter_sorting/counting_sort.lua new file mode 100644 index 0000000000..9e5ffffc7a --- /dev/null +++ b/codes/lua/chapter_sorting/counting_sort.lua @@ -0,0 +1,100 @@ +-- @script counting_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 计数排序的简单实现(无法用于排序对象) +--- @param nums table 待排序数组 +local function counting_sort_naive(nums) + -- 1. 统计数组最大元素 m + local m = 0 + for i = 1, #nums do + if nums[i] > m then + m = nums[i] + end + end + + -- 2. 统计各数字的出现次数 + -- counter[num] 代表 num 的出现次数 + local counter = {} + for i = 0, m do + counter[i] = 0 + end + + for i = 1, #nums do + local num = nums[i] + counter[num] = counter[num] + 1 + end + + -- 3. 遍历 counter ,将各元素填入原数组 nums + local i = 1 + for num = 0, m do + for _ = 1, counter[num] do + nums[i] = num + i = i + 1 + end + end +end + +--- 计数排序(完整实现,可排序对象,并且是稳定排序) +--- @param nums table 待排序数组 +local function counting_sort(nums) + -- 1. 统计数组最大元素 m + local m = 0 + for i = 1, #nums do + if nums[i] > m then + m = nums[i] + end + end + + -- 2. 统计各数字的出现次数 + -- counter[num] 代表 num 的出现次数 + local counter = {} + for i = 0, m do + counter[i] = 0 + end + + for i = 1, #nums do + local num = nums[i] + counter[num] = counter[num] + 1 + end + + -- 3. 求 counter 的前缀和,将"出现次数"转换为"尾索引" + -- 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i = 0, m - 1 do + counter[i + 1] = counter[i + 1] + counter[i] + end + + -- 4. 倒序遍历 nums ,将各元素填入结果数组 res + -- 初始化数组 res 用于记录结果 + local n = #nums + local res = {} + for i = 1, n do + res[i] = 0 + end + + for i = n, 1, -1 do + local num = nums[i] + res[counter[num]] = num -- 将 num 放置到对应索引处 + counter[num] = counter[num] - 1 -- 令前缀和自减 1 ,得到下次放置 num 的索引 + end + + -- 使用结果数组 res 覆盖原数组 nums + for i = 1, n do + nums[i] = res[i] + end +end + +-- Driver Code +local function main() + local nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 } + + counting_sort_naive(nums) + print("计数排序(无法排序对象)完成后 nums = [" .. table.concat(nums, ", ") .. "]") + + local nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 } + counting_sort(nums1) + print("计数排序完成后 nums1 = [" .. table.concat(nums1, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/heap_sort.lua b/codes/lua/chapter_sorting/heap_sort.lua new file mode 100644 index 0000000000..f916ead7b9 --- /dev/null +++ b/codes/lua/chapter_sorting/heap_sort.lua @@ -0,0 +1,72 @@ +-- @script heap_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 从节点 i 开始,从顶至底堆化 +--- @param nums table 待堆化的数组 +--- @param n integer 堆的长度 +--- @param i integer 当前节点索引 +local function sift_down(nums, n, i) + while true do + -- 判断节点 i, l, r 中值最大的节点,记为 ma + local l = 2 * i + 1 + local r = 2 * i + 2 + local ma = i + + if l < n and nums[l + 1] > nums[ma + 1] then + ma = l + end + + if r < n and nums[r + 1] > nums[ma + 1] then + ma = r + end + + -- 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i then + break + end + + -- 交换两节点 + nums[i + 1], nums[ma + 1] = nums[ma + 1], nums[i + 1] + + -- 循环向下堆化 + i = ma + end +end + +--- 堆排序 +--- @param nums table 待排序的数组 +--- @return table 排序后的数组 +local function heap_sort(nums) + if not nums or #nums == 0 then + return nums + end + + local n = #nums + + -- 建堆操作:堆化除叶节点以外的其他所有节点 + for i = math.floor(n / 2) - 1, 0, -1 do + sift_down(nums, n, i) + end + + -- 从堆中提取最大元素,循环 n-1 轮 + for i = n - 1, 1, -1 do + -- 交换根节点与最右叶节点(交换首元素与尾元素) + nums[1], nums[i + 1] = nums[i + 1], nums[1] + + -- 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0) + end + + return nums +end + +-- Driver Code +local function main() + local nums = { 4, 1, 3, 1, 5, 2 } + heap_sort(nums) + print("堆排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/insertion_sort.lua b/codes/lua/chapter_sorting/insertion_sort.lua new file mode 100644 index 0000000000..1ab9bceb7f --- /dev/null +++ b/codes/lua/chapter_sorting/insertion_sort.lua @@ -0,0 +1,29 @@ +-- @script heap_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 插入排序 +--- @param nums table 待排序的数组 +local function insertion_sort(nums) + -- 外循环:已排序区间为 [1, i-1] + for i = 2, #nums do + local base = nums[i] + local j = i - 1 + -- 内循环:将 base 插入到已排序区间 [1, i-1] 中的正确位置 + while j >= 1 and nums[j] > base do + nums[j + 1] = nums[j] -- 将 nums[j] 向右移动一位 + j = j - 1 + end + nums[j + 1] = base -- 将 base 赋值到正确位置 + end +end + +-- Driver Code +local function main() + local nums = { 4, 1, 3, 1, 5, 2 } + insertion_sort(nums) + print("插入排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/merge_sort.lua b/codes/lua/chapter_sorting/merge_sort.lua new file mode 100644 index 0000000000..39583a1d1e --- /dev/null +++ b/codes/lua/chapter_sorting/merge_sort.lua @@ -0,0 +1,78 @@ +-- @script merge_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 合并左子数组和右子数组 +--- @param nums table 待合并的数组 +--- @param left integer 左边界索引 +--- @param mid integer 中间索引 +--- @param right integer 右边界索引 +local function merge(nums, left, mid, right) + -- 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + -- 创建一个临时数组 tmp,用于存放合并后的结果 + local tmp = {} + -- 初始化左子数组和右子数组的起始索引 + local i, j, k = left, mid + 1, 1 + + -- 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while i <= mid and j <= right do + if nums[i] <= nums[j] then + tmp[k] = nums[i] + i = i + 1 + else + tmp[k] = nums[j] + j = j + 1 + end + k = k + 1 + end + + -- 将左子数组的剩余元素复制到临时数组中 + while i <= mid do + tmp[k] = nums[i] + i = i + 1 + k = k + 1 + end + + -- 将右子数组的剩余元素复制到临时数组中 + while j <= right do + tmp[k] = nums[j] + j = j + 1 + k = k + 1 + end + + -- 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for idx = 1, #tmp do + nums[left + idx - 1] = tmp[idx] + end +end + +--- 归并排序 +--- @param nums table 待排序的数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +local function merge_sort(nums, left, right) + -- 终止条件:当子数组长度为 1 时终止递归 + if left >= right then + return + end + + -- 划分阶段:计算中点 + local mid = math.floor((left + right) / 2) + + -- 递归左子数组 + merge_sort(nums, left, mid) + -- 递归右子数组 + merge_sort(nums, mid + 1, right) + -- 合并阶段 + merge(nums, left, mid, right) +end + +-- Driver Code +local function main() + local nums = { 7, 3, 2, 6, 0, 1, 5, 4 } + merge_sort(nums, 1, #nums) + print("归并排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/quick_sort.lua b/codes/lua/chapter_sorting/quick_sort.lua new file mode 100644 index 0000000000..2b7708cde8 --- /dev/null +++ b/codes/lua/chapter_sorting/quick_sort.lua @@ -0,0 +1,205 @@ +-- @script quick_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class QuickSort +--- 快速排序类 +QuickSort = {} +QuickSort.__index = QuickSort + +--- 创建新的快速排序实例 +--- @return QuickSort 实例 +function QuickSort.new() + local obj = {} + setmetatable(obj, QuickSort) + return obj +end + +--- 哨兵划分 +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +--- @return integer 基准数索引 +function QuickSort:partition(nums, left, right) + -- 以 nums[left] 为基准数 + local i, j = left, right + while i < j do + while i < j and nums[j] >= nums[left] do + j = j - 1 -- 从右向左找首个小于基准数的元素 + end + while i < j and nums[i] <= nums[left] do + i = i + 1 -- 从左向右找首个大于基准数的元素 + end + -- 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + -- 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i -- 返回基准数的索引 +end + +--- 快速排序 +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +function QuickSort:quick_sort(nums, left, right) + -- 子数组长度为 1 时终止递归 + if left >= right then + return + end + -- 哨兵划分 + local pivot = self:partition(nums, left, right) + -- 递归左子数组、右子数组 + self:quick_sort(nums, left, pivot - 1) + self:quick_sort(nums, pivot + 1, right) +end + +--- @class QuickSortMedian +--- 快速排序类(中位基准数优化) +QuickSortMedian = {} +QuickSortMedian.__index = QuickSortMedian + +--- 创建新的中位基准数快速排序实例 +-- @return QuickSortMedian 实例 +function QuickSortMedian.new() + local obj = {} + setmetatable(obj, QuickSortMedian) + return obj +end + +--- 选取三个候选元素的中位数 +--- @param nums table 数组 +--- @param left integer 左边界索引 +--- @param mid integer 中间索引 +--- @param right integer 右边界索引 +--- @return integer 中位数索引 +function QuickSortMedian:median_three(nums, left, mid, right) + local l, m, r = nums[left], nums[mid], nums[right] + if (l <= m and m <= r) or (r <= m and m <= l) then + return mid -- m 在 l 和 r 之间 + end + if (m <= l and l <= r) or (r <= l and l <= m) then + return left -- l 在 m 和 r 之间 + end + return right +end + +--- 哨兵划分(三数取中值) +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +--- @return integer 基准数索引 +function QuickSortMedian:partition(nums, left, right) + -- 以 nums[left] 为基准数 + local med = self:median_three(nums, left, math.floor((left + right) / 2), right) + -- 将中位数交换至数组最左端 + nums[left], nums[med] = nums[med], nums[left] + -- 以 nums[left] 为基准数 + local i, j = left, right + while i < j do + while i < j and nums[j] >= nums[left] do + j = j - 1 -- 从右向左找首个小于基准数的元素 + end + while i < j and nums[i] <= nums[left] do + i = i + 1 -- 从左向右找首个大于基准数的元素 + end + -- 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + -- 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i -- 返回基准数的索引 +end + +--- 快速排序 +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +function QuickSortMedian:quick_sort(nums, left, right) + -- 子数组长度为 1 时终止递归 + if left >= right then + return + end + -- 哨兵划分 + local pivot = self:partition(nums, left, right) + -- 递归左子数组、右子数组 + self:quick_sort(nums, left, pivot - 1) + self:quick_sort(nums, pivot + 1, right) +end + +--- @class QuickSortTailCall +--- 快速排序类(递归深度优化) +QuickSortTailCall = {} +QuickSortTailCall.__index = QuickSortTailCall + +--- 创建新的尾递归优化快速排序实例 +--- @return QuickSortTailCall 实例 +function QuickSortTailCall.new() + local obj = {} + setmetatable(obj, QuickSortTailCall) + return obj +end + +--- 哨兵划分 +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +--- @return integer 基准数索引 +function QuickSortTailCall:partition(nums, left, right) + -- 以 nums[left] 为基准数 + local i, j = left, right + while i < j do + while i < j and nums[j] >= nums[left] do + j = j - 1 -- 从右向左找首个小于基准数的元素 + end + while i < j and nums[i] <= nums[left] do + i = i + 1 -- 从左向右找首个大于基准数的元素 + end + -- 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + -- 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i -- 返回基准数的索引 +end + +--- 快速排序(递归深度优化) +--- @param nums table 待排序数组 +--- @param left integer 左边界索引 +--- @param right integer 右边界索引 +function QuickSortTailCall:quick_sort(nums, left, right) + -- 子数组长度为 1 时终止 + while left < right do + -- 哨兵划分操作 + local pivot = self:partition(nums, left, right) + -- 对两个子数组中较短的那个执行快速排序 + if pivot - left < right - pivot then + self:quick_sort(nums, left, pivot - 1) -- 递归排序左子数组 + left = pivot + 1 -- 剩余未排序区间为 [pivot + 1, right] + else + self:quick_sort(nums, pivot + 1, right) -- 递归排序右子数组 + right = pivot - 1 -- 剩余未排序区间为 [left, pivot - 1] + end + end +end + +-- Driver Code +local function main() + -- 快速排序 + local nums = { 2, 4, 1, 0, 3, 5 } + QuickSort.new():quick_sort(nums, 1, #nums) + print("快速排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") + + -- 快速排序(中位基准数优化) + local nums1 = { 2, 4, 1, 0, 3, 5 } + QuickSortMedian.new():quick_sort(nums1, 1, #nums1) + print("快速排序(中位基准数优化)完成后 nums = [" .. table.concat(nums1, ", ") .. "]") + + -- 快速排序(递归深度优化) + local nums2 = { 2, 4, 1, 0, 3, 5 } + QuickSortTailCall.new():quick_sort(nums2, 1, #nums2) + print("快速排序(递归深度优化)完成后 nums = [" .. table.concat(nums2, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/radix_sort.lua b/codes/lua/chapter_sorting/radix_sort.lua new file mode 100644 index 0000000000..2af6b4fe67 --- /dev/null +++ b/codes/lua/chapter_sorting/radix_sort.lua @@ -0,0 +1,96 @@ +-- @script radix_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 获取元素的第 k 位,其中 exp = 10^(k-1) +--- @param num integer 要获取位数的数字 +--- @param exp integer 基数,10^(k-1) +--- @return integer 第 k 位的数字 +local function digit(num, exp) + -- 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return math.floor(num / exp) % 10 +end + +--- 计数排序(根据 nums 第 k 位排序) +--- @param nums table 要排序的数字数组 +--- @param exp integer 基数,10^(k-1) +local function counting_sort_digit(nums, exp) + -- 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + local counter = {} + for i = 0, 9 do + counter[i] = 0 + end + + local n = #nums + + -- 统计 0~9 各数字的出现次数 + for i = 1, n do + local d = digit(nums[i], exp) -- 获取 nums[i] 第 k 位,记为 d + counter[d] = counter[d] + 1 -- 统计数字 d 的出现次数 + end + + -- 求前缀和,将"出现个数"转换为"数组索引" + for i = 1, 9 do + counter[i] = counter[i] + counter[i - 1] + end + + -- 倒序遍历,根据桶内统计结果,将各元素填入 res + local res = {} + for i = n, 1, -1 do + local d = digit(nums[i], exp) + local j = counter[d] -- 获取 d 在数组中的索引 j + res[j] = nums[i] -- 将当前元素填入索引 j + counter[d] = counter[d] - 1 -- 将 d 的数量减 1 + end + + -- 使用结果覆盖原数组 nums + for i = 1, n do + nums[i] = res[i] + end +end + +--- 基数排序 +--- @param nums table 要排序的数字数组 +local function radix_sort(nums) + -- 获取数组的最大元素,用于判断最大位数 + local m = nums[1] + for i = 2, #nums do + if nums[i] > m then + m = nums[i] + end + end + + -- 按照从低位到高位的顺序遍历 + local exp = 1 + while exp <= m do + -- 对数组元素的第 k 位执行计数排序 + -- k = 1 -> exp = 1 + -- k = 2 -> exp = 10 + -- 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp = exp * 10 + end +end + +-- Driver Code +local function main() + -- 基数排序 + local nums = { + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + } + + radix_sort(nums) + print("基数排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_sorting/selection_sort.lua b/codes/lua/chapter_sorting/selection_sort.lua new file mode 100644 index 0000000000..bf4cdfdd2c --- /dev/null +++ b/codes/lua/chapter_sorting/selection_sort.lua @@ -0,0 +1,31 @@ +-- @script selection_sort.lua +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- 选择排序 +--- @param nums table 待排序的数组(原地修改) +local function selection_sort(nums) + local n = #nums + -- 外循环:未排序区间为 [i, n] + for i = 1, n - 1 do + -- 内循环:找到未排序区间内的最小元素 + local k = i + for j = i + 1, n do + if nums[j] < nums[k] then + k = j -- 记录最小元素的索引 + end + end + -- 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] + end +end + +-- Driver Code +local function main() + local nums = { 4, 1, 3, 1, 5, 2 } + selection_sort(nums) + print("选择排序完成后 nums = [" .. table.concat(nums, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/array_deque.lua b/codes/lua/chapter_stack_and_queue/array_deque.lua new file mode 100644 index 0000000000..9c7f4ef978 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/array_deque.lua @@ -0,0 +1,175 @@ +-- @script: array_deque.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class ArrayDeque +--- 基于环形数组实现的双向队列 +--- @field _nums table 用于存储双向队列元素的数组 +--- @field _front number 队首指针,指向队首元素 +--- @field _size number 双向队列长度 +local ArrayDeque = {} +ArrayDeque.__index = ArrayDeque + +--- 构造方法 +--- @param capacity integer 队列容量 +--- @return ArrayDeque +function ArrayDeque.new(capacity) + local obj = {} + setmetatable(obj, ArrayDeque) + + obj._nums = {} + for i = 1, capacity do + obj._nums[i] = 0 + end + obj._front = 1 + obj._size = 0 + + return obj +end + +--- 获取双向队列的容量 +--- @return integer +function ArrayDeque:capacity() + return #self._nums +end + +--- 获取双向队列的长度 +--- @return integer +function ArrayDeque:size() + return self._size +end + +--- 判断双向队列是否为空 +--- @return boolean +function ArrayDeque:is_empty() + return self._size == 0 +end + +--- 计算环形数组索引 +--- @param i integer 原始索引 +--- @return integer +function ArrayDeque:index(i) + -- 通过取余操作实现数组首尾相连 + -- 当 i 越过数组尾部后,回到头部 + -- 当 i 越过数组头部后,回到尾部 + return (i + self:capacity() - 1) % self:capacity() + 1 +end + +--- 队首入队 +--- @param num number 要入队的数字 +function ArrayDeque:push_first(num) + if self._size == self:capacity() then + print("双向队列已满") + return + end + -- 队首指针向左移动一位 + -- 通过取余操作实现 front 越过数组头部后回到尾部 + self._front = self:index(self._front - 1) + -- 将 num 添加至队首 + self._nums[self._front] = num + self._size = self._size + 1 +end + +--- 队尾入队 +--- @param num number 要入队的数字 +function ArrayDeque:push_last(num) + if self._size == self:capacity() then + print("双向队列已满") + return + end + -- 计算队尾指针,指向队尾索引 + 1 + local rear = self:index(self._front + self._size) + -- 将 num 添加至队尾 + self._nums[rear] = num + self._size = self._size + 1 +end + +--- 队首出队 +--- @return number +function ArrayDeque:pop_first() + local num = self:peek_first() + -- 队首指针向后移动一位 + self._front = self:index(self._front + 1) + self._size = self._size - 1 + return num +end + +--- 队尾出队 +--- @return number +function ArrayDeque:pop_last() + local num = self:peek_last() + self._size = self._size - 1 + return num +end + +--- 访问队首元素 +--- @return number +--- @raise 双向队列为空时抛出错误 +function ArrayDeque:peek_first() + if self:is_empty() then + error("双向队列为空") + end + return self._nums[self._front] +end + +--- 访问队尾元素 +--- @return number +--- @raise 双向队列为空时抛出错误 +function ArrayDeque:peek_last() + if self:is_empty() then + error("双向队列为空") + end + -- 计算尾元素索引 + local last = self:index(self._front + self._size - 1) + return self._nums[last] +end + +--- 返回数组用于打印 +--- @return table +function ArrayDeque:to_array() + local res = {} + -- 仅转换有效长度范围内的列表元素 + for i = 0, self._size - 1 do + res[i + 1] = self._nums[self:index(self._front + i)] + end + return res +end + +-- Driver Code +local function main() + -- 初始化双向队列 + local deque = ArrayDeque.new(10) + deque:push_last(3) + deque:push_last(2) + deque:push_last(5) + print("双向队列 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + + -- 访问元素 + local peek_first = deque:peek_first() + print("队首元素 peek_first = " .. tostring(peek_first)) + local peek_last = deque:peek_last() + print("队尾元素 peek_last = " .. tostring(peek_last)) + + -- 元素入队 + deque:push_last(4) + print("元素 4 队尾入队后 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + deque:push_first(1) + print("元素 1 队首入队后 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + + -- 元素出队 + local pop_last = deque:pop_last() + print(string.format("队尾出队元素 = %d ,队尾出队后 deque = [%s]", pop_last, table.concat(deque:to_array(), ", "))) + local pop_first = deque:pop_first() + print(string.format("队首出队元素 = %d ,队首出队后 deque = [%s]", pop_first, table.concat(deque:to_array(), ", "))) + + -- 获取双向队列的长度 + local size = deque:size() + print("双向队列长度 size = " .. tostring(size)) + + -- 判断双向队列是否为空 + local is_empty = deque:is_empty() + print("双向队列是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/array_queue.lua b/codes/lua/chapter_stack_and_queue/array_queue.lua new file mode 100644 index 0000000000..c7ad4892a5 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/array_queue.lua @@ -0,0 +1,144 @@ +-- @script: array_queue.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class ArrayQueue +--- 基于环形数组实现的队列 +--- @field _nums table 用于存储队列元素的数组 +--- @field _front integer 队首指针,指向队首元素 +--- @field _size integer 队列长度 +local ArrayQueue = {} +ArrayQueue.__index = ArrayQueue + +--- 构造方法 +--- @param size integer 队列容量 +--- @return ArrayQueue 实例 +function ArrayQueue.new(size) + local obj = {} + setmetatable(obj, ArrayQueue) + + -- 用于存储队列元素的数组 + obj._nums = {} + for i = 1, size do + obj._nums[i] = 0 + end + + -- 队首指针,指向队首元素(Lua索引从1开始) + obj._front = 1 + -- 队列长度 + obj._size = 0 + + return obj +end + +--- 获取队列的容量 +--- @return integer +function ArrayQueue:capacity() + return #self._nums +end + +--- 获取队列的长度 +--- @return integer +function ArrayQueue:size() + return self._size +end + +--- 判断队列是否为空 +--- @return boolean +function ArrayQueue:is_empty() + return self._size == 0 +end + +--- 入队 +--- @param num integer 要入队的元素 +--- @raise 如果队列已满则抛出错误 +function ArrayQueue:push(num) + if self._size == self:capacity() then + error("队列已满") + end + + -- 计算队尾指针,指向队尾索引 + 1 + -- 通过取余操作实现rear越过数组尾部后回到头部 + local rear = (self._front + self._size - 1) % self:capacity() + 1 + -- 将num添加至队尾 + self._nums[rear] = num + self._size = self._size + 1 +end + +--- 出队 +--- @return integer +--- @raise 队列为空时抛出错误 +function ArrayQueue:pop() + local num = self:peek() + -- 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self._front = (self._front % self:capacity()) + 1 + self._size = self._size - 1 + return num +end + +--- 访问队首元素 +--- @return integer +--- @raise 队列为空时抛出错误 +function ArrayQueue:peek() + if self:is_empty() then + error("队列为空") + end + return self._nums[self._front] +end + +--- 返回列表用于打印 +--- @return table +function ArrayQueue:to_list() + local res = {} + local j = self._front + + for i = 1, self:size() do + -- 计算实际索引,处理环形数组的环绕 + local index = ((j - 1) % self:capacity()) + 1 + res[i] = self._nums[index] + j = j + 1 + end + + return res +end + +-- Driver Code +local function main() + -- 初始化队列 + local queue = ArrayQueue.new(10) + + -- 元素入队 + queue:push(1) + queue:push(3) + queue:push(2) + queue:push(5) + queue:push(4) + print("队列 queue = [" .. table.concat(queue:to_list(), ", ") .. "]") + + -- 访问队首元素 + local peek = queue:peek() + print("队首元素 peek = " .. tostring(peek)) + + -- 元素出队 + local pop = queue:pop() + print("出队元素 pop = " .. tostring(pop)) + print("出队后 queue = [" .. table.concat(queue:to_list(), ", ") .. "]") + + -- 获取队列的长度 + local size = queue:size() + print("队列长度 size = " .. tostring(size)) + + -- 判断队列是否为空 + local is_empty = queue:is_empty() + print("队列是否为空 = " .. tostring(is_empty)) + + -- 测试环形数组 + for i = 0, 9 do + queue:push(i) + queue:pop() + print(string.format("第 %d 轮入队 + 出队后 queue = [%s]", i, table.concat(queue:to_list(), ", "))) + end +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/array_stack.lua b/codes/lua/chapter_stack_and_queue/array_stack.lua new file mode 100644 index 0000000000..a0519970a8 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/array_stack.lua @@ -0,0 +1,101 @@ +-- @script: array_stack.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class ArrayStack +--- 基于数组实现的栈 +--- @field _stack table 使用表作为栈的存储结构 +local ArrayStack = {} +ArrayStack.__index = ArrayStack + +--- 构造方法 +--- @return ArrayStack +function ArrayStack.new() + local obj = {} + setmetatable(obj, ArrayStack) + obj._stack = {} -- 使用表作为栈的存储结构 + return obj +end + +--- 获取栈的长度 +--- @return integer +function ArrayStack:size() + return #self._stack +end + +--- 判断栈是否为空 +--- @return boolean +function ArrayStack:is_empty() + return self:size() == 0 +end + +--- 入栈操作 +--- @param item integer 要入栈的元素 +function ArrayStack:push(item) + table.insert(self._stack, item) +end + +--- 出栈操作 +--- @return integer +--- @raise 栈为空时抛出错误 +function ArrayStack:pop() + if self:is_empty() then + error("栈为空") + end + return table.remove(self._stack) +end + +--- 访问栈顶元素 +--- @return integer +--- @raise 栈为空时抛出错误 +function ArrayStack:peek() + if self:is_empty() then + error("栈为空") + end + return self._stack[#self._stack] +end + +--- 返回列表用于打印 +--- @return table +function ArrayStack:to_list() + -- 返回栈的浅拷贝以避免外部修改 + local result = {} + for i, v in ipairs(self._stack) do + result[i] = v + end + return result +end + +-- Driver Code +local function main() + -- 初始化栈 + local stack = ArrayStack.new() + + -- 元素入栈 + stack:push(1) + stack:push(3) + stack:push(2) + stack:push(5) + stack:push(4) + print("栈 stack = [" .. table.concat(stack:to_list(), ", ") .. "]") + + -- 访问栈顶元素 + local peek = stack:peek() + print("栈顶元素 peek = " .. peek) + + -- 元素出栈 + local pop = stack:pop() + print("出栈元素 pop = " .. pop) + print("出栈后 stack = [" .. table.concat(stack:to_list(), ", ") .. "]") + + -- 获取栈的长度 + local size = stack:size() + print("栈的长度 size = " .. size) + + -- 判断是否为空 + local is_empty = stack:is_empty() + print("栈是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/deque.lua b/codes/lua/chapter_stack_and_queue/deque.lua new file mode 100644 index 0000000000..e02862a749 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/deque.lua @@ -0,0 +1,44 @@ +-- @script dequeue.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- Driver Code +local function main() + -- 使用Lua表模拟双向队列数据结构 + -- 初始化双向队列 + local deq = {} + + -- 元素入队 + table.insert(deq, 2) -- 添加至队尾 + table.insert(deq, 5) + table.insert(deq, 4) + table.insert(deq, 1, 3) -- 添加至队首 + table.insert(deq, 1, 1) + print("双向队列 deque = [" .. table.concat(deq, ", ") .. "]") + + -- 访问元素 + local front = deq[1] -- 队首元素 + print("队首元素 front = " .. tostring(front)) + local rear = deq[#deq] -- 队尾元素 + print("队尾元素 rear = " .. tostring(rear)) + + -- 元素出队 + local pop_front = table.remove(deq, 1) -- 队首元素出队 + print("队首出队元素 pop_front = " .. tostring(pop_front)) + print("队首出队后 deque = [" .. table.concat(deq, ", ") .. "]") + local pop_rear = table.remove(deq) -- 队尾元素出队 + print("队尾出队元素 pop_rear = " .. tostring(pop_rear)) + print("队尾出队后 deque = [" .. table.concat(deq, ", ") .. "]") + + -- 获取双向队列的长度 + local size = #deq + print("双向队列长度 size = " .. tostring(size)) + + -- 判断双向队列是否为空 + local is_empty = #deq == 0 + print("双向队列是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/linkedlist_deque.lua b/codes/lua/chapter_stack_and_queue/linkedlist_deque.lua new file mode 100644 index 0000000000..ea64da12d4 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/linkedlist_deque.lua @@ -0,0 +1,208 @@ +-- @script linked_deque.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class DeListNode +--- 双向链表节点 +--- @field val number 节点值 +--- @field next DeListNode|nil 后继节点引用 +--- @field prev DeListNode|nil 前驱节点引用 +local DeListNode = {} +DeListNode.__index = DeListNode + +--- 构造方法 +--- @param val number 节点值 +--- @return DeListNode +function DeListNode.new(val) + local obj = {} + setmetatable(obj, DeListNode) + obj.val = val + obj.next = nil -- 后继节点引用 + obj.prev = nil -- 前驱节点引用 + return obj +end + +--- @class LinkedListDeque +--- 基于双向链表实现的双向队列 +--- @field _front DeListNode|nil 队首节点 +--- @field _rear DeListNode|nil 队尾节点 +--- @field _size integer 双向队列长度 +local LinkedListDeque = {} +LinkedListDeque.__index = LinkedListDeque + +--- 构造方法 +--- @return LinkedListDeque +function LinkedListDeque.new() + local obj = {} + setmetatable(obj, LinkedListDeque) + obj._front = nil -- 头节点 front + obj._rear = nil -- 尾节点 rear + obj._size = 0 -- 双向队列的长度 + return obj +end + +--- 获取双向队列的长度 +--- @return number +function LinkedListDeque:size() + return self._size +end + +--- 判断双向队列是否为空 +--- @return boolean +function LinkedListDeque:is_empty() + return self._size == 0 +end + +--- 入队操作 +--- @param num number 入队值 +--- @param is_front boolean 是否在队首入队 +function LinkedListDeque:push(num, is_front) + local node = DeListNode.new(num) + + -- 若链表为空,则令 front 和 rear 都指向 node + if self:is_empty() then + self._front = node + self._rear = node + -- 队首入队操作 + elseif is_front then + -- 将 node 添加至链表头部 + self._front.prev = node + node.next = self._front + self._front = node -- 更新头节点 + -- 队尾入队操作 + else + -- 将 node 添加至链表尾部 + self._rear.next = node + node.prev = self._rear + self._rear = node -- 更新尾节点 + end + self._size = self._size + 1 -- 更新队列长度 +end + +--- 队首入队 +--- @param num number 入队值 +function LinkedListDeque:push_first(num) + self:push(num, true) +end + +--- 队尾入队 +--- @param num number 入队值 +function LinkedListDeque:push_last(num) + self:push(num, false) +end + +--- 出队操作 +--- @param is_front boolean 是否在队首出队 +--- @return number +--- @raise 双向队列为空时抛出错误 +function LinkedListDeque:pop(is_front) + if self:is_empty() then + error("双向队列为空") + end + + local val + -- 队首出队操作 + if is_front then + val = self._front.val -- 暂存头节点值 + -- 删除头节点 + local fnext = self._front.next + if fnext ~= nil then + fnext.prev = nil + self._front.next = nil + end + self._front = fnext -- 更新头节点 + -- 队尾出队操作 + else + val = self._rear.val -- 暂存尾节点值 + -- 删除尾节点 + local rprev = self._rear.prev + if rprev ~= nil then + rprev.next = nil + self._rear.prev = nil + end + self._rear = rprev -- 更新尾节点 + end + self._size = self._size - 1 -- 更新队列长度 + return val +end + +--- 队首出队 +--- @return number +function LinkedListDeque:pop_first() + return self:pop(true) +end + +--- 队尾出队 +--- @return number +function LinkedListDeque:pop_last() + return self:pop(false) +end + +--- 访问队首元素 +--- @return number +function LinkedListDeque:peek_first() + if self:is_empty() then + error("双向队列为空") + end + return self._front.val +end + +--- 访问队尾元素 +--- @return number +function LinkedListDeque:peek_last() + if self:is_empty() then + error("双向队列为空") + end + return self._rear.val +end + +--- 返回数组用于打印 +--- @return table +function LinkedListDeque:to_array() + local node = self._front + local res = {} + for i = 1, self:size() do + res[i] = node.val + node = node.next + end + return res +end + +-- Driver Code +local function main() + -- 初始化双向队列 + local deque = LinkedListDeque.new() + deque:push_last(3) + deque:push_last(2) + deque:push_last(5) + print("双向队列 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + + -- 访问元素 + local peek_first = deque:peek_first() + print("队首元素 peek_first = " .. tostring(peek_first)) + local peek_last = deque:peek_last() + print("队尾元素 peek_last = " .. tostring(peek_last)) + + -- 元素入队 + deque:push_last(4) + print("元素 4 队尾入队后 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + deque:push_first(1) + print("元素 1 队首入队后 deque = [" .. table.concat(deque:to_array(), ", ") .. "]") + + -- 元素出队 + local pop_last = deque:pop_last() + print(string.format("队尾出队元素 = %d ,队尾出队后 deque = [%s]", pop_last, table.concat(deque:to_array(), ", "))) + local pop_first = deque:pop_first() + print(string.format("队首出队元素 = %d ,队首出队后 deque = [%s]", pop_first, table.concat(deque:to_array(), ", "))) + + -- 获取双向队列的长度 + local size = deque:size() + print("双向队列长度 size = " .. tostring(size)) + + -- 判断双向队列是否为空 + local is_empty = deque:is_empty() + print("双向队列是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/linkedlist_queue.lua b/codes/lua/chapter_stack_and_queue/linkedlist_queue.lua new file mode 100644 index 0000000000..a3f6b069f6 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/linkedlist_queue.lua @@ -0,0 +1,121 @@ +-- @script linked_queue.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local ListNode = list_node.ListNode + +--- @class LinkedListQueue +--- 基于链表实现的队列 +--- @field _front ListNode 队首节点 +--- @field _rear ListNode 队尾节点 +--- @field _size integer 队列长度 +local LinkedListQueue = {} +LinkedListQueue.__index = LinkedListQueue + +--- 构造方法 +--- @return LinkedListQueue 新的队列实例 +function LinkedListQueue.new() + local instance = setmetatable({}, LinkedListQueue) + instance._front = nil -- 头节点 front + instance._rear = nil -- 尾节点 rear + instance._size = 0 -- 队列长度 + return instance +end + +--- 获取队列的长度 +--- @return integer +function LinkedListQueue:size() + return self._size +end + +--- 判断队列是否为空 +--- @return boolean +function LinkedListQueue:is_empty() + return self._size == 0 +end + +--- 入队 +--- @param num number +function LinkedListQueue:push(num) + -- 在尾节点后添加 num + local node = ListNode.new(num) + -- 如果队列为空,则令头、尾节点都指向该节点 + if self._front == nil then + self._front = node + self._rear = node + -- 如果队列不为空,则将该节点添加到尾节点后 + else + self._rear.next = node + self._rear = node + end + self._size = self._size + 1 +end + +--- 出队 +--- @return number +function LinkedListQueue:pop() + local num = self:peek() + -- 删除头节点 + self._front = self._front.next + self._size = self._size - 1 + return num +end + +--- 访问队首元素 +--- @return number +function LinkedListQueue:peek() + if self:is_empty() then + error("队列为空") + end + return self._front.val +end + +--- 转化为列表用于打印 +--- @return table +function LinkedListQueue:to_list() + local queue = {} + local temp = self._front + while temp do + table.insert(queue, temp.val) + temp = temp.next + end + return queue +end + +-- Driver Code +local function main() + -- 初始化队列 + local queue = LinkedListQueue.new() + + -- 元素入队 + queue:push(1) + queue:push(3) + queue:push(2) + queue:push(5) + queue:push(4) + print("队列 queue = [" .. table.concat(queue:to_list(), ", ") .. "]") + + -- 访问队首元素 + local peek = queue:peek() + print("队首元素 front = " .. tostring(peek)) + + -- 元素出队 + local pop_front = queue:pop() + print("出队元素 pop = " .. tostring(pop_front)) + print("出队后 queue = [" .. table.concat(queue:to_list(), ", ") .. "]") + + -- 获取队列的长度 + local size = queue:size() + print("队列长度 size = " .. tostring(size)) + + -- 判断队列是否为空 + local is_empty = queue:is_empty() + print("队列是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/linkedlist_stack.lua b/codes/lua/chapter_stack_and_queue/linkedlist_stack.lua new file mode 100644 index 0000000000..cd026c6a39 --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/linkedlist_stack.lua @@ -0,0 +1,113 @@ +-- @script linked_stack.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local list_node = require("list_node") +local ListNode = list_node.ListNode + +--- @class LinkedListStack +--- 基于链表实现的栈 +--- @field _peek ListNode 栈顶节点 +--- @field _size integer 栈的长度 +local LinkedListStack = {} +LinkedListStack.__index = LinkedListStack + +--- 构造方法 +--- @return LinkedListStack 新的栈实例 +function LinkedListStack.new() + local obj = {} + setmetatable(obj, LinkedListStack) + obj._peek = nil -- 栈顶节点 + obj._size = 0 -- 栈的长度 + return obj +end + +--- 获取栈的长度 +--- @return integer +function LinkedListStack:size() + return self._size +end + +--- 判断栈是否为空 +--- @return boolean +function LinkedListStack:is_empty() + return self._size == 0 +end + +--- 入栈 +--- @param val number +function LinkedListStack:push(val) + local node = ListNode.new(val) + node.next = self._peek + self._peek = node + self._size = self._size + 1 +end + +--- 出栈 +--- @return number +function LinkedListStack:pop() + local num = self:peek() + self._peek = self._peek.next + self._size = self._size - 1 + return num +end + +--- 访问栈顶元素 +--- @return number 栈顶元素 +--- @raise 栈为空时抛出错误 +function LinkedListStack:peek() + if self:is_empty() then + error("栈为空") + end + return self._peek.val +end + +---转化为列表用于打印 +---@return table +function LinkedListStack:to_list() + local arr = {} + local node = self._peek + while node do + table.insert(arr, 1, node.val) -- 在头部插入,相当于 reverse + node = node.next + end + return arr +end + +-- Driver Code +local function main() + -- 初始化栈 + local stack = LinkedListStack.new() + + -- 元素入栈 + stack:push(1) + stack:push(3) + stack:push(2) + stack:push(5) + stack:push(4) + print("栈 stack = [" .. table.concat(stack:to_list(), ", ") .. "]") + + -- 访问栈顶元素 + local peek = stack:peek() + print("栈顶元素 peek = " .. peek) + + -- 元素出栈 + local pop = stack:pop() + print("出栈元素 pop = " .. pop) + print("出栈后 stack = [" .. table.concat(stack:to_list(), ", ") .. "]") + + -- 获取栈的长度 + local size = stack:size() + print("栈的长度 size = " .. size) + + -- 判断是否为空 + local is_empty = stack:is_empty() + print("栈是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/queue.lua b/codes/lua/chapter_stack_and_queue/queue.lua new file mode 100644 index 0000000000..5ffedd627b --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/queue.lua @@ -0,0 +1,39 @@ +-- @script queue.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- Driver Code +local function main() + -- 使用Lua表模拟队列数据结构 + -- 初始化队列 + local que = {} + + -- 元素入队 + table.insert(que, 1) + table.insert(que, 3) + table.insert(que, 2) + table.insert(que, 5) + table.insert(que, 4) + print("队列 que = [" .. table.concat(que, ", ") .. "]") + + -- 访问队首元素 + local front = que[1] + print("队首元素 front = " .. tostring(front)) + + -- 元素出队 + local pop = #que == 0 and nil or table.remove(que, 1) + print("出队元素 pop = " .. tostring(pop)) + print("出队后 que = [" .. table.concat(que, ", ") .. "]") + + -- 获取队列长度 + local size = #que + print("队列长度 size = " .. tostring(size)) + + -- 判断队列是否为空 + local is_empty = #que == 0 + print("队列是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_stack_and_queue/stack.lua b/codes/lua/chapter_stack_and_queue/stack.lua new file mode 100644 index 0000000000..7776337c1c --- /dev/null +++ b/codes/lua/chapter_stack_and_queue/stack.lua @@ -0,0 +1,39 @@ +-- @script stack.lua +-- @date 2025-11-11 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- Driver Code +local function main() + -- 初始化栈 + -- Lua 没有内置的栈类,可以把 table 当作栈来使用 + local stack = {} + + -- 元素入栈 + table.insert(stack, 1) + table.insert(stack, 3) + table.insert(stack, 2) + table.insert(stack, 5) + table.insert(stack, 4) + print("栈 stack = [" .. table.concat(stack, ", ") .. "]") + + -- 访问栈顶元素 + local peek = stack[#stack] + print("栈顶元素 peek = " .. peek) + + -- 元素出栈 + local pop = table.remove(stack) + print("出栈元素 pop = " .. pop) + print("出栈后 stack = [" .. table.concat(stack, ", ") .. "]") + + -- 获取栈的长度 + local size = #stack + print("栈的长度 size = " .. size) + + -- 判断是否为空 + local is_empty = #stack == 0 + print("栈是否为空 = " .. tostring(is_empty)) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_tree/array_binary_tree.lua b/codes/lua/chapter_tree/array_binary_tree.lua new file mode 100644 index 0000000000..470683f0c9 --- /dev/null +++ b/codes/lua/chapter_tree/array_binary_tree.lua @@ -0,0 +1,166 @@ +-- @script array_binary_tree.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- @class ArrayBinaryTree +--- 数组表示下的二叉树类 +--- @field _tree number[] 二叉树的数组表示 +local ArrayBinaryTree = {} + +--- 构造方法 +--- @param arr number[] 二叉树数组表示 +--- @return ArrayBinaryTree +function ArrayBinaryTree:new(arr) + local obj = {} + setmetatable(obj, self) + self.__index = self + obj._tree = {} + for i, v in ipairs(arr) do + obj._tree[i] = v + end + return obj +end + +---列表容量 +---@return integer +function ArrayBinaryTree:size() + return #self._tree +end + +--- 获取索引为 i 节点的值 +--- @param i integer 节点索引 +--- @return number 节点值,若索引越界则返回 math.huge +function ArrayBinaryTree:val(i) + -- 若索引越界,则返回 math.huge ,代表空位 + if i < 1 or i > self:size() then + return math.huge + end + return self._tree[i] +end + +--- 获取索引为 i 节点的左子节点的索引 +--- @param i integer 节点索引 +--- @return integer +function ArrayBinaryTree:left(i) + return 2 * i +end + +--- 获取索引为 i 节点的右子节点的索引 +--- @param i integer 节点索引 +--- @return integer +function ArrayBinaryTree:right(i) + return 2 * i + 1 +end + +--- 获取索引为 i 节点的父节点的索引 +--- @param i integer 节点索引 +--- @return integer +function ArrayBinaryTree:parent(i) + return math.floor(i / 2) +end + +--- 层序遍历 +--- @return number[] +function ArrayBinaryTree:level_order() + local res = {} + -- 直接遍历数组 + for i = 1, self:size() do + if self:val(i) ~= math.huge then + table.insert(res, self:val(i)) + end + end + return res +end + +--- 深度优先遍历 +--- @param i integer 当前节点索引 +--- @param order string 遍历顺序:"pre"|"in"|"post" +--- @param res table 结果数组 +function ArrayBinaryTree:_dfs(i, order, res) + if self:val(i) == math.huge then + return + end + -- 前序遍历 + if order == "pre" then + table.insert(res, self:val(i)) + end + self:_dfs(self:left(i), order, res) + -- 中序遍历 + if order == "in" then + table.insert(res, self:val(i)) + end + self:_dfs(self:right(i), order, res) + -- 后序遍历 + if order == "post" then + table.insert(res, self:val(i)) + end +end + +--- 前序遍历 +--- @return table +function ArrayBinaryTree:pre_order() + local res = {} + self:_dfs(1, "pre", res) + return res +end + +--- 中序遍历 +--- @return table +function ArrayBinaryTree:in_order() + local res = {} + self:_dfs(1, "in", res) + return res +end + +--- 后序遍历 +--- @return table +function ArrayBinaryTree:post_order() + local res = {} + self:_dfs(1, "post", res) + return res +end + +-- Driver Code +local function main() + -- 初始化二叉树 + -- 注意:Lua中数组索引从1开始,用 math.huge 表示空位 + local arr = { 1, 2, 3, 4, math.huge, 6, 7, 8, 9, math.huge, math.huge, 12, math.huge, math.huge, 15 } + local root = list_to_tree(arr) + + print("\n初始化二叉树\n") + print("二叉树的数组表示:") + print(string.format("[%s]", table.concat(arr, ", "))) + print("二叉树的链表表示:") + print_util.print_tree(root, nil, false) + + -- 数组表示下的二叉树类 + local abt = ArrayBinaryTree:new(arr) + + -- 访问节点 + local i = 2 -- Lua索引从1开始,对应原Python代码中的索引1 + local l, r, p = abt:left(i), abt:right(i), abt:parent(i) + print(string.format("\n当前节点的索引为 %d ,值为 %s", i, tostring(abt:val(i)))) + print(string.format("其左子节点的索引为 %d ,值为 %s", l, tostring(abt:val(l)))) + print(string.format("其右子节点的索引为 %d ,值为 %s", r, tostring(abt:val(r)))) + print(string.format("其父节点的索引为 %d ,值为 %s", p, tostring(abt:val(p)))) + + -- 遍历树 + local res = abt:level_order() + print("\n层序遍历为:[" .. table.concat(res, ", ") .. "]") + res = abt:pre_order() + print("前序遍历为:[" .. table.concat(res, ", ") .. "]") + res = abt:in_order() + print("中序遍历为:[" .. table.concat(res, ", ") .. "]") + res = abt:post_order() + print("后序遍历为:[" .. table.concat(res, ", ") .. "]") +end + +-- 运行主函数 +main() diff --git a/codes/lua/chapter_tree/avl_tree.lua b/codes/lua/chapter_tree/avl_tree.lua new file mode 100644 index 0000000000..09bddcd28d --- /dev/null +++ b/codes/lua/chapter_tree/avl_tree.lua @@ -0,0 +1,268 @@ +-- @script avl_tree.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local TreeNode = tree_node.TreeNode + +--- @class AVLTree +--- AVL 树 +--- @field _root TreeNode|nil AVL 树的根节点 +local AVLTree = {} + +---AVLTree 构造函数 +---@return AVLTree +function AVLTree:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + obj._root = nil + return obj +end + +--- 获取二叉树根节点 +--- @return TreeNode|nil +function AVLTree:get_root() + return self._root +end + +--- 获取节点高度 +--- @param node TreeNode|nil +--- @return integer +function AVLTree:height(node) + -- 空节点高度为 -1 ,叶节点高度为 0 + if node ~= nil then + return node.height + end + return -1 +end + +--- 更新节点高度 +--- @param node TreeNode|nil +function AVLTree:update_height(node) + if node == nil then + return + end + -- 节点高度等于最高子树高度 + 1 + node.height = math.max(self:height(node.left), self:height(node.right)) + 1 +end + +--- 获取平衡因子 +--- @param node TreeNode|nil +--- @return integer +function AVLTree:balance_factor(node) + -- 空节点平衡因子为 0 + if node == nil then + return 0 + end + -- 节点平衡因子 = 左子树高度 - 右子树高度 + return self:height(node.left) - self:height(node.right) +end + +--- 右旋操作 +--- @param node TreeNode +--- @return TreeNode +function AVLTree:right_rotate(node) + local child = node.left + local grand_child = child.right + -- 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grand_child + -- 更新节点高度 + self:update_height(node) + self:update_height(child) + -- 返回旋转后子树的根节点 + return child +end + +--- 左旋操作 +--- @param node TreeNode +--- @return TreeNode +function AVLTree:left_rotate(node) + local child = node.right + local grand_child = child.left + -- 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grand_child + -- 更新节点高度 + self:update_height(node) + self:update_height(child) + -- 返回旋转后子树的根节点 + return child +end + +--- 执行旋转操作,使该子树重新恢复平衡 +--- @param node TreeNode +--- @return TreeNode +function AVLTree:rotate(node) + -- 获取节点 node 的平衡因子 + local balance_factor = self:balance_factor(node) + -- 左偏树 + if balance_factor > 1 then + if self:balance_factor(node.left) >= 0 then + -- 右旋 + return self:right_rotate(node) + else + -- 先左旋后右旋 + node.left = self:left_rotate(node.left) + return self:right_rotate(node) + end + -- 右偏树 + elseif balance_factor < -1 then + if self:balance_factor(node.right) <= 0 then + -- 左旋 + return self:left_rotate(node) + else + -- 先右旋后左旋 + node.right = self:right_rotate(node.right) + return self:left_rotate(node) + end + end + -- 平衡树,无须旋转,直接返回 + return node +end + +--- 插入节点 +--- @param val integer +function AVLTree:insert(val) + self._root = self:insert_helper(self._root, val) +end + +--- 递归插入节点(辅助方法) +--- @param node TreeNode|nil +--- @param val integer +--- @return TreeNode +function AVLTree:insert_helper(node, val) + if node == nil then + return TreeNode.new(val) + end + -- 1. 查找插入位置并插入节点 + if val < node.val then + node.left = self:insert_helper(node.left, val) + elseif val > node.val then + node.right = self:insert_helper(node.right, val) + else + -- 重复节点不插入,直接返回 + return node + end + -- 更新节点高度 + self:update_height(node) + -- 2. 执行旋转操作,使该子树重新恢复平衡 + return self:rotate(node) +end + +--- 删除节点 +--- @param val integer +function AVLTree:remove(val) + self._root = self:remove_helper(self._root, val) +end + +--- 递归删除节点(辅助方法) +--- @param node TreeNode|nil +--- @param val integer +--- @return TreeNode|nil +function AVLTree:remove_helper(node, val) + if node == nil then + return nil + end + -- 1. 查找节点并删除 + if val < node.val then + node.left = self:remove_helper(node.left, val) + elseif val > node.val then + node.right = self:remove_helper(node.right, val) + else + if node.left == nil or node.right == nil then + local child = node.left or node.right + -- 子节点数量 = 0 ,直接删除 node 并返回 + if child == nil then + return nil + -- 子节点数量 = 1 ,直接删除 node + else + node = child + end + else + -- 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + local temp = node.right + while temp.left ~= nil do + temp = temp.left + end + node.right = self:remove_helper(node.right, temp.val) + node.val = temp.val + end + end + -- 更新节点高度 + self:update_height(node) + -- 2. 执行旋转操作,使该子树重新恢复平衡 + return self:rotate(node) +end + +--- 查找节点 +--- @param val integer +--- @return TreeNode|nil +function AVLTree:search(val) + local cur = self._root + -- 循环查找,越过叶节点后跳出 + while cur ~= nil do + -- 目标节点在 cur 的右子树中 + if cur.val < val then + cur = cur.right + -- 目标节点在 cur 的左子树中 + elseif cur.val > val then + cur = cur.left + -- 找到目标节点,跳出循环 + else + break + end + end + -- 返回目标节点 + return cur +end + +-- Driver Code +local function main() + local function test_insert(tree, val) + tree:insert(val) + print(string.format("\n插入节点 %d 后,AVL 树为", val)) + print_util.print_tree(tree:get_root(), nil, false) + end + + local function test_remove(tree, val) + tree:remove(val) + print(string.format("\n删除节点 %d 后,AVL 树为", val)) + print_util.print_tree(tree:get_root(), nil, false) + end + + -- 初始化空 AVL 树 + local avl_tree = AVLTree:new() + + -- 插入节点 + -- 请关注插入节点后,AVL 树是如何保持平衡的 + local test_values = { 1, 2, 3, 4, 5, 8, 7, 9, 10, 6 } + for _, val in ipairs(test_values) do + test_insert(avl_tree, val) + end + + -- 插入重复节点 + test_insert(avl_tree, 7) + + -- 删除节点 + -- 请关注删除节点后,AVL 树是如何保持平衡的 + test_remove(avl_tree, 8) -- 删除度为 0 的节点 + test_remove(avl_tree, 5) -- 删除度为 1 的节点 + test_remove(avl_tree, 4) -- 删除度为 2 的节点 + + local result_node = avl_tree:search(7) + if result_node then + print(string.format("\n查找到的节点对象为 %s,节点值 = %d", tostring(result_node), result_node.val)) + else + print("\n未找到节点 7") + end +end + +-- 运行主函数 +main() diff --git a/codes/lua/chapter_tree/binary_search_tree.lua b/codes/lua/chapter_tree/binary_search_tree.lua new file mode 100644 index 0000000000..8d81b889d3 --- /dev/null +++ b/codes/lua/chapter_tree/binary_search_tree.lua @@ -0,0 +1,192 @@ +-- @script binary_search_tree.lua +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local TreeNode = tree_node.TreeNode + +--- @class BinarySearchTree +--- 二叉搜索树类 +--- @field _root TreeNode|nil 二叉搜索树的根节点 +local BinarySearchTree = {} +BinarySearchTree.__index = BinarySearchTree + +--- 构造方法 +--- 初始化空树 +--- @return BinarySearchTree +function BinarySearchTree.new() + local obj = {} + setmetatable(obj, BinarySearchTree) + obj._root = nil + return obj +end + +--- 获取二叉树根节点 +--- @return TreeNode|nil +function BinarySearchTree:get_root() + return self._root +end + +--- 查找节点 +--- @param num number 要查找的值 +--- @return TreeNode|nil +function BinarySearchTree:search(num) + local cur = self._root + -- 循环查找,越过叶节点后跳出 + while cur ~= nil do + -- 目标节点在 cur 的右子树中 + if cur.val < num then + cur = cur.right + -- 目标节点在 cur 的左子树中 + elseif cur.val > num then + cur = cur.left + -- 找到目标节点,跳出循环 + else + break + end + end + return cur +end + +--- 插入节点 +--- @param num number 要插入的值 +function BinarySearchTree:insert(num) + -- 若树为空,则初始化根节点 + if self._root == nil then + self._root = TreeNode.new(num) + return + end + + -- 循环查找,越过叶节点后跳出 + local cur = self._root + local pre = nil + while cur ~= nil do + -- 找到重复节点,直接返回 + if cur.val == num then + return + end + pre = cur + -- 插入位置在 cur 的右子树中 + if cur.val < num then + cur = cur.right + -- 插入位置在 cur 的左子树中 + else + cur = cur.left + end + end + + -- 插入节点 + local node = TreeNode.new(num) + if pre.val < num then + pre.right = node + else + pre.left = node + end +end + +---删除节点 +---@param num number 要删除的值 +function BinarySearchTree:remove(num) + -- 若树为空,直接提前返回 + if self._root == nil then + return + end + + -- 循环查找,越过叶节点后跳出 + local cur = self._root + local pre = nil + while cur ~= nil do + -- 找到待删除节点,跳出循环 + if cur.val == num then + break + end + pre = cur + -- 待删除节点在 cur 的右子树中 + if cur.val < num then + cur = cur.right + -- 待删除节点在 cur 的左子树中 + else + cur = cur.left + end + end + + -- 若无待删除节点,则直接返回 + if cur == nil then + return + end + + -- 子节点数量 = 0 or 1 + if cur.left == nil or cur.right == nil then + -- 当子节点数量 = 0 / 1 时,child = null / 该子节点 + local child = cur.left or cur.right + -- 删除节点 cur + if cur ~= self._root then + if pre.left == cur then + pre.left = child + else + pre.right = child + end + else + -- 若删除节点为根节点,则重新指定根节点 + self._root = child + end + -- 子节点数量 = 2 + else + -- 获取中序遍历中 cur 的下一个节点 + local tmp = cur.right + while tmp.left ~= nil do + tmp = tmp.left + end + -- 递归删除节点 tmp + self:remove(tmp.val) + -- 用 tmp 覆盖 cur + cur.val = tmp.val + end +end + +-- Driver Code +local function main() + -- 初始化二叉搜索树 + local bst = BinarySearchTree.new() + local nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 } + -- 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + for _, num in ipairs(nums) do + bst:insert(num) + end + print("\n初始化的二叉树为\n") + print_util.print_tree(bst:get_root(), nil, false) + + -- 查找节点 + local node = bst:search(7) + if node then + print("\n查找到的节点对象为: " .. tostring(node) .. ",节点值= " .. node.val) + else + print("\n未找到节点 7") + end + + -- 插入节点 + bst:insert(16) + print("\n插入节点 16 后,二叉树为\n") + print_util.print_tree(bst:get_root(), nil, false) + + -- 删除节点 + bst:remove(1) + print("\n删除节点 1 后,二叉树为\n") + print_util.print_tree(bst:get_root(), nil, false) + + bst:remove(2) + print("\n删除节点 2 后,二叉树为\n") + print_util.print_tree(bst:get_root(), nil, false) + + bst:remove(4) + print("\n删除节点 4 后,二叉树为\n") + print_util.print_tree(bst:get_root(), nil, false) +end + +-- 运行主函数 +main() diff --git a/codes/lua/chapter_tree/binary_tree.lua b/codes/lua/chapter_tree/binary_tree.lua new file mode 100644 index 0000000000..b4ec87cba5 --- /dev/null +++ b/codes/lua/chapter_tree/binary_tree.lua @@ -0,0 +1,47 @@ +-- @script binary_tree.lua +-- @date 2025-11-13 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local TreeNode = tree_node.TreeNode + +-- Driver Code +local function main() + -- 初始化二叉树 + -- 初始化节点 + local n1 = TreeNode.new(1) + local n2 = TreeNode.new(2) + local n3 = TreeNode.new(3) + local n4 = TreeNode.new(4) + local n5 = TreeNode.new(5) + + -- 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + + print("\n初始化二叉树\n") + print_util.print_tree(n1, nil, false) + + -- 插入与删除节点 + local P = TreeNode.new(0) + -- 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + print("\n插入节点 P 后\n") + print_util.print_tree(n1, nil, false) + + -- 删除节点 + n1.left = n2 + print("\n删除节点 P 后\n") + print_util.print_tree(n1, nil, false) +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_tree/binary_tree_bfs.lua b/codes/lua/chapter_tree/binary_tree_bfs.lua new file mode 100644 index 0000000000..8f209a28e2 --- /dev/null +++ b/codes/lua/chapter_tree/binary_tree_bfs.lua @@ -0,0 +1,60 @@ +-- @script binary_tree_bfs.lua +-- @date 2025-11-13 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +--- 层序遍历二叉树 +--- @param root TreeNode|nil 二叉树的根节点 +--- @return table +local function level_order(root) + if not root then + return {} + end + + -- 初始化队列,加入根节点 + local queue = { root } + -- 初始化一个列表,用于保存遍历序列 + local res = {} + + while #queue > 0 do + -- 队列出队 + local node = table.remove(queue, 1) + -- 保存节点值 + table.insert(res, node.val) + + -- 左子节点入队 + if node.left then + table.insert(queue, node.left) + end + + -- 右子节点入队 + if node.right then + table.insert(queue, node.right) + end + end + + return res +end + +-- Driver Code +local function main() + -- 初始化二叉树 + -- 这里借助了一个从数组直接生成二叉树的函数 + local root = list_to_tree({ 1, 2, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树\n") + print_util.print_tree(root, nil, false) + + -- 层序遍历 + local res = level_order(root) + print("\n层序遍历的节点打印序列 = [" .. table.concat(res, ", ") .. "]\n") +end + +-- 执行主函数 +main() diff --git a/codes/lua/chapter_tree/binary_tree_dfs.lua b/codes/lua/chapter_tree/binary_tree_dfs.lua new file mode 100644 index 0000000000..e231c63247 --- /dev/null +++ b/codes/lua/chapter_tree/binary_tree_dfs.lua @@ -0,0 +1,77 @@ +-- @script binary_tree_dfs.lua +-- @date 2025-11-13 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + + +-- 添加模块搜索路径 +package.path = package.path .. ";codes/lua/modules/?.lua" + +local print_util = require("print_util") +local tree_node = require("tree_node") +local list_to_tree = tree_node.list_to_tree + +-- 全局结果表,用于存储遍历结果 +local res = {} + +--- 前序遍历 +--- @param root TreeNode | nil 树的根节点 +local function pre_order(root) + if root == nil then + return + end + -- 访问优先级:根节点 -> 左子树 -> 右子树 + table.insert(res, root.val) + pre_order(root.left) + pre_order(root.right) +end + +--- 中序遍历 +--- @param root TreeNode | nil 树的根节点 +local function in_order(root) + if root == nil then + return + end + -- 访问优先级:左子树 -> 根节点 -> 右子树 + in_order(root.left) + table.insert(res, root.val) + in_order(root.right) +end + +--- 后序遍历 +--- @param root TreeNode | nil 树的根节点 +local function post_order(root) + if root == nil then + return + end + -- 访问优先级:左子树 -> 右子树 -> 根节点 + post_order(root.left) + post_order(root.right) + table.insert(res, root.val) +end + +-- Driver Code +local function main() + -- 初始化二叉树 + -- 这里借助了一个从数组直接生成二叉树的函数 + local root = list_to_tree({ 1, 2, 3, 4, 5, 6, 7 }) + print("\n初始化二叉树\n") + print_util.print_tree(root, nil, false) + + -- 前序遍历 + res = {} + pre_order(root) + print("\n前序遍历的节点打印序列 = [" .. table.concat(res, ", ") .. "]") + + -- 中序遍历 + res = {} + in_order(root) + print("\n中序遍历的节点打印序列 = [" .. table.concat(res, ", ") .. "]") + + -- 后序遍历 + res = {} + post_order(root) + print("\n后序遍历的节点打印序列 = [" .. table.concat(res, ", ") .. "]") +end + +-- 执行主函数 +main() diff --git a/codes/lua/modules/heapq.lua b/codes/lua/modules/heapq.lua new file mode 100644 index 0000000000..b118d6273a --- /dev/null +++ b/codes/lua/modules/heapq.lua @@ -0,0 +1,89 @@ +-- @module heapq +-- @date 2025-11-14 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +local heapq = {} + +--- 上浮调整 +--- @param heap table 堆数组 +--- @param index integer 要调整的节点索引 +local function _sift_up(heap, index) + while index > 1 do + local parent = math.floor(index / 2) + if heap[parent] <= heap[index] then + break + end + heap[parent], heap[index] = heap[index], heap[parent] + index = parent + end +end + +--- 下沉调整 +--- @param heap table 堆数组 +--- @param index integer 要调整的节点索引 +local function _sift_down(heap, index) + local n = #heap + while true do + local left = 2 * index + local right = 2 * index + 1 + local smallest = index + + if left <= n and heap[left] < heap[smallest] then + smallest = left + end + + if right <= n and heap[right] < heap[smallest] then + smallest = right + end + + if smallest == index then + break + end + + heap[index], heap[smallest] = heap[smallest], heap[index] + index = smallest + end +end + +--- 建堆操作 +--- @param heap 要构建为堆的数组 +function heapq.heapify(heap) + -- 从最后一个非叶子节点开始调整 + for i = math.floor(#heap / 2), 1, -1 do + _sift_down(heap, i) + end +end + +--- 元素入堆 +--- @function push +--- @param heap table 堆数组 +--- @param val integer 要插入的值 +function heapq.heappush(heap, val) + -- 元素入堆 + table.insert(heap, val) + -- 上浮调整 + _sift_up(heap, #heap) +end + +--- 堆顶元素出堆 +--- @param heap table 堆数组 +function heapq.heappop(heap) + flag = flag or 1 + if #heap == 0 then + print("\n堆为空,无法出堆") + return + end + + -- 交换堆顶和堆底元素 + heap[1], heap[#heap] = heap[#heap], heap[1] + -- 堆顶元素出堆 + local val = flag * table.remove(heap) + -- 下沉调整 + if #heap > 0 then + _sift_down(heap, 1) + end + + return val +end + +return heapq diff --git a/codes/lua/modules/list_node.lua b/codes/lua/modules/list_node.lua new file mode 100644 index 0000000000..cd73336d2e --- /dev/null +++ b/codes/lua/modules/list_node.lua @@ -0,0 +1,52 @@ +-- @classmod list_node +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class ListNode +--- 链表节点类 +--- @field val number 节点值 +--- @field next ListNode|nil 后继节点引用 +local ListNode = {} +ListNode.__index = ListNode + +--- 构造函数 +--- @param val number 节点值 +--- @return ListNode 新节点实例 +function ListNode.new(val) + local obj = setmetatable({}, ListNode) + obj.val = val -- 节点值 + obj.next = nil -- 后继节点引用 + return obj +end + +--- 将列表反序列化为链表 +--- @param arr table 整型数组 +--- @return ListNode|nil 链表头节点 +local function list_to_linked_list(arr) + local dum = ListNode.new(0) + local head = dum + for _, a in ipairs(arr) do + local node = ListNode.new(a) + head.next = node + head = head.next + end + return dum.next +end + +--- 将链表序列化为列表 +--- @param head ListNode 链表头节点 +--- @return table 整型数组 +local function linked_list_to_list(head) + local arr = {} + while head do + table.insert(arr, head.val) + head = head.next + end + return arr +end + +return { + ListNode = ListNode, + list_to_linked_list = list_to_linked_list, + linked_list_to_list = linked_list_to_list, +} diff --git a/codes/lua/modules/pair.lua b/codes/lua/modules/pair.lua new file mode 100644 index 0000000000..7207dbd5a3 --- /dev/null +++ b/codes/lua/modules/pair.lua @@ -0,0 +1,24 @@ +-- @classmod pair +-- @date 2025-11-12 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class Pair +--- 键值对 +--- @field key integer 键 +--- @field val string 值 +local Pair = {} + +--- 构造函数 +--- @param key integer 键 +--- @param val string 值 +--- @return Pair +function Pair:new(key, val) + local obj = {} + setmetatable(obj, self) + self.__index = self + obj.key = key + obj.val = val + return obj +end + +return Pair \ No newline at end of file diff --git a/codes/lua/modules/print_util.lua b/codes/lua/modules/print_util.lua new file mode 100644 index 0000000000..5ed2d12a14 --- /dev/null +++ b/codes/lua/modules/print_util.lua @@ -0,0 +1,114 @@ +-- @module print_util +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +local print_util = {} + +-- 导入依赖 +package.path = package.path .. ";codes/lua/modules/?.lua" +local tree_node = require("tree_node") +local list_node = require("list_node") + +--- 打印矩阵 +--- @param mat table 二维矩阵表 +function print_util.print_matrix(mat) + local s = {} + for i, arr in ipairs(mat) do + s[i] = " [" .. table.concat(arr, ", ") .. "]" + end + print("[\n" .. table.concat(s, ",\n") .. "\n]") +end + +--- 打印链表 +--- @param head table 链表头节点 +function print_util.print_linked_list(head) + local arr = list_node.linked_list_to_list(head) + local str_arr = {} + for i, v in ipairs(arr) do + str_arr[i] = tostring(v) + end + print(table.concat(str_arr, " -> ")) +end + +--- @class Trunk +--- 树干类,用于打印二叉树 +--- @field prev Trunk|nil 前一个树干 +--- @field str string 树干字符串 +local Trunk = {} +Trunk.__index = Trunk + +--- 构造函数 +--- @param prev table|nil 前一个树干 +--- @param str string 树干字符串 +--- @return Trunk 新树干实例 +function Trunk.new(prev, str) + local obj = { + prev = prev, + str = str + } + setmetatable(obj, Trunk) + return obj +end + +--- 显示树干 +--- @param p table 树干实例 +local function show_trunks(p) + if not p then + return + end + show_trunks(p.prev) + io.write(p.str) +end + +--- 打印二叉树 +--- This tree printer is borrowed from TECHIE DELIGHT +--- @see https://www.techiedelight.com/c-program-print-binary-tree/ +--- @param root table|nil 二叉树根节点 +--- @param prev table|nil 前一个树干 +--- @param is_right boolean 是否为右子树 +function print_util.print_tree(root, prev, is_right) + if not root then + return + end + + local prev_str = " " + local trunk = Trunk.new(prev, prev_str) + print_util.print_tree(root.right, trunk, true) + + if not prev then + trunk.str = "———" + elseif is_right then + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunks(trunk) + print(" " .. tostring(root.val)) + if prev then + prev.str = prev_str + end + trunk.str = " |" + print_util.print_tree(root.left, trunk, false) +end + +--- 打印字典 +--- @param hmap table 字典表 +function print_util.print_dict(hmap) + for key, value in pairs(hmap) do + print(string.format("%s -> %s", tostring(key), tostring(value))) + end +end + +--- 打印堆 +--- @param heap table 堆数组 +function print_util.print_heap(heap) + print("堆的数组表示:[" .. table.concat(heap, ", ") .. "]") + print("堆的树状表示:") + local root = tree_node.list_to_tree(heap) + print_util.print_tree(root, nil, false) +end + +return print_util diff --git a/codes/lua/modules/tree_node.lua b/codes/lua/modules/tree_node.lua new file mode 100644 index 0000000000..4638bd75cd --- /dev/null +++ b/codes/lua/modules/tree_node.lua @@ -0,0 +1,98 @@ +-- @classmod tree_node +-- @date 2025-11-10 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class TreeNode +--- 二叉树节点类 +--- @field val number 节点值 +--- @field height number 节点高度 +--- @field left TreeNode|nil 左子节点引用 +--- @field right TreeNode|nil 右子节点引用 +local TreeNode = {} +TreeNode.__index = TreeNode + +--- 构造函数 +--- @param val number 节点值 +--- @return TreeNode 新节点实例 +function TreeNode.new(val) + local obj = {} + setmetatable(obj, TreeNode) + obj.val = val or 0 -- 节点值 + obj.height = 0 -- 节点高度 + obj.left = nil -- 左子节点引用 + obj.right = nil -- 右子节点引用 + return obj +end + +-- 序列化编码规则请参考: +-- https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +-- 二叉树的数组表示: +-- [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +-- 二叉树的链表表示: +-- /——— 15 +-- /——— 7 +-- /——— 3 +-- | \——— 6 +-- | \——— 12 +-- ——— 1 +-- \——— 2 +-- | /——— 9 +-- \——— 4 +-- \——— 8 + +--- 将列表反序列化为二叉树:递归 +--- @param arr table 输入数组 +--- @param i integer 当前索引(从1开始) +--- @return TreeNode|nil 二叉树根节点 +local function list_to_tree_dfs(arr, i) + -- 如果索引超出数组长度,或者对应的元素为nil|math.huge,则返回nil + if i < 1 or i > #arr or arr[i] == nil or arr[i] == math.huge then + return nil + end + -- 构建当前节点 + local root = TreeNode.new(arr[i]) + -- 递归构建左右子树 + root.left = list_to_tree_dfs(arr, 2 * i) + root.right = list_to_tree_dfs(arr, 2 * i + 1) + return root +end + +--- 将列表反序列化为二叉树 +--- @param arr table 输入数组 +--- @return TreeNode|nil 二叉树根节点 +local function list_to_tree(arr) + return list_to_tree_dfs(arr, 1) +end + +--- 将二叉树序列化为列表:递归 +--- @param root TreeNode 二叉树根节点 +--- @param i integer 当前索引 +--- @param res table 结果数组 +local function tree_to_list_dfs(root, i, res) + if root == nil then + return + end + if i > #res then + for j = #res + 1, i do + res[j] = nil + end + end + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i, res) + tree_to_list_dfs(root.right, 2 * i + 1, res) +end + +--- 将二叉树序列化为列表 +--- @param root TreeNode 二叉树根节点 +--- @return table 序列化后的数组 +local function tree_to_list(root) + local res = {} + tree_to_list_dfs(root, 1, res) + return res +end + +return { + TreeNode = TreeNode, + list_to_tree = list_to_tree, + tree_to_list = tree_to_list, +} diff --git a/codes/lua/modules/vertex.lua b/codes/lua/modules/vertex.lua new file mode 100644 index 0000000000..2e2aeb663b --- /dev/null +++ b/codes/lua/modules/vertex.lua @@ -0,0 +1,46 @@ +-- @classmod vertex +-- @date 2025-11-15 +-- @author fisheryv (yue.fisher2025@gdhfi.com) + +--- @class Vertex +--- 顶点类 +--- @field val integer 顶点值 +local Vertex = {} +Vertex.__index = Vertex + +--- 构造函数 +--- @param val integer 顶点值 +--- @return Vertex 新顶点实例 +function Vertex.new(val) + local obj = setmetatable({}, Vertex) + obj.val = val + return obj +end + +--- 输入值列表,返回顶点列表 +--- @param vals table 值列表 +--- @return table 顶点列表 +local function vals_to_vets(vals) + local vets = {} + for _, val in ipairs(vals) do + table.insert(vets, Vertex.new(val)) + end + return vets +end + +--- 输入顶点列表,返回值列表 +--- @param vets table 顶点列表 +--- @return table 值列表 +local function vets_to_vals(vets) + local vals = {} + for _, vet in ipairs(vets) do + table.insert(vals, vet.val) + end + return vals +end + +return { + Vertex = Vertex, + vals_to_vets = vals_to_vets, + vets_to_vals = vets_to_vals +} diff --git a/docs/chapter_appendix/installation.md b/docs/chapter_appendix/installation.md index b0722afcda..aa97f53ea5 100644 --- a/docs/chapter_appendix/installation.md +++ b/docs/chapter_appendix/installation.md @@ -66,3 +66,8 @@ VS Code 拥有强大的扩展包生态系统,支持大多数编程语言的运 1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install) 。 2. 在 VS Code 的插件市场中搜索 `rust` ,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 + +### Lua 环境 + +1. 下载并安装 [Lua](https://www.lua.org/download.html) 。 +2. 在 VS Code 的插件市场中搜索 `lua` ,安装 [Lua](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) 。 \ No newline at end of file diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 25b1a78a3f..7f096237e3 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -126,6 +126,14 @@ nums = [1, 3, 2, 5, 4] ``` +=== "Lua" + + ```lua title="array.lua" + --- 初始化数组 + local arr = {0, 0, 0, 0, 0} + local nums = {1, 3, 2, 5, 4} + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 910e220333..6a1de4c2a2 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -185,6 +185,27 @@ end ``` +=== "Lua" + + ```lua title="" + --- @class ListNode + --- 链表节点类 + --- @field val interger 节点值 + --- @field next ListNode|nil 指向后继节点引用 + local ListNode = {} + ListNode.__index = ListNode + + --- 构造函数 + --- @param val interger 节点值 + --- @return ListNode 新节点实例 + function ListNode.new(val) + local obj = setmetatable({}, ListNode) + obj.val = val -- 节点值 + obj.next = nil -- 指向后继节点引用 + return obj + end + ``` + ## 链表常用操作 ### 初始化链表 @@ -413,6 +434,24 @@ n3.next = n4 ``` +=== "Lua" + + ```lua title="linked_list.lua" + -- 初始化链表 1 -> 3 -> 2 -> 5 -> 4 + -- 初始化各个节点 + local n0 = ListNode.new(1) + local n1 = ListNode.new(3) + local n2 = ListNode.new(2) + local n3 = ListNode.new(5) + local n4 = ListNode.new(4) + + -- 构建节点之间的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -680,6 +719,29 @@ end ``` +=== "Lua" + + ```lua title="" + --- @class ListNode + --- 双向链表节点类 + --- @field val interger 节点值 + --- @field next ListNode|nil 指向后继节点的引用 + --- @field prev ListNode|nil 指向前驱节点的引用 + local ListNode = {} + ListNode.__index = ListNode + + --- 创建新的双向链表节点 + --- @param val interger 节点值 + --- @return ListNode 新的双向链表节点实例 + function ListNode.new(val) + local obj = setmetatable({}, ListNode) + obj.val = val -- 节点值 + obj.next = nil -- 指向后继节点的引用 + obj.prev = nil -- 指向前驱节点的引用 + return obj + end + ``` + ![常见链表种类](linked_list.assets/linkedlist_common_types.png) ## 链表典型应用 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index ded6505eb0..7f59250e7e 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -147,6 +147,14 @@ nums = [1, 3, 2, 5, 4] ``` +=== "Lua" + + ```lua title="list.lua" + -- 初始化列表 + local nums1 = {} -- 无初始值 + local nums = {1, 3, 2, 5, 4} -- 有初始值 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -278,6 +286,17 @@ nums[1] = 0 # 将索引 1 处的元素更新为 0 ``` +=== "Lua" + + ```lua title="list.lua" + -- 访问元素 + -- 注意:Lua 索引从 1 开始 + local num = nums[2] -- 访问索引 2 处的元素(对应其他语言的索引 1) + + --- 更新元素 + nums[2] = 0 -- 将索引 2 处的元素更新为 0 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -532,6 +551,27 @@ nums.delete_at(3) # 删除索引 3 处的元素 ``` +=== "Lua" + + ```lua title="list.lua" + -- 清空列表 + nums = {} + + -- 在尾部添加元素 + table.insert(nums, 1) + table.insert(nums, 3) + table.insert(nums, 2) + table.insert(nums, 5) + table.insert(nums, 4) + + -- 在中间插入元素 + -- 注意:Lua 索引从 1 开始 + table.insert(nums, 4, 6) -- 在索引 4 处插入数字 6 + + -- 删除元素 + table.remove(nums, 4) -- 删除索引 4 处的元素 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -733,6 +773,21 @@ end ``` +=== "Lua" + + ```lua title="list.lua" + -- 通过索引遍历列表 + local count = 0 + for i = 1, #nums do + count = count + nums[i] + end + + -- 直接遍历列表元素 + for _, num in ipairs(nums) do + count = count + num + end + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -844,6 +899,17 @@ nums += nums1 ``` +=== "Lua" + + ```lua title="list.lua" + -- 拼接两个列表 + local nums1 = {6, 8, 7, 10, 9} + -- 将列表 nums1 拼接到 nums 之后 + for _, v in ipairs(nums1) do + table.insert(nums, v) + end + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -942,6 +1008,13 @@ nums = nums.sort { |a, b| a <=> b } # 排序后,列表元素从小到大排列 ``` +=== "Lua" + + ```lua title="list.lua" + -- 排序列表 + table.sort(nums) -- 排序后,列表元素从小到大排列 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index 3955ded731..fdf3a12ebd 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -429,6 +429,34 @@ end ``` +=== "Lua" + + ```lua title="" + --- 回溯算法框架 + local function backtrack(state, choices, res) + -- 检查是否为解 + if is_solution(state) then + -- 记录解 + record_solution(state, res) + -- 不再继续搜索 + return + end + + -- 遍历所有选择 + for _, choice in ipairs(choices) do + -- 剪枝:检查选择是否合法 + if is_valid(state, choice) then + -- 尝试:做出选择,更新状态 + make_choice(state, choice) + -- 进行下一轮选择 + backtrack(state, { choice.left, choice.right }, res) + -- 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end + end + ``` + 接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表: ```src diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 13b48eb280..767900194c 100755 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -361,6 +361,46 @@ end ``` +=== "Lua" + + ```lua title="" + --- @class Node + --- 节点类 + --- @field val number 节点值 + --- @field next Node|nil 指向下一节点的引用 + local Node = {} + Node.__index = Node + + --- 构造函数 + --- @param x number 节点值 + --- @return Node + function Node.new(x) + local obj = {} + setmetatable(obj, Node) + obj.val = x -- 节点值 + obj.next = nil -- 指向下一节点的引用 + return obj + end + + --- 示例函数 + --- @return number + local function exampleFunction() + -- 执行某些操作... + return 0 + end + + --- 算法函数 + --- @param n any 输入数据 + --- @return number 输出数据 + local function algorithm(n) + local A = 0 -- 暂存数据(常量,一般用大写字母表示) + local b = 0 -- 暂存数据(变量) + local node = Node.new(0) -- 暂存数据(对象) + local c = exampleFunction() -- 栈帧空间(调用函数) + return A + b + c -- 输出数据 + end + ``` + ## 推算方法 空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从“操作数量”转为“使用空间大小”。 @@ -523,6 +563,20 @@ end ``` +=== "Lua" + + ```lua title="" + local function algorithm(n) + local a = 0 -- O(1) + -- Lua 中没有专门的数组类型,而是使用 table 实现数组/列表的功能。 + -- Lua 的 table 是动态的,无法指定大小,也没有 Python 的列表乘法,需要一个一个填入数据 + local b = {} -- O(1) + if n > 10 then + local nums = {} -- O(10) + end + end + ``` + **在递归函数中,需要注意统计栈帧空间**。观察以下代码: === "Python" @@ -795,6 +849,33 @@ end ``` +=== "Lua" + + ```lua title="" + --- 执行某些操作 + --- @return integer + local function func() + return 0 + end + + --- 循环的空间复杂度为 O(1) + --- @param n integer + local function loop(n) + for i = 1, n do + func() + end + end + + --- 递归的空间复杂度为 O(n) + --- @param n integer + local function recur(n) + if n == 1 then + return + end + return recur(n - 1) + end + ``` + 函数 `loop()` 和 `recur()` 的时间复杂度都为 $O(n)$ ,但空间复杂度不同。 - 函数 `loop()` 在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 7576382798..cd0dc71ef1 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -201,6 +201,22 @@ end ``` +=== "Lua" + + ```lua title="" + --- 在某运行平台下 + function algorithm(n) + local a = 2 -- 1 ns + a = a + 1 -- 1 ns + a = a * 2 -- 10 ns + + -- 循环 n 次 + for _ = 1, n do -- 1 ns + print(0) -- 5 ns + end + end + ``` + 根据以上方法,可以得到算法的运行时间为 $(6n + 12)$ ns : $$ @@ -484,6 +500,29 @@ $$ end ``` +=== "Lua" + + ```lua title="" + ---算法 A 的时间复杂度:常数阶 + local function algorithm_A(n) + print(0) + end + + ---算法 B 的时间复杂度:线性阶 + local function algorithm_B(n) + for _ = 1, n do + print(0) + end + end + + ---算法 C 的时间复杂度:常数阶 + local function algorithm_C(n) + for _ = 1, 1000000 do + print(0) + end + end + ``` + 下图展示了以上三个算法函数的时间复杂度。 - 算法 `A` 只有 $1$ 个打印操作,算法运行时间不随着 $n$ 增大而增长。我们称此算法的时间复杂度为“常数阶”。 @@ -683,6 +722,21 @@ $$ end ``` +=== "Lua" + + ```lua title="" + local function algorithm(n) + local a = 1 -- +1 + a = a + 1 -- +1 + a = a * 2 -- +1 + + -- 循环 n 次 + for i = 1, n do -- +1 + print(0) -- +1 + end + end + ``` + 设算法的操作数量是一个关于输入数据大小 $n$ 的函数,记为 $T(n)$ ,则以上函数的操作数量为: $$ @@ -960,6 +1014,27 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 end ``` +=== "Lua" + + ```lua title="" + local function algorithm(n) + local a = 1 -- +0(技巧 1) + a = a + n -- +0(技巧 1) + + -- +n(技巧 2) + for i = 1, 5 * n + 1 do + print(0) + end + + -- +n*n(技巧 3) + for i = 1, 2 * n do + for j = 1, n + 1 do + print(0) + end + end + end + ``` + 以下公式展示了使用上述技巧前后的统计结果,两者推算出的时间复杂度都为 $O(n^2)$ 。 $$ diff --git a/docs/chapter_data_structure/basic_data_types.md b/docs/chapter_data_structure/basic_data_types.md index 33ec9f77dd..829576beeb 100644 --- a/docs/chapter_data_structure/basic_data_types.md +++ b/docs/chapter_data_structure/basic_data_types.md @@ -170,6 +170,19 @@ data = [0, 0.0, 'a', false, ListNode(0)] ``` +=== "Lua" + + ```lua title="" + -- Lua 中没有严格的类型系统,数据类型通过上下文推断 + -- Lua 中也没有专门的数组类型,而是使用 table 实现数组/列表的功能。 + local numbers = {0, 0, 0, 0, 0} + local decimals = {0.0, 0.0, 0.0, 0.0, 0.0} + local characters = {'0', '0', '0', '0', '0'} + local bools = {false, false, false, false, false} + -- Lua 的 table 可以自由存储各种基本数据类型和对象引用 + local data = [0, 0.0, 'a', false, ListNode(0)] + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_hashing/hash_algorithm.md b/docs/chapter_hashing/hash_algorithm.md index 0ecf1f1411..fca6e0e2e6 100644 --- a/docs/chapter_hashing/hash_algorithm.md +++ b/docs/chapter_hashing/hash_algorithm.md @@ -399,6 +399,12 @@ $$ # 节点对象 # 的哈希值为 4302940560806366381 ``` +=== "Lua" + + ```lua title="built_in_hash.lua" + -- Lua 未提供内置 hash code 函数 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index b4c0e9664a..dd3a7d2a6c 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -313,6 +313,44 @@ hmap.delete(10583) ``` +=== "Lua" + + ```lua title="hash_map.lua" + -- 根据 key 删除 table 中的元素 + local function remove_by_key(tbl, key) + -- Lua table 不支持按 key 删除元素,因此需要重构整张 table + local new_table = {} + for _key, _value in pairs(tbl) do + if _key ~= key then + new_table[_key] = _value + end + end + return new_table + end + + -- 初始化哈希表 + local hmap = {} + + -- 添加操作 + -- 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + print("\n添加完成后,哈希表为\nKey -> Value") + print_util.print_dict(hmap) + + -- 查询操作 + -- 向哈希表中输入键 key ,得到值 value + local name = hmap[15937] + print("\n输入学号 15937 ,查询到姓名 " .. name) + + -- 删除操作 + -- 在哈希表中删除键值对 (key, value) + hmap = remove_by_key(hmap, 10583) + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -534,6 +572,26 @@ hmap.values.each { |val| puts val } ``` +=== "Lua" + + ```lua title="hash_map.lua" + -- 遍历哈希表 + -- 遍历键值对 key->value + for key, value in pairs(hmap) do + print(key .. " -> " .. value) + end + + -- 单独遍历键 key + for key in pairs(hmap) do + print(key) + end + + -- 单独遍历值 value + for _, value in pairs(hmap) do + print(value) + end + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index add5733b8c..2e07696252 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -408,6 +408,54 @@ # Ruby 未提供内置 Heap 类 ``` +=== "Lua" + + ```lua title="heap.lua" + -- 添加模块搜索路径 + package.path = package.path .. ";codes/lua/modules/?.lua" + + -- Lua 没有内置堆模块,因此手动实现了一个 + local heapq = require("heapq") + + -- 初始化小顶堆 + local min_heap, flag = {}, 1 + + -- 初始化大顶堆 + local max_heap, flag = {}, -1 + + -- 这里手动实现的 heapq 模块与 Python 保持一致,默认实现小顶堆 + -- 考虑将"元素取负"后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + -- 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + -- 元素入堆 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + -- 获取堆顶元素 + local peek = flag * max_heap[1] + + -- 堆顶元素出堆 + local val + val = flag * heapq.heappop(max_heap) -- 5 + val = flag * heapq.heappop(max_heap) -- 4 + val = flag * heapq.heappop(max_heap) -- 3 + val = flag * heapq.heappop(max_heap) -- 2 + val = flag * heapq.heappop(max_heap) -- 1 + + -- 获取堆大小 + local size = #max_heap + + -- 判断堆是否为空 + local is_empty = #max_heap == 0 + + -- 输入列表并建堆 + min_heap = { 1, 3, 2, 5, 4 } + heapq.heapify(min_heap) + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index 64b097178d..e9612ef535 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -178,6 +178,19 @@ # 注释 ``` +=== "Lua" + + ```lua title="" + --- 标题注释,用于标注函数、类、测试样例等,采用 LDoc 注释规范 + + -- 内容注释,用于详解代码 + + --[[ + 多行 + 注释 + --]] + ``` + ## 在动画图解中高效学习 相较于文字,视频和图片具有更高的信息密度和结构化程度,更易于理解。在本书中,**重点和难点知识将主要通过动画以图解形式展示**,而文字则作为解释与补充。 diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 5bd6ccde02..df5ac0c2a5 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -383,6 +383,35 @@ is_empty = size.zero? ``` +=== "Lua" + + ```lua title="deque.lua" + -- 使用Lua表模拟双向队列数据结构 + -- 初始化双向队列 + local deq = {} + + -- 元素入队 + table.insert(deq, 2) -- 添加至队尾 + table.insert(deq, 5) + table.insert(deq, 4) + table.insert(deq, 1, 3) -- 添加至队首 + table.insert(deq, 1, 1) + + -- 访问元素 + local front = deq[1] -- 队首元素 + local rear = deq[#deq] -- 队尾元素 + + -- 元素出队 + local pop_front = table.remove(deq, 1) -- 队首元素出队 + local pop_rear = table.remove(deq) -- 队尾元素出队 + + -- 获取双向队列的长度 + local size = #deq + + -- 判断双向队列是否为空 + local is_empty = #deq == 0 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 0758fafc4c..38253d7d96 100755 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -356,6 +356,33 @@ is_empty = queue.empty? ``` +=== "Lua" + + ```lua title="queue.lua" + -- 使用Lua表模拟队列数据结构 + -- 初始化队列 + local que = {} + + -- 元素入队 + table.insert(que, 1) + table.insert(que, 3) + table.insert(que, 2) + table.insert(que, 5) + table.insert(que, 4) + + -- 访问队首元素 + local front = que[1] + + -- 元素出队 + local pop = #que == 0 and nil or table.remove(que, 1) + + -- 获取队列长度 + local size = #que + + -- 判断队列是否为空 + local is_empty = #que == 0 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 9b03591ae2..65504a18f3 100755 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -349,6 +349,33 @@ is_empty = stack.empty? ``` +=== "Lua" + + ```lua title="stack.lua" + -- 初始化栈 + -- Lua 没有内置的栈类,可以把 table 当作栈来使用 + local stack = {} + + -- 元素入栈 + table.insert(stack, 1) + table.insert(stack, 3) + table.insert(stack, 2) + table.insert(stack, 5) + table.insert(stack, 4) + + -- 访问栈顶元素 + local peek = stack[#stack] + + -- 元素出栈 + local pop = table.remove(stack) + + -- 获取栈的长度 + local size = #stack + + -- 判断是否为空 + local is_empty = #stack == 0 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index 1b20887771..da204713b5 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -128,6 +128,14 @@ tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` +=== "Lua" + + ```lua title="" + -- 二叉树的数组表示 + -- 使用 math.huge 来表示空位 + tree = {1, 2, 3, 4, math.huge, 6, 7, 8, 9, math.huge, math.huge, 12, math.huge, math.huge, 15} + ``` + ![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png) 值得说明的是,**完全二叉树非常适合使用数组来表示**。回顾完全二叉树的定义,`None` 只出现在最底层且靠右的位置,**因此所有 `None` 一定出现在层序遍历序列的末尾**。 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 31d3c6ca6b..678c81101f 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -228,6 +228,32 @@ AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二 end ``` +=== "Lua" + + ```lua title="" + --- @class TreeNode + --- AVL 树节点类 + --- @field val number 节点值 + --- @field height number 节点高度 + --- @field left TreeNode|nil 左子节点引用 + --- @field right TreeNode|nil 右子节点引用 + local TreeNode = {} + TreeNode.__index = TreeNode + + --- 构造函数 + --- @param val number 节点值 + --- @return TreeNode 新节点实例 + function TreeNode.new(val) + local obj = {} + setmetatable(obj, TreeNode) + obj.val = val or 0 -- 节点值 + obj.height = 0 -- 节点高度 + obj.left = nil -- 左子节点引用 + obj.right = nil -- 右子节点引用 + return obj + end + ``` + “节点高度”是指从该节点到它的最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 $0$ ,而空节点的高度为 $-1$ 。我们将创建两个工具函数,分别用于获取和更新节点的高度: ```src diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 72e9b3b80a..08f66b1cf8 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -201,6 +201,30 @@ end ``` +=== "Lua" + + ```lua title="" + --- @class TreeNode + --- 二叉树节点类 + --- @field val number 节点值 + --- @field left TreeNode|nil 左子节点引用 + --- @field right TreeNode|nil 右子节点引用 + local TreeNode = {} + TreeNode.__index = TreeNode + + --- 构造函数 + --- @param val number 节点值 + --- @return TreeNode 新节点实例 + function TreeNode.new(val) + local obj = {} + setmetatable(obj, TreeNode) + obj.val = val or 0 -- 节点值 + obj.left = nil -- 左子节点引用 + obj.right = nil -- 右子节点引用 + return obj + end + ``` + 每个节点都有两个引用(指针),分别指向左子节点(left-child node)右子节点(right-child node),该节点被称为这两个子节点的父节点(parent node)。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树(left subtree),同理可得右子树(right subtree)。 **在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如下图所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 @@ -449,6 +473,23 @@ n2.right = n5 ``` +=== "Lua" + + ```lua title="binary_tree.lua" + -- 初始化二叉树 + -- 初始化节点 + local n1 = TreeNode.new(1) + local n2 = TreeNode.new(2) + local n3 = TreeNode.new(3) + local n4 = TreeNode.new(4) + local n5 = TreeNode.new(5) + -- 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false @@ -615,6 +656,18 @@ n1.left = n2 ``` +=== "Lua" + + ```lua title="binary_tree.lua" + -- 插入与删除节点 + local P = TreeNode.new(0) + -- 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + -- 删除节点 + n1.left = n2 + ``` + ??? pythontutor "可视化运行" https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false