“选择了错误的算法,便注定了失败的命运”。最近对这句话感触颇深,经常因为一开始思路错误,修改半天,到头来却都是无用功,所以学好算法势在必行。
算法的泛化过程
如何设计一个算法,使他适用于任何(大多数)数据结构呢?先看一个算法泛华的实例。
假设我们要写一个find()函数,在array中寻找特定值。面对整数array,我们很快能写出:
int *find(int *array,int size,int target) { for(int i=0;i<size;i++) { if(array[i]==target) break; } return &(array[i]); }
该函数在某个区间内寻找target。返回的是一个指针,只想它所找到的第一个符合条件的元素;如果没有找到,就返回最后一个元素的下一个地址。“最后一个元素的下一个地址”称为end。返回end就表示查找无结果。
现在可以这样使用find()函数:
const int size=7; int array[size]={0,1,2,3,4,5,6}; int *end=array+size; //最后一个元素位置 int *ans=find(array,sizeof(array)/sizeof(int),4); if(ans==end) cout<<"4 not found"<<endl; else cout<<"4 found。 "<<*ans<<endl;
但是,find()暴露了容器太多的实现细节,也因此太依附于容器。为了让find()试用于所有类型的容器,其操作应该更抽象化。让find()接受两个指针作为参数,标出一个操作区间:
int *find(int *begin,int *end,int value) { while(begin!=end&&*begin!=value) begin++; return begin; }
由于find()函数之内并无任何操作时针对特定的整数array,所以我们可以将它改为一个template:
template<class T> T *find(T *begin,T *end,T& value) { while(begin!=end&&*begin!=value) begin++; return begin; }
这样的find()几乎适用于任何容器。但对于list呢?还是不够。
在C++中,上述操作符或操作行为都可以被重载。如果我们以一个原生指针指向某个list,则对该指针进行“++”操作并不能指向下一个串行节点,但如果哦我们设计一个class,拥有原生指针的行为,并使其”++“操作指向list的下一个节点,那么find()就可以施行于list容器上了。这便是迭代器(iterator)的观念。迭代器是一种行为类似指针的对象,下面将find()函数内的指针以迭代器取代:
template<class Iterator,class T> Iterator *find(Iterator *begin,Iterator *end,const T& value) { while(begin!=end&&*begin!=value) begin++; return begin; }
这便是一个完全泛型化的find()函数,有了这样的观念,再看STL的各式各样的泛型算法,就轻松多了。
参考:《STL源码剖析》