考虑给定的两个数组A和B,不要重复( 是,同一元素不会重复出现)。任务是检查是否 不考虑顺序,B的每个元素也是A的元素。 例如,如果A = [1、2、3、4]而B = [2、3、1],则答案为是。如果 但是B = [1,2,5],则答案为否,因为5不在A中。
针对上述问题设计递归算法(不使用循环)。
我正在尝试解决上述问题,并且不使用循环就找不到解决方法。有人会知道一种无需使用循环即可递归解决此问题的方法吗?
我不能使用任何内置函数,这是算法和数据结构的递归练习。
答案 0 :(得分:0)
用于递归测试数组A
是否包含给定元素的函数的伪代码,x
可能看起来像这样:
function isMember(x, A):
if A = [] then return false
if x = A[0] then return true
return isMember(x, A[1..-1])
end
此函数的前提是要测试x
是否是A
的成员,我们可以测试一下A
的第一个元素是否表示为{{1} },与A[0]
相同。如果是这样,我们可以终止该函数并返回x
。如果不是,则再次调用该函数,并向其传递数组true
的所有元素,除外,我们已经测试过的第一个元素。我用A
表示了A
的其余元素,即元素编号1到-1,在某些语言中,这是引用最后一个元素的另一种方式。
现在,在第二次调用此函数期间,正在将A[1..-1]
与x
的第一个元素进行比较,后者当然是A[1..-1]
的第二个元素。如此反复进行,每次缩小数组A
的大小时,都会测试并丢弃列表顶部的元素。
然后,我们最终到达最终情况,A
中不再有要测试的元素,这导致对该函数的最终递归调用传递给一个空数组。在这种情况下,我们可以推断出A
中的每个元素都无法与A
匹配,因此我们可以安全地返回x
,说明false
不是 是x
的成员。
现在,为了确定给定数组A
是否包含在数组B
中,A
中的每个元素都需要进行上述测试。如果B
为isMember()
中的每个元素返回true
,则B
必须包含在B
中。如果任何一个元素导致A
返回isMember()
,那么我们可以停止进一步的测试,因为false
包含不在B
中的元素,因此它不能是子数组。 / p>
以下是说明此递归过程的伪代码:
A
在很多方面,它与第一个功能非常相似,这并不奇怪。但是,这次每次递归时数组function containedBy(B, A):
if B = [] then return true
let x := B[0]
if not isMember(x, A) then return false
return containedBy(B[1..-1], A)
end
的大小都会减小,因为其前导元素会传递到B
函数,然后在isMember()
返回isMember()
时被丢弃。和以前一样,最后一次递归调用true
之后的最后一种情况将通过一个空列表代替containedBy()
。这仅意味着B
的每个元素都已成功通过B
通过成员资格测试,因此我们返回A
以确认true
确实包含在{{1 }}。
答案 1 :(得分:0)
一种方法是将具有嵌套循环的简单迭代算法转换为递归算法。
一种迭代解决方案,看起来没有像使用地图那样经过优化,但是它具有 O(n²)时间复杂度,它看起来像这样:
function includesValue(A, v):
for i = 0 to length(A) - 1:
if A[i] == v:
return true
return false
function includesArray(A, B):
for j = 0 to length(B) - 1:
if not includesValue(A, B[j]):
return false
return true
如果将其转换为递归模式,则可以将当前索引作为附加参数传递:
function recIncludesValueAfter(A, v, i):
if i >= length(A):
return false
if A[i] == v:
return true
return recIncludesValuesAfter(A, v, i + 1)
function recIncludesSubArray(A, B, j):
if j >= length(B):
return true
if not recIncludesValueAfter(A, B[j], 0):
return false
return recIncludesSubArray(A, B, j + 1)
您将使用第三个参数0来调用它:
recIncludesSubArray(A, B, 0)
两个递归函数中的每一个都使用这种模式:
if
块对应于迭代版本中循环的结尾if
块对应于迭代版本(包括其潜在的突破)中for循环的主体如果您需要一个优化的版本,并使用一组(仅键是重要的映射,而不是与键相关的值的映射),则迭代版本应如下所示:
function includesArray(A, B):
setA = new Set
for i = 0 to length(A):
setA.add(A[i])
for j = 0 to length(B) - 1:
if setA.has(B[j]):
return false
return true
同样,我们可以将其转换为递归版本:
function recAddSubArrayToSet(setA, B, j):
if j >= length(B):
return setA
setA.add(B[j])
return recAddSubArrayToSet(setA, B, j + 1)
function recSetIncludesSubArray(setA, B, j):
if j >= length(B):
return true
if not setA.has(B[j]):
return false
return recSetIncludesSubArray(A, B, j + 1)
function recIncludesSubArray(A, B):
setA = new empty Set
recAddSubArrayToSet(setA, B, 0)
return recSetIncludesSubArray(setA, B, 0)
您写道,不允许使用内置函数。仅当您牢记特定的目标编程语言时,这才有意义。在伪代码中,没有内置函数的概念。
某些语言会以一种仅提供您自己的方式来提供地图/场景/词典的功能,只需调用它们(内置)的方法即可对它们进行一些有用的操作,而其他语言则允许您应用运算符(例如+
),然后使用in
运算符测试成员资格。
但是,即使要获取数组的大小,也可能需要使用某些语言进行函数调用。因此,此约束实际上仅在特定编程语言的上下文中才有意义。
答案 2 :(得分:0)
您可以通过设计对原始数组的一部分进行操作的递归函数/方法,将循环转换为递归(从技术上讲,是在子数组上)(最初,子数组就是您的完整数组),并将数组的大小减小(每次传递递归方法时)减小1。
以下方法(我已经用Java实现了)只是检查数组/列表中是否存在给定的数字。但是请注意,它也需要startIndex
和endIndex
,它们专门表示我们的子数组/子列表边界。
简而言之,以下方法检查给定的number
是否存在于list
中,但是仅在startIndex
和endIndex
(包括两个端点)之间进行检查。请考虑将数组B
(在我的情况下为listB
)的每个元素传递给此方法,并且list
参数实际上是对数组A
的引用({{ 1}})。
listA
现在,一旦有了上述方法,您现在就可以使用一种递归方法来一次显示/**
* This method recursively checks whether given
* number is contained in the given list or not.
*
* For this method startIndex and endIndex
* correspond to the indices of listA
*/
private static boolean contains(List<Integer> list, int startIndex, int endIndex, int number) {
if (startIndex == endIndex) {
return list.get(startIndex) == number;
} else if (startIndex < endIndex) {
return list.get(startIndex) == number || contains(list, startIndex + 1, endIndex, number);
}
// should never be the case
return true;
}
的所有元素,然后将其“插入”上述方法中。可以很珍贵地完成以下操作:
listB
对上述方法的调用将类似于/**
* This method recurse over each element of listB and checks
* whether the current element is contained in listA or not
*
* for this method startIndex and endIndex correspond to the
* indices of listB
*/
private static boolean contains(List<Integer> listA, List<Integer> listB, int startIndex, int endIndex) {
if (startIndex > endIndex) {
return true;
}
boolean c = contains(listA, 0, listA.size() - 1, listB.get(startIndex));
if (!c) {
return false;
}
return contains(listA, listB, startIndex + 1, endIndex);
}
答对了!!完成了。
我希望您以特定的方式考虑递归函数。可以将它们视为需要接受哪些论据,以及可以做什么。因此,当您在递归方法内进行递归调用时,您无需考虑该递归调用将如何工作,而不必在那里进行抽象,并相信递归调用将为您提供结果。现在,重点介绍如何使用此返回结果使此递归方法正确工作。