一篇讨论std::list与binary_search的文章:
Why std::binary_search of std::list Works,But You Shouldn’t Use It!
如文中所述,STL中二分搜索的大概实现是这样的(以lower_bound
为例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
template <class ForwardIterator, class T>
ForwardIterator lower_bound(ForwardIterator first,
ForwardIterator last, const T& value)
{
typedef typename iterator_traits<ForwardIterator>::difference_type difference_type;
difference_type len = distance(first, last);
while (len > 0)
{
ForwardIterator i = first;
difference_type len2 = len / 2;
advance(i, len2);
if (*i < value)
{
first = ++i;
len -= len2 + 1;
}
else
len = len2;
}
return first;
}
|
算法的主要耗时操作是distance、advance和比较操作。
对于比较操作,无论 vector 还是 list ,线性搜索的最坏时间复杂度都是O(n),二分搜索为O(logn);
而distance操作,对于 vector 这类容器支持随机访问的迭代器,只是类似指针加减一样的算术操作,时间复杂度为O(1), 而 list 不支持随机访问,其distance操作,是从到到尾逐一访问、累加长度,时间复杂度为O(n);
advance操作类似distance,对于链表其时间复杂度为一次循环中搜索区间长度的一半,而这样的advance操作多达log(n)次。
可以看出来,对于链表来说,二分搜索的比较操作要比线性搜索少(O(logn) vs O(n) )。
但对链表元素的访问操作却要比普通的线性搜索多出来很多。
一个实验
一段用来比较std::list之上线性搜索与二分搜索效率的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#include <sys/time.h>
#include <list>
#include <iostream>
#include <algorithm>
#define BENCHMARK_COUNT (5000*10000)
void print_elapse(timeval& start, timeval& end)
{
timeval res;
timersub(&end, &start, &res);
std::cout << res.tv_sec << "s " <<
res.tv_usec / 1000 << "ms." << std::endl;
}
void compare_search(const std::list<int>& lst, int target)
{
std::cout << "target: " << target << std::endl;
timeval start, end;
gettimeofday(&start, NULL);
binary_search(lst.begin(), lst.end(), target);
gettimeofday(&end, NULL);
std::cout << "binary search: ";
print_elapse(start, end);
gettimeofday(&start, NULL);
for (std::list<int>::const_iterator it = lst.begin();
it != lst.end(); ++it) {
if (*it == target) break;
}
gettimeofday(&end, NULL);
std::cout << "linear search: ";
print_elapse(start, end);
std::cout << std::endl;
}
int main(int argc, char *argv[])
{
std::list<int> lst;
for (int i = 0; i < BENCHMARK_COUNT; ++i)
lst.push_back(i);
compare_search(lst, BENCHMARK_COUNT - 1);
compare_search(lst, BENCHMARK_COUNT / 2);
compare_search(lst, BENCHMARK_COUNT / 5);
return 0;
}
|
编译,优化级别为O2,运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
|
# ./a.out
target: 49999999
binary search: 0s 463ms.
linear search: 0s 178ms.
target: 25000000
binary search: 0s 326ms.
linear search: 0s 104ms.
target: 10000000
binary search: 0s 335ms.
linear search: 0s 29ms.
|
可以看出即使是在线性搜索的最坏情况下(目标为最后一个元素或者不存在),
链表的线性搜索也要比二分搜索快得多!
究其原因,除了上述复杂度的分析外,顺序查找对缓存更为友好这也是很重要的一方面。