算法的代价

Cost of an algorithm

本文关键字:代价 算法      更新时间:2023-10-16

问题是下一个:

如果想知道vector的大小,我们不能使用size(),但是我们有一个函数inBounds(vector&arr,int index),如果索引是vector对象的有效位置,则返回true。

我的方法是迭代位置。从1开始,复制(2,4,8,16,32…),直到inBounds返回false,后退一步,在子范围内重复搜索。

举个例子,N = 21:

  • 1 = True
  • 2 = True
  • 4 = True
  • 8 = True
  • 16 = True
  • 32 = False

返回到16,在16-32范围内搜索:

  • (16+1) = True
  • (16+2) = True
  • (16+4) = True
  • (16+8) = False

回到20(16+4),重新开始:

  • (20+1) = True
  • (20+2) = False

重新开始:

  • (21+1) = False

好的,那么大小是21

这是我的代码实现:

#include <iostream>
#include <vector>
using namespace std;
int inBounds(const vector<int>& arr,int i)
{
    return i < arr.size();
}
int size(const vector<int>& arr)
{
    int init = 0;
    while (inBounds(arr,init))
    {
        int start = 2;
        while (inBounds(arr,init+start))
        {
            start *= 2;
        }
        start /= 2;
        init += start;
    }
    return init;
}

int main()
{
    vector<int> arr;
    for (int i = 0;i < 1000;i++)
    {
        arr.resize(i);
        int n = size(arr);
        if (n != arr.size())
            cout << "FAIL " << arr.size() << ' ' << n << endl;
    }
    return 0;
}

这个效果很好。但我不知道这个算法的确切成本。第一次搜索确实是log(N),但现在我需要添加子范围搜索。所以我对实际成本有疑问

实际上,最坏的情况是O(log(n)2)(这是意料之中的,因为您有一个O(log)周期嵌套在另一个日志周期中)

要了解原因,请尝试缩小31(一般情况下为2N-1)

我们举个例子,N = 31:

1 = True
2 = True
4 = True
8 = True
16 = True
32 = False

O(log(N))到这里,好吧,没人质疑它

,

现在计算额外的步骤

返回到16,在16-32范围内搜索:

(16+1) = True
(16+2) = True
(16+4) = True
(16+8) = True
(16+16) = False 

(4 + 1)步骤——日志(32/2)+ 1 =日志(32)

24岁退一步

(24+1) = True
(24+2) = True
(24+4) = True
(24+8) = False

(3+1)步-即log(16/2)+1=log(16)

28岁退一步:

(28+1) = True
(28+2) = True
(28+4) = False

(2+1)步-即log(8/2)+1=log(8)

30岁退一步

(30+1) = True
(30+2) = False

(1+1)步,即log(4/2)+1=log(4)

结果:(4+3+2+1=正10步+负4步)。或者用另一种形式log(32)+log(16)+log(8)+log(4)+log(2) - 1 =log(32)(1+1/2+1/3+1/4+1/5)-1。忽略最后的-1,你的公式就变成了像

这样的东西

log(N)*(1+1/2+1/3+1/4+...+1/N)

对于大N-es,调和级数是发散的,渐近行为是对数的。

你得到了一个很好的O(log(N)*log(N))复杂度。

QED (? ?)

似乎你的第一个子范围搜索也是logN(因为它使用与初始部分基本相同的算法),而你的最终子范围搜索是线性的(迭代每个值),但它非常有限,是n的一小部分。我会说你的算法大约是c * logN,其中c是一个小值,代表你正在做的子搜索的数量,所以一般是O(logN)。

一旦你找到了上界,你真的应该做一个二分查找。应用到您的示例:

  • 初始循环的结果:16→true, 32→false,所以size in (16,32]

二分查找总是测试最大的发现为真和最小的发现为假之间的中间值:

  • 24→false => (16,24)
  • 20 - true => (20,24)
  • 22→false => (20,22)
  • 21→false => (20,21)

所以尺寸是21

注意,这只需要4个额外的测试,而不是你的算法需要7个。

假设向量的大小为N = 31。由于这将是你的算法的最坏情况,这里是它的工作原理:

first pass : 1,2,4,8,16,32            [ total = 6 ]
second pass: 17,18,20,24,32           [ total = 5 ]
third pass : 25,26,28,32              [ total = 4 ]
forth pass : 29,30,32                 [ total = 3 ]
fifth pass : 31                       [ total = 1 ]

如果我们用n来表示,它将是:

T(N) = logN*(1+1/2+1/4+1/8+....)

顺序为:(logN)^2

你应该考虑@celtschk给出的方法,即O(logN)