簡體   English   中英

為什么 STL 的 next_permutation 實現不使用二分查找?

[英]Why doesn't STL's implementation of next_permutation use the binary search?

在閱讀了std::next_permutation Implementation Explanation的優秀答案后,我提出了這個問題。 請參閱該帖子以了解 STL 使用的算法的說明,但我將在此處復制代碼以供您參考

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename It>
bool next_permutation(It begin, It end)
{
    if (begin == end)
        return false;

    It i = begin;
    ++i;
    if (i == end)
        return false;

    i = end;
    --i;

    while (true)
    {
        It j = i;
        --i;

        if (*i < *j)
        {
            It k = end;

            while (!(*i < *--k))
                /* pass */;

            iter_swap(i, k);
            reverse(j, end);
            return true;
        }

        if (i == begin)
        {
            reverse(begin, end);
            return false;
        }
    }
}

int main()
{
    vector<int> v = { 1, 2, 3, 4 };

    do
    {
        for (int i = 0; i < 4; i++)
        {
            cout << v[i] << " ";
        }
        cout << endl;
    }
    while (::next_permutation(v.begin(), v.end()));
}

我們來看看這部分

It k = end;

while (!(*i < *--k))
    /* pass */;

iter_swap(i, k);

根據我的理解,這種線性掃描可以用二分搜索代替,因為通過構造i之后的元素已經是降序(或者在重復元素的情況下,非升序)。 假設我們有一個布爾數組,其項為*idx > *i對於每個j <= idx < end ,那么我需要做的就是找到項為True的最大索引。 這樣的索引必須存在,因為我們有*j > *i ,這意味着數組以True開頭。

我對 C++ 的了解不足以自信地提供一個工作示例,但這里是 Rust 中next_permutation的完整實現。 如果您不了解 Rust,那么下面的偽代碼應該可以很好地理解我所說的“二分搜索”是什么意思。 (嗯,是的,它是 Python,它的可讀性足以被稱為偽代碼 :)

from typing import List

def bisearch(last: List[bool]) -> int:
    p, q = 0, len(lst) - 1
    while p + 1 < q:
        mid = (p + q) // 2
        if lst[mid]:
            p = mid
        else:
            q = mid

    return q if lst[q] else q - 1


if __name__ == '__main__':
    for pos_count in range(1, 5):
        for neg_count in range(5):
            lst = [True] * pos_count + [False] * neg_count
            assert bisearch(lst) == pos_count - 1

問題:為什么 STL 的next_permutation實現不使用二分查找? 我知道找到i需要O(n) ,並且O(n) + O(n)O(n) + O(ln(n))都是O(n) ,但實際上二進制搜索應該仍然在最低限度地提高性能?

正如@RichardCritten 指出的那樣,僅僅因為我們有更好的算法復雜性並不意味着執行速度更快。 此外,實現有點復雜。

下面,我們對中間部分的原始算法進行了非常簡單的改動。

原始代碼:

if (*i < *j)
{
    It k = end;
    
    while (!(*i < *--k))
        /* pass */;
    
    iter_swap(i, k);
    reverse(j, end);
    return true;
}

簡單的二進制方法

if (*i < *j)
{
    auto test = std::lower_bound(std::make_reverse_iterator(end),
                                 std::make_reverse_iterator(i), *i);
    
    std::iter_swap(i, test);
    std::reverse(j, end);
    return true;
}

我們使用std::lower_bound以及std::make_reverse_iterator因為給定的范圍是降序的。

應該注意的是,這種實現不是完全證明,並且在重復時不起作用。 要點之一是證明即使對於簡單的情況,原始實現也更快。

這是一個演示每種方法的速度的實時 ideone 示例 在我們的測試中,我們測量每種方法生成10! = 3,628,800 10! = 3,628,800次排列 100 次。

您會注意到線性實現的速度幾乎是其兩倍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM