一、查找算法
无论是在现实世界还是在计算机的虚拟世界中,对一个物品或者说对象的查找搜索,都是广泛出现的。比如我们在众多的苹果中寻找一个最大的,在很多的道路中查找出一个最近的。而这些映射到计算机世界中,就形成了抽象的查找算法,比如在编译器编译源码时会寻找一些TOKEN。查找的算法有很多类型,可以把相关的数据排序后查找,也可以不排序查找。可以只查找,不管理数据;也可以边查找边增加或者删除。
正如前面排序,查找也可以用复杂度来描述,主要包括两个方面:
1、平均查找长度:ASL(Average Search Length),查找运算中,因为需要耗费时间在关键字的比较上,故把平均需要和待查找值比较的关键字次数称为平均查找长度。
2、查找时间复杂度:简单的说就是需要的时间的量化表示。
主要包括:
1、顺序查找:非常好理解,一个个来,直到找到自己的需要的。
2、二分查找:也称折半查找,就是排序后不断的再次折半来查找具体目标。
3、插值查找:二分查找的进一步推广,将中间点的选择改为动态判定。
4、斐波那契查找:二分查找的一种推广,基于黄金分割点。
5、树表查找:利用树的特点为来查找
6、分块查找:基于分治的思想进一步推进查找的方法。
7、哈希查找:通过哈希算法来查找。
二、STL算法中的查找分类
STL中对查找分为两大类:
一类为已排序容器中进行查找,如:binary_search
另外一类为非排序容器中进行查找,如:find,find_if
从查找内容上,可以分为:
查找单个元素,如:find,min_element,find_first_of
查找区间元素,如:search,lower_bound,equal_range
查找条件元素,如:find_if,equal,count,value_comp,key_comp
更多请看:
三、源码分析
这里对典型的查找算法源码进行一下分析:
//find
template<class _InIt,
class _Ty>
_NODISCARD inline _InIt find(_InIt _First, const _InIt _Last, const _Ty& _Val)
{ // find first matching _Val
_Adl_verify_range(_First, _Last);
_Seek_wrapped(_First,
_Find_unchecked(_Get_unwrapped(_First), _Get_unwrapped(_Last), _Val));
return (_First);
}
template<class _InIt,
class _Ty> inline
_InIt _Find_unchecked1(_InIt _First, const _InIt _Last, const _Ty& _Val, false_type)
{ // find first matching _Val 源码就是个迭代遍历
for (; _First != _Last; ++_First)
if (*_First == _Val)
break;
return (_First);
}
//二分法查找
template<class _FwdIt,
class _Ty,
class _Pr>
_NODISCARD inline bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred)
{ // test if _Val equivalent to some element, using _Pred
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_UFirst = _STD lower_bound(_UFirst, _ULast, _Val, _Pass_fn(_Pred));
return (_UFirst != _ULast && !_Pred(_Val, *_UFirst));
}
template<class _FwdIt,
class _Ty,
class _Pr>
_NODISCARD inline _FwdIt lower_bound(_FwdIt _First, const _FwdIt _Last,
const _Ty& _Val, _Pr _Pred)
{ // find first element not before _Val, using _Pred
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
_Iter_diff_t<_FwdIt> _Count = _STD distance(_UFirst, _Get_unwrapped(_Last));
while (0 < _Count)
{ // divide and conquer, find half that contains answer 从一半中查找
const _Iter_diff_t<_FwdIt> _Count2 = _Count >> 1; // TRANSITION, VSO#433486
const auto _UMid = _STD next(_UFirst, _Count2);
if (_Pred(*_UMid, _Val))
{ // try top half
_UFirst = _Next_iter(_UMid);
_Count -= _Count2 + 1;
}
else
{
_Count = _Count2;
}
}
_Seek_wrapped(_First, _UFirst);
return (_First);
}
看上面的代码发现如果除去一些复杂的定义和模板的判别,其实是非常简单的。所以说简单才是美。这是每个Coder的追求才对。
四、实例
看一下几个相关的实例(cppreference.com):
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
int main()
{
std::vector<int> v{1, 2, 3, 4};
int n1 = 3;
int n2 = 5;
auto is_even = [](int i){ return i%2 == 0; };
auto result1 = std::find(begin(v), end(v), n1);
auto result2 = std::find(begin(v), end(v), n2);
auto result3 = std::find_if(begin(v), end(v), is_even);
(result1 != std::end(v))
? std::cout << "v contains " << n1 << '\n'
: std::cout << "v does not contain " << n1 << '\n';
(result2 != std::end(v))
? std::cout << "v contains " << n2 << '\n'
: std::cout << "v does not contain " << n2 << '\n';
(result3 != std::end(v))
? std::cout << "v contains an even number: " << *result3 << '\n'
: std::cout << "v does not contain even numbers\n";
}
输出结果:
v contains 3
v does not contain 5
v contains an even number: 2
#include <algorithm>
#include <vector>
#include <iostream>
struct S
{
int number;
char name;
// note: name is ignored by this comparison operator
bool operator< ( const S& s ) const { return number < s.number; }
};
int main()
{
// note: not ordered, only partitioned w.r.t. S defined below
const std::vector<S> vec = { {1,'A'}, {2,'B'}, {2,'C'}, {2,'D'}, {4,'G'}, {3,'F'} };
const S value = {2, '?'};
std::cout << "Compare using S::operator<(): ";
const auto p = std::equal_range(vec.begin(), vec.end(), value);
for ( auto i = p.first; i != p.second; ++i )
std::cout << i->name << ' ';
std::cout << "\n" "Using heterogeneous comparison: ";
struct Comp
{
bool operator() ( const S& s, int i ) const { return s.number < i; }
bool operator() ( int i, const S& s ) const { return i < s.number; }
};
const auto p2 = std::equal_range(vec.begin(),vec.end(), 2, Comp{});
for ( auto i = p2.first; i != p2.second; ++i )
std::cout << i->name << ' ';
}
输出结果:
Compare using S::operator<(): B C D
Using heterogeneous comparison: B C D
#include <iostream>
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> haystack {1, 3, 4, 5, 9};
std::vector<int> needles {1, 2, 3};
for (auto needle : needles) {
std::cout << "Searching for " << needle << '\n';
if (std::binary_search(haystack.begin(), haystack.end(), needle)) {
std::cout << "Found " << needle << '\n';
} else {
std::cout << "no dice!\n";
}
}
}
运行结果:
Searching for 1
Found 1
Searching for 2
no dice!
Searching for 3
Found 3
由此可见,STL的目标仍然是把c++的编程难度门槛降低然后提高便捷性。在网站上还有更多的例程和相关的说明,请大家自行查阅!
五、总结
无论是在查找还是其它算法中,STL都会提供一个接近平衡的算法来处理相关的元素,这也意味着在某些情况下,可能效率会有不足,此时可以使用提供自定义算法的接口来实现更好的算法实现。但是一般到这种地步,更多的是完全手撸代码了,STL反而不是问题。从STL的源码中可以学习到什么?看到什么设计思想?能不能为我所用,这才是学习STL和其相关源码的主要目的。
切中肯綮!方可游刃有余!