[英]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.