算法:30~37

第30条:确保目标区间足够大

  • 算法transform:
//transform的实现
template< class InputIt, class OutputIt, class UnaryOperation >
OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first,
                    UnaryOperation unary_op ){
	while (first1 != last1) {
        *d_first++ = unary_op(*first1++); //向迭代器目标位置d_first赋值,需要保证该位置有效;
    }
    return d_first;
}

//实例:向res尾部插入1,4,9,16,25
int f(int x){ return x*x; }

vector<int> v{1,2,3,4,5};
vector<int> res;
transform(v.begin(), v.end(), res.end(), f); //错误,不能向目标位置res.end()赋值,空间不足

transform(v.begin(), v.end(), back_inserter(res), f);//插入迭代器,可以随着插入的进行,增大目标区间 

res.resize(v.size());//提前分配足够的空间保证正确赋值
transform(v.begin(), v.end(), res.begin(), f); 
  • 对于需指定一个目标区间的算法,要保证目标区间足够大,才能在向其写值的时候有效
  1. 要么提前分配足够的空间,目标区间保证足够大,才能向有效的位置写入值而不是妄图向一个容器以外的位置赋值
  2. 要么使用插入型迭代器,保证目标区间随着算法的运行而增大(如ostream_iterator,或由Inserter,back_inserter,front_inserter返回的迭代器);其中back_inserter只适用于提供了push_back的容器vector,string,deque,list; front_inserter只适用于提供了push_front的容器deque,list;

第31条:了解各种与排序有关的选择

  • 各种排序算法:
//功能:对容器元素全排序    
//要求:容器满足随机访问迭代器
sort,及稳定版本stable_sort    注:list有专门的版本list::sort ,forward_list有专门版本forward_list::sort

//功能:只选出容器中满足要求的一部分元素放在容器开头,这部分元素要求有序,剩下的元素无需排序
//要求:容器满足随机访问迭代器
partial_sort

//功能:选出容器中满足要求的一部分元素放在开头,这部分元素也无需排序; 或找到容器中第n个位置的元素
//要求:容器满足随机访问迭代器
nth_element

//功能:按容器元素是否符号条件分为前后两部分;
//要求:容器满足双向迭代器
partition,及稳定版本stable_partition
  • 各种STL容器支持的迭代器类型:
vector,string,deque,array:随机访问迭代器
list:双向迭代器
forward_list:单向迭代器
有序的关联容器:双向迭代器
stack, queue, priority_queue:不支持迭代器,都是容器适配器

第32条:如果确实需要删除元素,则需要在remove这一类算法之后再调用容器的erase成员函数

  • STL中实现删除操作的算法:remove,remove_if,unique
  • STL的算法无法真正删除元素,所作的只是调整元素的位置
  • remove的实例:
vector<int> v{1,2,3,4,5,6,7,8,9,10};
    v[3] = v[5] = v[9] = 99;
    vector<int>::iterator it = remove(v.begin(), v.end(), 99);//remove将要删的元素统一放到容器的后半部分,并返回一个指向第一个要删元素的迭代器
    cout << v.size() << ' '; //10
    for(auto c : v){
        cout << c << ' ';
    }
    cout << endl; //1 2 3 5 7 8 9 8 9 99,实现使用的双指针,因此“被删”的后半部分不一定都是99

    v.erase(it, v.end());//想真正删除99,需要调用容器的成员函数erase
    cout << v.size() << ' ';//7
    for(auto c : v){
        cout << c << ' ';
    }
    cout << endl;//1 2 3 5 7 8 9
  • STL中的list比较特殊,list::remove, list::remove_if, list::unique都是真正删除了容器元素的成员函数函数,并且比其他容器使用的remove-erase, remove_if-erase, unique-erase更高效:
 std::list<int> l = { 1,100,2,2,3,10,1,1,11,-1,-1,-1,12};
 cout << l.size() << endl; //13,list现在的元素为1,100,2,2,3,10,1,1,11,-1,-1,-1,12
 
 l.remove(1); // 真正删除list中所有值为1的元素
 cout << l.size() << endl;//10,删除了三个1,list现在的元素为100,2,2,3,10,11,-1,-1,-1,12
 
 l.remove_if([](int n){ return n > 10; }); // 真正删除list中所有值>10的元素
 cout << l.size() << endl;//7,删除了100,11,12,list现在的元素为2,2,3,10,-1,-1,-1
 
 l.unique(); //将连续的重复元素只留下一个,重复元素被真正删除
 cout << l.size() << endl; //4,list现在的元素为2,3,10,-1
  • 除了list,其它顺序容器想真正删除容器元素需结合remove-erase, remove_if-erase, unique-erase;关联容器直接使用erase成员函数;

第33条:对包含指针的容器使用remove这一类算法要特别小心

在这里插入图片描述

  • 对于存放指向new分配的内存的指针的容器,若要对这种容器使用remove,remove_if,unique等类似的算法,为了防止资源泄露,措施有:
  1. 容器中存放带引用计数的智能指针,如shared_ptr;
    由于容器中每个shared_ptr元素均指向各自动态分配的内存块,即引用计数均为1,当使用remove类的算法时,每当“删除”一个元素时,即这个智能指针会指向别处导致引用计数降为0,因此shared_ptr类会自动释放所指向的内存块;
  2. 笨办法:在对容器调用remove等算法之前,先手动析构那些将要被“删除”的指针所指向的对象并释放其内存;

第34条:了解那些算法要求使用排序的区间作为参数

  • 很多算法需要提供迭代器范围,不同算法对于区间的要求不同:
  1. 若算法要求提供排序区间参数,就必须传给它一个排序的区间
  2. 算法所需要的比较函数,与排序所使用的比较函数必须一致
binary_search默认需要的区间是升序的,则必须提供一个升序区间范围给它,即算法需要什么区间就得给什么
  • 需要提供排序区间的算法:
//要求排序区间
//注:由于使用二分查找,若提供随机访问迭代器,则性能更好
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value); //使用二分查找
ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value );//使用二分查找
bool binary_search( ForwardIt first, ForwardIt last, const T& value );//实现调用了lower_bound();
std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last, const T& value );//实现调用了low_bound()和upper_bound()


OutputIt set_union( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
OutputIt set_intersection( InputIt1 first1, InputIt1 last1,  InputIt2 first2, InputIt2 last2, OutputIt d_first );
OutputIt set_difference(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2,OutputIt d_first);
OutputIt set_symmetric_difference(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first);


OutputIt merge( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
void inplace_merge( BidirIt first, BidirIt middle, BidirIt last );
bool includes( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );

第35条:通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较

  • 借助两种算法mismatch或lexicographical_compare,实现可以忽略英文字母大小写的字符串比较函数;
  • 1.通过mismatch实现:
//mismatch版本1:多一个参数last2,可以不同担心两字符串参数的长短问题;c++14
template<class InputIt1, class InputIt2, class BinaryPredicate>
std::pair<InputIt1, InputIt2>
    mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p)
{
	//只要任一字符串先比较完了,就会跳出,不存在访问出界;
    while (first1 != last1 && first2 != last2 && p(*first1, *first2)) {//比较到第一个使p返回false的位置停止
        ++first1, ++first2;
    }
    return std::make_pair(first1, first2);
}
//mismatch版本2:需要传入的字符串前者不长于后者
template<class InputIt1, class InputIt2, class BinaryPredicate>
std::pair<InputIt1, InputIt2>
    mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, BinaryPredicate p)
{
    while (first1 != last1 && p(*first1, *first2)) {//条件值判断字符串1,因此若字符串1更长,会导致字符串2访问出界
        ++first1, ++first2;
    }
    return std::make_pair(first1, first2);
}
//实现
int ciCharCompare(char c1, char c2){//两个忽略大小写的字符比较:
	int lc1 = tolower(static_cast<unsigned char>(c1));
	int lc2 = tolower(static_cast<unsigned char>(c2));
	//根据两字符的大小返回0,1,-1
	if(lc1 > lc2) return 1;
	else if(lc1 < lc2) return -1;
	else return 0;
}

int ciStringCompareImpl(const string& s1, const string& s2){//做实际比较工作
	typedef pair<string::const_iterator, string::const_iterator> PSCI;
	//ciCharCompare字符匹配时返回0,代表mismatch匹配失败,就会停止;即与mismatch的比较规则刚好相反,需要not2反一下
	PSCI p = mismatch(s1.begin(), s1.end(), s2.begin(), not2(ptr_fun(ciCharCompare)));
	if(p.first == s1.end()){
		if(p.second == s2.end()) return 0;//一样长且对应字符相等,因此s1 == s2
		else -1;//s1是s2的前缀,因此s1 < s2
	}
	else{
		return ciCharCompare(*p.first, *p.second);  //短的s1都未比完就出现了不同,直接调用字符比较函数;
	} 
}
//实现后提供的接口ciStringCompare(s1,s2); 结果:s1 > s2 ,返回1; s1 < s2, 返回-1; s1 == s2,返回0;类似于strcmp
//此处使用mismatch版本2
int ciStringCompare(const string& s1, const string& s2){//提供的接口;此处之确保调用mismatch时传入的字符串前不长于后,将实际比较的工作放入ciStringCompareImpl,
	if(s1.size() > s2.size()) return -ciStringCompareImpl(s2, s1);
	else return ciStringCompareImpl(s1, s2);
}
  • 2.通过lexicographical_compare实现:
//lexicographical_compar的e实现
template<class InputIt1, class InputIt2, class Compare>
bool lexicographical_compare(InputIt1 first1, InputIt1 last1,
                             InputIt2 first2, InputIt2 last2,
                             Compare comp)
{
    for ( ; (first1 != last1) && (first2 != last2); ++first1, (void) ++first2 ) {
        if (comp(*first1, *first2)) return true;
        if (comp(*first2, *first1)) return false;
    }
    return (first1 == last1) && (first2 != last2);
}
//实现
bool ciCharLess(char c1, char c2){
	return tolower(static_cast<unsigned char>(c1)) < tolower(static_cast<unsigned char>(c2));
}
//提供的最终接口ciStringCompare(s1, s2); 结果:若s1 < s2, 返回true; s1 >= s2,返回false;
bool ciStringCompare(const string& s1, const string& s2){
	return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ciCharLess);
}
  • 3.通过调用非标准c库的函数stricmp或strcmpi实现,实际未提供此函数;
#include <string.h>
extern int stricmp(char *s1,char * s2);//需要的参数是char*而不是string
当s1=s2时,返回值=0
当s1<s2时,返回值<0,但不一定是-1
当s1>s2时,返回值>0,但不一定是1
int ciStringCompare(const string& s1, const string& s2){
	return stricmp(s1.c_str(), s2.c_str());//将string转成char*
}
  • best practice:
    前两种算法实现使用的是标准STL算法,但速度不如第三种;
    stricmp或strcmpi速度快;但是非标准c库的函数,因此可移植性不强;传入的参数是char*而不是string;

第36条:理解copy_if算法的正确实现

  • 实现1:
//两种形式
bool isDefective(const Widget& w);
struct isDefective{
	bool operator()(const Widget& w){
		...
	} 
};

//通过remove_copy_if + not1实现
template<class InputIt, class OutputIt, typename UnaryPredicate>
OutputIt copy_if(InputIt begin, InputIt end, OutputIt d_first, UnaryPredicate p){
	return remove_copy_if(begin, end, d_first, not1(ptr_fun(p)));//p是函数指针,因此需要ptr_fun转化,若直接是仿函数则不用
}
  • 实现2(最优):
template<class InputIt, class OutputIt, typename UnaryPredicate>
OutputIt copy_if(InputIt begin, InputIt end, OutputIt d_first, UnaryPredicate p){
	while(begin != end){
		if(p(*begin)) *d_first++ = *first++;
	}
}

第37条:使用accumulate或者for_each进行区间统计

  • accumulate的实现:
    注:传入的第三参数表示累计的初始值,最后得到的类型也是T(因此可以直接得到最终的平均值点),因此往往T和迭代器区间的元素类型相同;
    第三参数在遍历迭代器返回这个过程中是重复用的(注意体会这点);
    (若指定的话)第三参数的谓词是二元的
//实现1:
template<class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init)//默认使用operator+累加
{
    for (; first != last; ++first) {
        init = std::move(init) + *first; 
    }
    return init;
}
//实现2
template<class InputIt, class T, class BinaryOperation>
T accumulate(InputIt first, InputIt last, T init,  BinaryOperation op)//指定累加仿函数op
{
    for (; first != last; ++first) {
        init = op(std::move(init), *first); // 每次累计,将和赋给init,且返回类型也是T
    }
    return init;
}
  • for_each的实现:
    注:谓词f是一元的;且返回的结果是一个函数对象
template<class InputIt, class UnaryFunction>
constexpr UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
    for (; first != last; ++first) {
        f(*first);
    }
    return f; // 
}
  • 例1. 求区间和
list<double> ld;
...
double sum = accumulate(ld.begin(), ld.end(), 0.0);//初始值必须是0.0不能是0,否则最后求和是int类型而不是doubel
  • 例2:求容器中string元素的长度和
  • 对于标准容器,container::size_type 等价于 size_t
string::size_type stringLengthSum(string::size_type sumSoFar, const string& s){
	return sumSoFar + s.size();
}

set<string> ss;
...
string::size_type lengthSum = accumulate(ss.begin(), ss.end(), static_cast<string::size_type>(0), stringLengthSum);//同理,初始值类型必须是string::size_type,才能满足stringLengthSum的参数
  • 例3:计算区间中数值的乘积(有直接用的函数子类模板multiplies)
vector<float> vf;
...
float res = accumulate(vf.begin(), vf.end(), 1.0f, multiplies<float>());
  • 例4:求一些点的平均值
struct point{//点的定义
	point(double initX, double initY) : x(initX), y(initY){} 
	double x, y;
};
方法1:使用accumulate
//重点在于不断更新三个private变量,时刻都能得到遍历过的点的平均值
class PointAvg : public binary_function<point, point, point>{
private:
	size_t numPoints;
	double sumX;
	double sumY;
public:
	PointAvg() : sumX(0), sumY(0), numPoints(0){} //初始化三个变量
	const point operator()(const point& a, const point& b){ //只更新三个变量,并不做其他事情,a其实没用上
		++numPoints;
		sumX += b.x;
		sumY += b.y;
		return point(sumX / numPoints, sumY / numPoints);//时刻得到的都是遍历过的点的平均值
	}
};

list<ponit> lp;
...
point avg = accumulate(lp.begin(), lp.end(), point(0.0, 0.0), PointAvg());//初始值point(0.0, 0.0)实际没用上
方法2:使用for_each
//正因为for_each返回的是个函数对象,因此需要提供个接口才能得到平均值点;这点不如accumulate
class PointAvg : public unary_function<>{
private:
	size_t numPoints;
	double sumX;
	double sumY;
public:
	PointAvg() : sumX(0), sumY(0), numPoints(0){} //初始化三个变量
	void operator()(const point& a){
		numPoints++;
		sumX += a.x;
		sumY += a.y;
	}
	point result()const{ //接口
		return point(sumX / numPoints, sumY / numPoints);
	}
};

list<ponit> lp;
...
point avg = for_each(lp.begin(), lp.end(), PointAvg().result());
  • 总结:实际上这两种方法区别就在于算法实现的不同:accumulate和for_each接受的谓词一个一元一个二元,且返回类型也不同;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值