递归比较两个数组而不使用循环[伪代码]

时间:2018-12-08 06:00:31

标签: arrays algorithm recursion

  

考虑给定的两个数组A和B,不要重复(   是,同一元素不会重复出现)。任务是检查是否   不考虑顺序,B的每个元素也是A的元素。   例如,如果A = [1、2、3、4]而B = [2、3、1],则答案为是。如果   但是B = [1,2,5],则答案为否,因为5不在A中。

     

针对上述问题设计递归算法(不使用循环)。

我正在尝试解决上述问题,并且不使用循环就找不到解决方法。有人会知道一种无需使用循环即可递归解决此问题的方法吗?

我不能使用任何内置函数,这是算法和数据结构的递归练习。

3 个答案:

答案 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中的每个元素都需要进行上述测试。如果BisMember()中的每个元素返回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实现了)只是检查数组/列表中是否存在给定的数字。但是请注意,它也需要startIndexendIndex,它们专门表示我们的子数组/子列表边界。

简而言之,以下方法检查给定的number是否存在于list中,但是仅在startIndexendIndex(包括两个端点)之间进行检查。请考虑将数组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); } 答对了!!完成了。

我希望您以特定的方式考虑递归函数。可以将它们视为需要接受哪些论据,以及可以做什么。因此,当您在递归方法内进行递归调用时,您无需考虑该递归调用将如何工作,而不必在那里进行抽象,并相信递归调用将为您提供结果。现在,重点介绍如何使用此返回结果使此递归方法正确工作。