这是递归的有效方式(好的风格)吗?

时间:2012-12-12 18:45:29

标签: c++ recursion

除了这个代码是非常低效的,这种方式我写的递归函数在这里被认为是“好风格”。比如我正在创建一个包装器然后传递int mid和一个计数器int count

此代码的作用是从数组中获取值,然后查看与blockIndex结合的值是否大于mid。那么,从低效率来看,我会得到一份写这样的递归函数的工作吗?

int NumCriticalVotes :: CountCriticalVotesWrapper(Vector<int> & blocks, int blockIndex)
{

    int indexValue = blocks.get(blockIndex);
    blocks.remove(blockIndex);
    int mid = 9;

    return CountCriticalVotes(blocks, indexValue, mid, 0);
}


int NumCriticalVotes :: CountCriticalVotes(Vector<int> & blocks, int blockIndex, int mid, int counter)
{
    if (blocks.isEmpty())
    {
        return counter;
    }

    if (blockIndex + blocks.get(0) >= mid)
    {
        counter += 1;
    } 

    Vector<int> rest = blocks;
    rest.remove(0);
    return CountCriticalVotes(rest, blockIndex, mid, counter);
}

5 个答案:

答案 0 :(得分:2)

这对于足够小的集合来说是有效的。

然而,这是非常低效的 - 对于每个递归调用,您正在创建Vector的整个不计数部分的副本。所以,如果你计算一个包含1000个项目的向量,你将首先创建一个999项目的Vector,然后是998个项目中的另一个,然后创建另一个997,依此类推,一直到0项目。

这本身就很浪费,但似乎甚至变得更糟。然后,您正在从Vector中删除项目 - 但是您要删除第一个项目。假设您的Vector类似于std::vector,删除最后一项需要一段时间,但删除第一项需要线性时间 - 即,删除第一项,之后的每一项都“向前”移动到空出的位置

这意味着您的算法不是占用恒定的空间和线性时间,而是在空间和时间上都是二次的。除非所涉及的集合非常小,否则它将非常浪费。

不是为每个调用创建一个完整的新Vector,而是将偏移量传递给现有的Vector。这样可以避免复制和删除项目,因此在时间和空间上都是线性的(这仍然很不合适,但至少不如二次方差)。

要进一步减少使用的空间,请将阵列视为两半。分别计算每一半,然后将结果加在一起。这会将递归深度减少到对数而不是线性,这通常相当实质上(例如,对于1000个项目,它的深度大约为10而不是大约1000.对于一百万个项目,深度上升到大约20而不是一百万。)

答案 1 :(得分:1)

如果不确切知道你想要完成什么,这是一个非常难以回答的问题。我看到递归或一般编码的方式是满足以下三个要求。

  1. 它是否实现了所有所需的功能?
  2. 是否有错误的弹性?这意味着当传递无效输入或边缘情况时它不会中断。
  3. 它是否在足够的时间内完成了目标?
  4. 我认为你担心3号,我可以说时间应该适合这个问题。例如,如果您搜索2个巨大的列表,则O(n ^ 2)可能是不可接受的。但是,假设您正在搜索2个小集合O(n ^ 2)可能足够快。

    我可以说的是尝试在测试用例上计算算法的不同实现。仅仅因为你的解决方案是递归的并不意味着它总是比“强力”实现更快。 (这当然具体取决于案例)。

    要回答你的问题,就递归而言,这个样本看起来很好。但是,你会得到一份写这样代码的工作吗?我不知道这满足其他两个编码要求有多好?

答案 2 :(得分:1)

您的解决方案的问题在于您传递计数。停止传递计数并使用堆栈来跟踪它。另一个问题是我不确定你的第二个条件是什么。

int NumCriticalVotes :: CountCriticalVotesWrapper(Vector<int> & blocks, int blockIndex)
{

    int indexValue = blocks.get(blockIndex);
    blocks.remove(blockIndex);
    int mid = 9;

    return CountCriticalVotes(blocks, indexValue, mid);
}


int NumCriticalVotes :: CountCriticalVotes(Vector<int> & blocks, int blockIndex, int mid)
{
    if (blocks.isEmpty())
    {
        return 0;
    }

    if (/*Not sure what the condition is*/) 
    {
        return 1 + CountCriticalVotes(blocks, blockIndex, mid);
    }

    return CountCriticalVotes(blocks, blockIndex, mid);
}

答案 3 :(得分:1)

非常主观的问题。尾递归是很好的(在我的书中),但我会平衡每次调用时创建一个新的向量,这使得线性算法是二次的。独立于递归,这是一个很大的禁忌,特别是因为它很容易避免。

关于代码打算完成什么的一些评论也会有所帮助,尽管我认为在上下文中问题会更少。

答案 4 :(得分:1)

在C ++中,使用递归遍历任意长度的列表绝不是一个好习惯。这不仅仅与性能有关。该标准不要求尾部调用优化,因此如果您不能保证列表的大小有限,则存在堆栈溢出的风险。

当然,递归深度必须是几十万,具有典型的堆栈大小,但是在设计程序时很难知道它将来必须能够处理哪种输入。这个问题可能会在以后困扰你。