【问题标题】:Time complexity of an iterative algorithm迭代算法的时间复杂度
【发布时间】:2016-12-11 13:54:25
【问题描述】:

我试图找出这个algorithm 的时间复杂度。

迭代:算法从输入位串生成给定汉明距离内的所有位串。它生成所有递增序列0 <= a[0] < ... < a[dist-1] < strlen(num),并在相应索引处恢复位。

向量a 应该保留必须反转位的索引。因此,如果 a 包含当前索引 i,我们将打印 1 而不是 0,反之亦然。否则我们按原样打印该位(参见else-part),如下所示:

// e.g. hamming("0000", 2);
void hamming(const char* num, size_t dist) {
    assert(dist > 0);
    vector<int> a(dist);
    size_t k = 0, n = strlen(num);
    a[k] = -1;
    while (true)
        if (++a[k] >= n)
            if (k == 0)
                return;
            else {
                --k;
                continue;
            }
        else
            if (k == dist - 1) {
                // this is an O(n) operation and will be called
                // (n choose dist) times, in total.
                print(num, a);
            }
            else {
                a[k+1] = a[k];
                ++k;
            }
}

这个算法的时间复杂度是多少?


我的尝试说:

dist * n + (n 选择 t) * n + 2

但这似乎不是真的,请考虑以下示例,所有示例都使用 dist = 2:

len = 3, (3 choose 2) = 3 * O(n), 10 while iterations
len = 4, (4 choose 2) = 6 * O(n), 15 while iterations
len = 5, (5 choose 2) = 9 * O(n), 21 while iterations
len = 6, (6 choose 2) = 15 * O(n), 28 while iterations

这是两个有代表性的运行(打印发生在循环的开始):

000, len = 3
k = 0, total_iter = 1
vector a = -1 0 
k = 1, total_iter = 2
vector a = 0 0 
Paid O(n)
k = 1, total_iter = 3
vector a = 0 1 
Paid O(n)
k = 1, total_iter = 4
vector a = 0 2 
k = 0, total_iter = 5
vector a = 0 3 
k = 1, total_iter = 6
vector a = 1 1 
Paid O(n)
k = 1, total_iter = 7
vector a = 1 2 
k = 0, total_iter = 8
vector a = 1 3 
k = 1, total_iter = 9
vector a = 2 2 
k = 0, total_iter = 10
vector a = 2 3 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gsamaras@pythagoras:~/Desktop/generate_bitStrings_HammDistanceT$ ./iter
0000, len = 4
k = 0, total_iter = 1
vector a = -1 0 
k = 1, total_iter = 2
vector a = 0 0 
Paid O(n)
k = 1, total_iter = 3
vector a = 0 1 
Paid O(n)
k = 1, total_iter = 4
vector a = 0 2 
Paid O(n)
k = 1, total_iter = 5
vector a = 0 3 
k = 0, total_iter = 6
vector a = 0 4 
k = 1, total_iter = 7
vector a = 1 1 
Paid O(n)
k = 1, total_iter = 8
vector a = 1 2 
Paid O(n)
k = 1, total_iter = 9
vector a = 1 3 
k = 0, total_iter = 10
vector a = 1 4 
k = 1, total_iter = 11
vector a = 2 2 
Paid O(n)
k = 1, total_iter = 12
vector a = 2 3 
k = 0, total_iter = 13
vector a = 2 4 
k = 1, total_iter = 14
vector a = 3 3 
k = 0, total_iter = 15
vector a = 3 4 

【问题讨论】:

    标签: c++ algorithm loops bit-manipulation time-complexity


    【解决方案1】:

    while 循环有点聪明和微妙,可以说它做了两件不同的事情(如果算上a 的初始化,甚至是三件)。这就是让您的复杂性计算变得具有挑战性的原因,而且它的效率也低于预期。

    在摘要中,要从当前索引增量计算下一组索引,其想法是找到小于n-dist+i 的最后一个索引i,将其递增,并将以下索引设置为@ 987654325@、a[i]+2等。

    例如,如果 dist=5, n=11 并且您的索引是:

    0, 3, 5, 9, 10
    

    那么5是最后一个小于n-dist+i的值(因为n-dist是6,并且10=6+4,9=6+3,但是5

    所以我们递增5,并设置后续整数得到索引集:

    0, 3, 6, 7, 8
    

    现在考虑你的代码是如何运行的,假设k=4

    0, 3, 5, 9, 10
    
    • a[k] + 1 是 11,所以 k 变成 3。
    • ++a[k] 是 10,所以 a[k+1] 变成 10,k 变成 4。
    • ++a[k] 是 11,所以 k 变成 3。
    • ++a[k] 是 11,所以 k 变成 2。
    • ++a[k] 是 6,所以 a[k+1] 变成 6,k 变成 3。
    • ++a[k] 是 7,所以 a[k+1] 变成 7,k 变成 4。
    • ++a[k] 是 8,我们继续调用 print 函数。

    这段代码是正确的,但效率不高,因为k 在搜索可以递增而不会导致较高索引溢出的最高索引时会前后移动。实际上,如果从末尾开始最高索引为j,则代码使用了while循环的非线性次数迭代。如果您跟踪当n==distn 的不同值时发生了多少次while 循环,您可以自己轻松地演示这一点。只有一行输出,但您会看到迭代次数增加了 O(2^n)(实际上,您会看到 2^(n+1)-2 次迭代)。

    这种破坏使您的代码不必要地低效,并且也难以分析。

    相反,您可以用更直接的方式编写代码:

    void hamming2(const char* num, size_t dist) {
        int a[dist];
        for (int i = 0; i < dist; i++) {
            a[i] = i;
        }
        size_t n = strlen(num);
        while (true) {
            print(num, a);
            int i;
            for (i = dist - 1; i >= 0; i--) {
                if (a[i] < n - dist + i) break;
            }
            if (i < 0) return;
            a[i]++;
            for (int j = i+1; j<dist; j++) a[j] = a[i] + j - i;
        }
    }
    

    现在,每次通过 while 循环都会产生一组新的索引。每次迭代的确切成本并不简单,但由于print 为 O(n),而 while 循环中的剩余代码最坏情况下为 O(dist),因此总成本为 O(N_INCR_SEQ(n, dist) * n ),其中 N_INCR_SEQ(n, dist) 是长度为 dist 的自然数

    【讨论】:

    • +1。回复:“N_INCR_SEQ(n, dist) 是自然数的递增序列的数量 n choose 距离)。
    • 顺便说一句,您的分析的一个含义是,OP 可以通过将if (++a[k] &gt;= n) 更改为if (++a[k] &gt; n - dist + k) 来最低限度地修复他/她的代码的性能。这将消除所有不必要的/非生产性的来回传递。
    • 确实是@ruakh,但我更喜欢 Paul 的代码,在我年轻的眼中看起来更优雅!所以总体时间复杂度为 O((n choose dist) * n)...
    • @gsamaras:是的,我也更喜欢 Paul 的代码。 (哦,除了 int a[dist] 不是有效的 C++;C 具有可变长度数组,因此一些编译器在 C++ 中支持它们以及编译器扩展,但它们不是 C++ 标准的一部分。所以我认为你改用std::vector&lt;int&gt; 是对的。)
    • @AlexD hamming("&lt;40 zeros&gt;", 40) 表明优化很重要。我的代码版本实际上和你做的工作量相同(假设 ruakh 在上面的第二条评论中进行了更改以消除过度的破坏)。我的每次迭代做更多的工作,但你可以在打印语句之间做很多次迭代。两个版本的代码完全平衡,因为它们做的事情基本相同。
    【解决方案2】:

    注意,给定n 代表长度,t 代表所需距离,1n(或索引形式,在0n-1 之间)确实是n choose t,因为我们选择了t 不同的索引。

    问题出现在你这一代的那些系列中:

    -首先,请注意,例如在长度为 4 的情况下,您实际上会遍历 5 个不同的索引,从 0 到 4。

    -其次,请注意您正在考虑具有相同索引的帐户系列(在t=2 的情况下,它的0 0, 1 1, 2 2 等等),通常,您会经历每个非递减 系列,而不是通过每个增加系列。

    因此,在计算程序的 TC 时,请确保将其考虑在内。

    提示:尝试从这些级数的全域与某个方程的整数解的全域进行一一对应。

    如果您需要直接解决方案,请查看此处: https://math.stackexchange.com/questions/432496/number-of-non-decreasing-sequences-of-length-m


    最终的解决方案是(n+t-1) choose (t),但请注意程序中的第一个项目符号,它实际上是((n+1)+t-1) choose (t),因为您循环使用了一个额外的索引。 表示

    ((n+1)+t-1) choose (t) =: A , n choose t =: B

    总的来说,我们得到O(1) + B*O(n) + (A-B)*O(1)

    【讨论】:

    • 我不认为你的“第二”段是正确的。请注意,while 循环以无条件递增 a[k] 开始。 (由于某种原因,这被塞进if-test 混淆了;但它就在那里。)
    • @ruakh 查看他的运行示例,我认为它确实会遍历每个非递减序列。我错了吗?
    • 他/她的运行示例显示了在每次循环迭代的开始处向量的内容;但向量在循环迭代期间被修改。但是,这很复杂。向量的内容永远不能像1 1 1(相同的值重复3次),但它们可以暂时像1 1 41 2 2
    • 所以,总的来说,你也同意我的算法比 Paul 提供的算法慢,谢谢,+1。
    猜你喜欢
    • 2015-03-09
    • 2021-11-15
    • 2019-01-26
    • 2023-03-24
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 2012-05-09
    相关资源
    最近更新 更多