lua 算法题集(1)
来源:互联网 发布:火线 知乎 编辑:程序博客网 时间:2024/05/22 02:17
此处收集一些简单的算法以及其lua的简单实现。(题目来自于剑指offer、程序员代码面试指南、网络资源等;提供的实现代码并非为最优或最佳,也可能遗漏了某些特殊情形,因此仅供参考。若存在问题,可以留言交流。)
1 数据结构相关
1.1 数组
1 将一个递增数组前后部分互换,查找数组的最小数
local t = {3,4,5,12,45,1.1,1.5,2} -- 一般情形local t = {1,0,1,1,1} -- 特殊情形 只能走顺序查找function getPivot( ) local low = 1 local high = #t -- 基本有序 采用二分查找 -- low指向前半部分 high指向后半部分 high - low <= 1时high位置即为查找位置 while low <= high do local mididx = math.floor((low+high)/2) if high - low <= 1 then return t[high] end if t[low] == t[mididx] and t[high] == t[mididx] then -- 此种情形 必须顺序遍历查找了 return minByOrder(t,low,high) end if t[low] <= t[mididx] then -- 前半部分 low前进 low = mididx else -- 后半部分 high后退 high = mididx end endendfunction minByOrder( t,low,high ) -- 首次降低 即为最小 for i=low,high do if t[i] > t[i+1] then return t[i+1] end endend-- testprint(getPivot())
2 打印1到最大的n位数
如果n过大(如100)无论数据定位何种类型都有可能溢出。对于大数问题,可采用字符串数组表达。
function addOneToStr(str) local up = 1 for i=#str,1,-1 do str[i] = str[i] + up if str[i] > 9 then -- 进位 str[i] = 0 if i == 1 then -- 全部后移一位 for i=#str,1,-1 do str[i+1] = str[i] end str[1] = 1 break end else -- 无需进位 break end endend-- testn = 3str = {0}for i=0,math.pow(10,n)-1 do if n == 0 then return end local result = "" for i=1,#str do result = result..str[i] end print(result) -- 基于数组的加1操作 addOneToStr(str)end
上述代码虽直观,但由于需要模拟加法,稍微麻烦,还有一种更简洁的做法,利用全排列递归方式实现。
function recurse(a,index) if index > #a then printT(a) return end for i=0,9 do a[index] = i recurse(a,index+1) endendfunction printT( a ) local st = "" local firstValid = false for i=1,#a do -- 前置0忽略 if a[i] == 0 and not firstValid then -- 全部为0 if i == #a then st = "0" end else st = st..a[i] firstValid = true end end print(st)end-- testa = {0,0,0}recurse(a,1)
3 输出字符串的所有排列,如abc的所有输出:abc acb bac bca cba cab
用递归很容易实现,但有两个注意点:1)借助于字符交换实现O(1)的空间复杂度;2)递归后一定要将数组重新交换已还原递归前的数组状态,因为递归中始终使用的是同一个数组,后续的变动将引起前期的数组变化。
local t = {"a","b","c"}local len = #tfunction digui( srcT,destT,index ) if index == len+1 then printT(destT) return end for i = index,len do destT[index] = srcT[i] -- 交换index与i位置值 local tmp = srcT[index] srcT[index] = srcT[i] srcT[i] = tmp digui(srcT,destT,index+1) -- 交换位置 复原未递归前的状态(重要) local tmp = srcT[index] srcT[index] = srcT[i] srcT[i] = tmp endendfunction printT( t ) local st = "" for i,v in ipairs(t) do st = st .. v end print(st)end-- testdigui(t,{},1)
4 求字符的所有无序组合,如abc的所有组合为a b c ab ac bc abc。
该问题仍可通过递归求解,不同的是,对于组合而言,ab与ba是同一个。对于一个字符数组,每个位置上的字符要不参与组合,要不不参与组合。针对当前位置的两个状态,进行后续位置的递归操作。
local t = {"a","b","c","d","e"}local len = #t-- tIndex:当前递归进程中遍历到的原始数组的位置-- resultRemainNum:还需组合的剩余字符总数 | resultTotalNum:字符总数function digui(result,tIndex,resultRemainNum,resultTotalNum) -- 组合完成 if resultRemainNum <= 0 then printT(result) return end -- 组合的剩余字符总数与原表中剩余字符总数相等 无需递归 全部复制即可 -- 如组合还剩余两个位置 此时index刚好走到"d" 直接将t表中后面的字符复制给result if resultRemainNum == #t-tIndex+1 then for i=tIndex,#t do result[resultTotalNum-resultRemainNum+1+(i-tIndex)] = t[i] end printT(result) return end -- 需要组合字符数大于原数组能提供的字符数 异常情形 if resultRemainNum > #t-tIndex+1 then return end -- 访问到index时 组合有两种行为:使用该字符|不使用该字符 -- 如果组合使用了该字符,继续向后遍历原数组,并且剩余需要组合的字符减1 result[resultTotalNum-resultRemainNum+1] = t[tIndex] digui(result,tIndex+1,resultRemainNum-1,resultTotalNum) -- 如果组合未使用该字符,剩余需要组合的字符总数不变 result[resultTotalNum-resultRemainNum+1] = nil digui(result,tIndex+1,resultRemainNum,resultTotalNum)endlocal st = ""function printT( t ) st = st.." " for i,v in ipairs(t) do st = st .. v endend-- testfor i=1,#t do digui({},1,i,i)endprint(st)
5 连续子数组的最大和。数组中有正负值,提取和最大的子数组。如对于数组{1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2}。
local t = {1,-2,3,10,-4,7,2,-5}function getSubMaxArray() local startIdx,endIdx,curStart = 1,1,1 local tmp = 0 local max = 0 for i=1,#t do if tmp < 0 then -- 和变为负值, 重新开始计算子数组和(负值必会使得和变小 因此要抛弃重新开始) curStart = i -- 新的起点 tmp = t[i] -- 新的子数组和 else tmp = tmp + t[i] -- 和累加 end if tmp > max then -- 当前子数组和超过历史记录 更新子数组起止点 max = tmp startIdx = curStart endIdx = i end end print(max,startIdx,endIdx)end-- testgetSubMaxArray()
6 统计从1到n整数中1出现过的次数
这种抽象问题,最好通过实例来寻找规律。以n=21345为例,首先将整数分为两段:1-1345、1346-21345。先来考虑1346-21345,首位(first)为2,那么首位为1时,从千位到个位,0-9数字随机出现,因此出现1的总数(firstNum)为:10^4,如果首位等于1呢,如11345?那么出现1的总数因为:1345+1。
我们再将1346-21345分为两段:1346-11345、11346-21345。对于1346-11345来说,假定千位为1,那么从百位到个位数字在0-9随机出现,总数为10^3,总共四位(len-1),因此总数为4*(10^3),11346-21345计算方式一样,因此1346-21345出现1的总数为:2*4*(10^3)。因此,仅考虑首位,1出现的总数为:10^4+2*4*(10^3)。用公式表达为:firstNum + first*(len-1)*(10^(len-2))。那么对于1-1345,如何统计?实际上这是一个递归的过程,我们完成了首位的统计,后续的就是逐位递归。
local n = 21345function digui(n) if n < 1 then return 0 end local first,len = getFirstLen(n) -- 仅一位数 特殊处理 if len == 1 then return 1 end local firstNum = 0 -- 先统计首位出现的1总数 if first == 1 then firstNum = n - math.pow(10,len-1) +1 elseif first > 1 then firstNum = math.pow(10,len-1) end -- 除首位外,某位定位1,其他位0-9随机 firstNum = firstNum + first*(len-1)*math.pow(10,len-2) -- 去除首位 继续递归 n = n - first*math.pow(10,len-1) return firstNum + digui(n)endfunction getFirstLen( n ) local len = 1 while n > 10 do len = len+1 n = math.floor(n/10) end return n,lenend-- testprint(digui(n))
7 寻找n个丑数
因子仅包含2、3、5的数成为丑数。寻找第n个丑数。这个丑数实际上是在已找到的丑数数组中乘以上述基础因子,来逐步增加丑数数目,因此我们在计算时要保存已计算好的丑数;同时,在某个时刻,基础因子总与丑数数组的每个数对应(因为一个新的丑数总是来源于旧的丑数乘以基础因子),因此需要用一个数组保存因子对应的丑数数组的位置。
local factorT = {2,3,5}function getUgly(n) local uglyT = {1} -- 存放已发现的丑数 local factorInUglyIdx = {1,1,1} -- 记录基础因子对应uglyT表的位置 local curUgly = 1 -- 记录当前丑数 local i = 1 while i < n do local min = nil local minK = -1 for k,v in ipairs(factorT) do -- 与基础因子相乘,寻找最小值作为新的丑数 local tmp = v*uglyT[factorInUglyIdx[k]] if not min or tmp <= min then minK = k min = tmp end end if(curUgly == min) then -- 找到的丑数与前一次相同 不做处理 else -- 找到一个新的丑数 curUgly = min table.insert(uglyT,min) i = i+1 end -- 在已有丑数表中的位置进1 factorInUglyIdx[minK] = factorInUglyIdx[minK]+1 end print(curUgly)end-- testgetUgly(20)
8 数组中的逆序对
在{4,7,5,6}中,逆序对为{7,5}、{7,6}、{5、6}。如果用两层for循环,复杂度O(n^2)。如果我们以1/2的方式缩小数组规模(lg(n)),然后在小规模小统计逆序对,然后合并小规模保持其有序,然后逐层递归,复杂度能达到O(nlg(n))。这实际上就是一个归并排序的过程。
在下面程序中有一个小技巧:归并排序在递归完需要利用一个临时数组更新并合并已经分别保持有序的左右子数组,数组更新不可避免,那么该合并过程?我们可通过增加一个辅助数组。用该辅助数组参与数组的更新,完成当前递归后,将辅助数据直接作为原数组,将原数组作为辅助数组进行后续计算。
local t = {7,5,6,4,1,9,11,3}local copy = {7,5,6,4,1,9,11,3}function merge( t,copy,startIdx,endIdx ) if endIdx - startIdx <= 0 then return 0 end local midIdx = math.floor((startIdx+endIdx)/2) -- 递归 统计左右子数组的逆序对 local reversedNumInLeft = merge(copy,t,startIdx,midIdx) -- 将辅助数组copy作为原数组,将原数组作为辅助数组进行后续计算 local reversedNumInRight = merge(copy,t,midIdx+1,endIdx) -- 递归后 左右子数组保持有序 将其合并 local reversedNumInMerge = 0 local tmpIdx = endIdx local preIdx,postIdx = midIdx,endIdx-- [[ 第一种形式 while startIdx <= preIdx or midIdx+1 <= postIdx do -- 左侧循环 while startIdx <= preIdx do if midIdx+1 > postIdx then -- 右侧子数组已全部处理完成 更新至辅助数组copy copy[tmpIdx] = t[preIdx] preIdx = preIdx - 1 tmpIdx = tmpIdx - 1 else if t[preIdx] <= t[postIdx] then -- 退出当前循环体(当前循环体仅用于处理左侧子数组 右侧子数组的处理交予后面的循环体) break else -- 当前左侧数大于右侧数 表明存在逆序对 copy[tmpIdx] = t[preIdx] -- 更新辅助数组 preIdx = preIdx - 1 tmpIdx = tmpIdx - 1 -- 统计逆序对 -- 什么原理?举例说明:左侧子数组{4,7},右侧{5,6},当处理左侧的7时,postIdx指向的6小于7,共有两个逆序对(7,5)与(7,6) reversedNumInMerge = reversedNumInMerge + postIdx-(midIdx+1)+1 end end end while midIdx+1 <= postIdx do -- 右侧循环 if startIdx > preIdx then -- 左侧子数组全部处理完成 copy[tmpIdx] = t[postIdx] postIdx = postIdx - 1 tmpIdx = tmpIdx - 1 else if t[preIdx] > t[postIdx] then -- 交予左侧循环处理 break else copy[tmpIdx] = t[postIdx] postIdx = postIdx - 1 tmpIdx = tmpIdx - 1 end end end end--]]--[[ 第二种形式 简洁一些但效率不变 while startIdx <= preIdx and midIdx+1 <= postIdx do -- 左侧循环 if t[preIdx] > t[postIdx] then copy[tmpIdx] = t[preIdx] preIdx = preIdx - 1 tmpIdx = tmpIdx - 1 reversedNumInMerge = reversedNumInMerge + postIdx-(midIdx+1)+1 else copy[tmpIdx] = t[postIdx] postIdx = postIdx - 1 tmpIdx = tmpIdx - 1 end end for i=preIdx,startIdx,-1 do copy[tmpIdx] = t[i] tmpIdx = tmpIdx - 1 end for i=postIdx,midIdx+1,-1 do copy[tmpIdx] = t[i] tmpIdx = tmpIdx - 1 end--]] return reversedNumInLeft+reversedNumInRight+reversedNumInMergeend--testlocal rt = merge(t,copy,1,#t)
9 打印和为某整数的连续正数序列
过程比较简单。设立前后指针,小于目标整数时后指针向后走,大于目标整数时前指针向后走,两指针相遇时退出。
function f(target) local pre = 1 local post = 2 local sum = pre + post while pre < post do -- 指针相遇 退出 while sum <= target do if sum == target then printSeq(pre,post) end -- 小于目标 后指针向后走 post = post + 1 sum = sum + post end while sum > target do -- 大于目标 前指针向后走 sum = sum - pre pre = pre + 1 end endendfunction printSeq(pre,post) for i=pre,post do print(i) endend-- testf(15)
1.2 堆栈
1 栈逆序 (仅能用递归 不能使用辅助空间)
不能使用辅助空间,意味着只能对原栈进行操作以完成逆序。需要两个递归函数。
第一个递归函数:移除栈底元素并返回。第二个递归:递归调用第一个函数,之后逐个压入栈内。
-- 第一个递归函数-- 返回最后一个 其余重新压入function removeLastEle() local curEle = stack:pop() if stack:isEmpty() then -- curEle是最后一个元素 return curEle end local lastEle = self:removeLastEle() stack:push(curEle) return lastEle -- 始终返回最后一个元素end-- 第二个递归函数function revertStackEle() if stack:isEmpty() then return end local curEle = removeLastEle() -- 逐个获取栈底 revertStackEle() stack:push(curEle) -- 相当于倒序压入end
2 生成窗口最大值数组
一个长度为n的整型数组和一个大小为w的窗口从数组的最左边滑到最右边。输出这n-w+1个窗口的最大值。
暴力解法为每生成一个窗口遍历一遍窗体并计算最大值,复杂度O(n*w)。可以用双端队列将复杂度将至O(n)。队列头存放数组最大值位置i,当其失效(移动窗口离开该位置)则弹出;当数组中的值大于队列尾部值时,从队列尾部弹出,然后将该值压入队列。
local winSize = 3 local arr = {4,3,5,4,3,3,6,7} function findMaxInWin() for i=1,#arr do if queueMax:isEmpty() then -- 如果双向队列为空 入队尾 queueMax:push_back(i) else if i - queueMax:front() >= 3 then -- 队头无效 弹出 queueMax:pop_front() end while not queueMax:isEmpty() and arr[queueMax:back()] <= arr[i] do -- 弹出所有小于arr[i]的数 queueMax:pop_back() end queueMax:push_back(i) end if i >= winSize then -- 输出当前窗体最大值 print(arr[i]) end end end
3 构造数组(无重复)的MaxTree
MaxTree定义为:二叉树;树中的任何子树,最大的节点均为树头。
构造方法:1)每个数父节点为它左侧第一个(从它向左侧数起)比它大的数和它右侧第一个比它大的数中的较小值;2)整个数组的最大值为MaxTree的头节点。该方法能得到有效二叉树。反证:如果一个父节点n有两个以上孩子节点,则必有两个数i,j在n的同侧,这是矛盾的。因为如果i < j < n,那么i的父节点应当为j;如果j < i < n,那么j的父节点应该为i。
local a = {3,4,5,1,2}function generateTree() local nodeLst = {} for i,v in ipairs(a) do local curNode = createNode(v) table.insert(nodeLst,curNode) end local leftBigMap = {} -- 记录节点左侧第一个大于它的节点 for i=1,#a do while not stack:isEmpty() and stack:peak() <= a[i] do -- 这种构造式能够让栈保持递减序列 -- a[i]大于栈顶 递减序列会被破坏 因此需将所有小的数弹出 -- 同时,弹出时将前一个数记录为后一个数的leftBigNode(递减序列保证了这一点) MapAndPop(stack,leftBigMap) end stack:push(a[i]) end while not stack:isEmpty() do -- 处理栈中未处理数据 MapAndPop(stack,leftBigMap) end local rightBigMap = {} -- 记录节点右侧第一个大于它的节点 for i=1,#a do while not stack:isEmpty() and stack:peak() <= a[i] do MapAndPop(stack,rightBigMap) end stack:push(a[i]) end while not stack:isEmpty() do MapAndPop(stack,rightBigMap) end local headNode -- 树头 for k,v in pairs(nodeLst) do -- 构建二叉树 local leftBigNode = leftBigMap[v] local rightBigNode = rightBigMap[v] if not leftBigNode and not leftBigNode then headNode = v elseif not leftBigNode and rightBigNode then rightBigNode.leftNode = v elseif leftBigNode and not rightBigNode then leftBigNode.rightNode = v elseif leftBigNode and rightBigNode then if leftBigNode.value < rightBigNode.value then leftBigNode.rightNode = v else rightBigNode.leftNode = v end end end return headNodeendfunction MapAndPop( stack,leftBigMap ) local popNode = stack:pop() local peakNode = (not stack:isEmpty()) and stack:peak() or nil leftBigMap[popNode] = peakNodeend
4 计算最大子矩阵大小
给定一个map矩阵,仅包含0,1,计算其中全是1的矩形区域中,面积最大的一个。
方法:1)计算每一层至第一层 每个列位置上1连续出现的次数;2)计算每一层能够生成的最大矩形面积。
local map = { [1] = {1,0,1,1}, [2] = {1,1,1,1}, [3] = {1,1,1,0}, }local height = {}local MaxRectSize = 0function MaxRectSize() -- 为每一层建立高度数组 for i,#map do -- 统计从1层至i层 每个列位置出现连续1的总数 height[i] = {} for j=1,#map[i] do height[i][j] = 0 end for j=1,#map[i] do if i == 1 then height[i][j] = map[i] > 0 and 1 or 0 else height[i][j] = map[i] > 0 and height[i-1][j] + 1 or 0 end end end -- 统计每一层的最大矩阵面积(其实就是全部为1的矩形内1的数目) for i=1,#height do -- 计算该层 某列左侧及右侧大于它高度的最远位置(实际上就是找出左侧及右侧第一个小于它高度的位置 再加减1) -- 我们仍使用栈完成这一搜索过程 local stack = Stack:create() for j=1,#height[i] do while not stack:isEmpty() and stack:peak() >= height[i][j] do -- 该方式将栈构造成递增序列(当前高度小于等于栈顶 pop) popAndCalcuMaxRectSize(stack,height[i],j) end stack:push(j) end -- 如果栈内还有数据 继续出栈处理 while not stack:isEmpty() do -- 栈内还有数据 表明其高度均小于最后一个位置的高度(否则在遍历最后一个元素时必将其pop) -- 因此它的rightBiggerColIdx为#height[i] popAndCalcuMaxRectSize(stack,height[i],#height[i]) end endendfunction popAndCalcuMaxRectSize( stack,curRowHeight,rightBiggerColIdx ) local rightBiggerHeight = curRowHeight[rightBiggerColIdx] local popIdx = stack:pop() local popHeight = curRowHeight[popIdx] local peakIdx = -1 if not stack:isEmpty() then peakIdx = stack:peak() end -- 弹出位置对应高度的左侧第一个小于它高度的位置是栈顶记录的位置 -- 以 {3,4,5,4}为例 经过一次处理后,中间所有大于等于4的数4,5被pop,形成的新栈为{peakidx=3,popidx=4} -- 可以发现:posidx左侧第一个小于它的位置就是peakidx local leftFirstSmallIdx = peakIdx -- 弹出位置对应高度的右侧第一个小于它高度的位置正是将要入栈的位置rightBiggerColIdx -- 该位置若要被弹出 说明第一次遇到了小于它的数 local rightFirstSmallIdx = rightBiggerColIdx if popHeight > rightBiggerHeight then local curMaxRectSize = ((rightBiggerColIdx-1)-(leftFirstSmallIdx+1)+1)*popHeight -- 将当前pop位置左右扩展至最远的大于它的高度 距离乘以高度即为完全包含1的矩形面积(想像成直方图) MaxRectSize = math.max(curMaxRectSize,MaxRectSize) elseif popHeight == rightBiggerHeight then -- 如果相等 我们可以暂且不管 -- 因为rightBiggerColIdx后续仍会被入栈 其与popidx能够产生完全一样的矩形 当rightBiggerColIdx被pop时会被处理 因此不会遗漏掉最大矩形的搜索 endend
5 最大值减最小值小于等于num的子数组数量
最大值|最小值|子数组,这些关键词意味着可以使用最大最小双端队列来计算。双端队列有两个功能:1)保证遍历过得元素中出现的极值位于队头;2)遍历过程中的不重要数据间断性抛出,使得有效性数据成有序排列(实际上栈亦有此功能,只是栈无法访问栈底)。什么是不重要数据?比如想要得知数组从某位置至当前位置的最大值,那么如果当前值比前面的值大,那么前面的值就是不重要的值,应当抛出。
local a = { 2,5,7,3,5,8 }local num = 3local totalCnt = 0function getNum() local i,j = 1,1 Queue qMax = Queue:create() Queue qMin = Queue:create() while i <= #a do while j <= #a do -- 构建最大最小双向队列 while not qMax:isEmpty() and a[qMax:peek()] <= a[j] do -- j之前所有小的数全部弹出 -- 构建队列的递减序列 qMax:pop_back() end qMax:push_back(j) while not qMin:isEmpty() and a[qMin:peek()] >= a[j] do -- 构建队列的递增序列 qMin:pop_back() end qMin:push_back(j) if (qMax:peek() - qMin:peek()) > num then -- 不满足条件 退出向后扩展过程 -- 到达j位置才不满足条件 说明 i 至 j-1区间满足条件 break end j = j+1 -- 后侧向后扩展 end if i == qMax:peek() then -- i已走到最大队列的头部位置 弹出 qMax:pop_front() end if i == qMin:peek() then -- 已走至最小队列头部位置 弹出 qMin:pop_front() end totalCnt = totalCnt+((j-1)-i+1) -- [i,j-1]区间满足条件 [i,i+1] ~ [i,j-2]也一定都满足条件 i = i+1 -- 前侧向后扩展 endend
6 一次数组遍历能够产生哪些栈/队形式?
1.3 树
1 基于先序与中序 还原二叉树结构
local preOrder = {1,2,4,7,3,5,6,8}local inOrder = {4,7,2,1,5,3,8,6}-- 递归过程-- 每次递归 先序和中序遍历对应的子数组 均从某个位置startIdx开始 持续Len个长度function constructTree(node,preOrderStartIdx,inOrderStartIdx,len) if len < 1 then return end -- 计算当前先序中的根节点在中序中的位置 node.value = preOrder[preOrderStartIdx] local curInOrderRootIdx = getInOrderIdx(node.value,inOrderStartIdx,len) if not curInOrderRootIdx then return end -- 计算左右子节点长度 local leftLen = curInOrderRootIdx - inOrderStartIdx local rightLen = len - leftLen - 1 -- 基于先序中确定左右子节点值 local left,right = {},{} node.left = left node.right = right --左右递归 constructTree(left,preOrderStartIdx+1,curInOrderRootIdx-leftLen,leftLen) constructTree(right,preOrderStartIdx+leftLen+1,curInOrderRootIdx+1,rightLen)endfunction getInOrderIdx( v,inOrderStartIdx,len ) for i=inOrderStartIdx,inOrderStartIdx+len-1 do if inOrder[i] == v then return i end endend-- testlocal root = {}constructTree(root,1,1,8)function visit( node ) if not node then return end print(node.value) visit(node.left) visit(node.right)endvisit(root)
2 输入两棵二叉树A和B,判断B是否为A的子树(节点结构相同 值相同)。
function visitTree( aNode,bNode ) if not aNode or not bNode then return false end if aNode.value == bNode.value then -- 当前节点值一致 进行子节点递归判断 if isSubFunc(aNode,bNode) then return true end end local isSub = visitTree(aNode.left,bNode) -- 左子树上已找到相同子树 直接返回 无需再遍历右子树 if isSub then return true end isSub = visitTree(aNode.right,bNode) return isSubend-- 遍历完bNode 判断是否与aNode完全一致function isSubFunc( aNode,bNode ) -- b树当前父节点没有子节点 -- 此处为何能直接返回true?因为此递归还有一个终止条件:aNode.value ~= bNode.value,如果能成功绕过该判断并走到B树的分支最后,说明这条分支上所有值都能满足:aNode.value == bNode.value。这是子树判断的关键。 if not bNode then return true end -- b树还有子节点 但a树没有 结构不一致 非相同树 if not aNode then return false end -- 节点值不同 非相同树 if aNode.value ~= bNode.value then return false end -- 判断左树是否一致 local isSub = isSubFunc(aNode.left,bNode.left) if not isSub then return false -- 若不一致 没有走右树判断的必要 end isSub = isSubFunc(aNode.right,bNode.right) return isSub -- 返回最终判断结果end-- testaNode = {value=8}aNode.left = {value=8}aNode.left.left = {value=9}aNode.left.right = {value=2}aNode.left.right.left = {value=21}aNode.left.right.right = {value=4}aNode.right = {value=7}bNode = {value=8}bNode.left = {value=9}bNode.right = {value=2}bNode.right.right = {value=4}print(visitTree(aNode,bNode))
3 树的深度
计算树的深度,在树的遍历过程中加入深度统计过程即可。下面用两种方式实现。
-- 方式1:利用先序遍历计算深度-- 根节点深度为1 方向向下 逐层加1 叶节点返回最大深度local function preOrderForDepth(node,depth) if not node then return 0 end -- depth:父节点深度 下到当前层 深度加1 local curNodeDepth = depth + 1 if not node.left and not node.right then -- 叶子节点 返回其深度 return curNodeDepth end local leftDepth = preOrderForDepth(node.left,curNodeDepth) local rightDepth = preOrderForDepth(node.right,curNodeDepth) return math.max(leftDepth,rightDepth)end-- 方式2:利用后续遍历计算速度-- 叶节点深度为1 方向向上 逐层加1 根节点返回最大深度local function lastOrderForDepth( node ) if not node then return 0 end if not node.left and not node.right then -- 叶子节点 深度为1 return 1 end local leftDepth = lastOrderForDepth(node.left) local rightDepth = lastOrderForDepth(node.right) return math.max(leftDepth+1,rightDepth+1)end-- testaNode = {value=8}aNode.left = {value=8}aNode.left.left = {value=9}aNode.left.right = {value=2}aNode.left.right.left = {value=21}aNode.left.right.right = {value=4}aNode.right = {value=7}aNode.right.right = {value=5}aNode.right.right.right = {value=20}aNode.right.right.right.left = {value=11}local rt = preOrderForDepth(aNode,0)local rt = lastOrderForDepth(aNode)
1.3 一些实际问题
1 n个骰子的点数
求n个骰子点数总数为某数的点数序列。一种简单粗暴的方法是采用递归的方式对每个骰子的所有点数进行累加。如何实现呢?实际上,这一对应着一个多叉树,每个骰子对应一层,每层有六个子节点(1-6),遍历每一条路径并统计其总和。这类问题很多,程序结构也是固定的:递归(完成遍历)加for循环(访问同层所有子节点)。
local target = 34function f( n,list,curCnt ) if n <= 0 then -- 递归至叶子节点 判断总和 if curCnt == target then printT(list) end return end for i=1,6 do -- 处理每一个子节点 list[n] = i f(n-1,list,curCnt+i) -- 深度遍历 endendfunction printT( t ) local s = "" for i,v in ipairs(t) do s = s .. " " .. v end print(s)end-- testf(6,{},0)
上述问题采用遍历所有路径的方式求解所有点数序列,如果问题改一下,只需统计骰子的点数和等于某一整数值的总次数,是否还是需要全部遍历一遍树?实际上可以采用效率更高的做法。每增加一个骰子,骰子的各个点数和会出现什么变化?每个点数和次数应该是前面6个点数和出现次数的总和。
-- 方法2function f2(n) -- 方式1 if n < 1 then return 0 end local a1 = {} local a2 = {} -- 辅助数组 存放上一次结果 -- 一个骰子的情形 for i=1,6 do a1[i] = 1 a2[i] = 1 end -- 两个以上骰子 for i=2,n do -- 总和i之前的全部为0(每个骰子至少是1 因此总和最小为i) for k=1,i-1 do a1[k] = 0 end -- 总和最大值n*6(所有骰子点数均为6) for k=i,n*6 do a1[k] = 0 for t=k-6,k-1 do if a2[t] then -- 每增加一个骰子 总和k出现的总数为前面6个数出现次数的总和 -- 如对于两个骰子 第二个骰子8出现的情形如下:第一个骰子的2+第二个骰子的6;依次类推为:3+5;4+4;5+3;6+2 -- 因此8出现的次数为 第一个骰子2、3、4、5、6出现次数的总和 a1[k] = a1[k] + a2[t] end end end -- 更新辅助函数 for i,v in ipairs(a1) do a2[i] = v end end -- printT(a1) return a1[target]end-- testprint(f2(6))
- lua 算法题集(1)
- lua写排序算法
- cocos2dx(lua)ASTAR算法
- lua-洗牌算法
- lua实现KMP算法
- A*算法lua实现
- lua基础算法
- LUA 排序算法和性能分析[1]:table.sort
- lua调试打印table算法
- Lua快速排序算法+代码
- Lua 编写快速排序算法
- Lua快速排序算法+代码
- lua(1)
- Lua - 1
- Lua(1)
- lua(1)
- [Lua-1] Metatable In Lua
- 【Lua】【1】探讨Lua基础知识
- xcode报错:"_OBJC_CLASS_$_RootModel",referenced from
- 时间与日期问题
- 3389端口映射-使用mstsc进行远程协助
- 掌握了这些衍生模式,储蓄升值不再被坑
- EasyUI 动态添加标签页
- lua 算法题集(1)
- linux下I2C驱动架构全面分析
- Worker::setProcessTitle()解析
- C++的函数指针和宏定义——define、typedefine
- 常见的mysql数据库的优化
- BLE-CC2640R2F-(01)off-oad片外空中升级
- 高可用Docker容器云在58集团的实践
- 亿图图示完全破解版(edraw max)软件介绍
- Mac上终端的命令总结