泛型算法

       标准库并未给每个容器添加大量功能,而是提供了一组算法,它们实现了一些经典算法的公共接口,如排序和搜索。这些算法中大多独立于任何特定容器,它们可以用于不同类型的元素和多种容器类型,所以称之为泛型算法,大多数算法定义在#include <algorithm> 中,标准库还在头文件#include <numeric> 中定义了一组数值泛型算法,一般情况下这些算法并不直接操作容器,而是遍历由俩个迭代器指定的一个元素范围来进行操作,例如使用标准库算法find来搜索

int val = 42;   //将要查找的值
vector<int> v{ 1,2,3,4,44,55,66,42 };
auto result = find(v.cbegin(), v.cend(), val);
if (result == v.cend())   //用于判断是否此范围内是否存在此数
    cout << "not exist" << endl;
else
    cout << *result << " is exist" << endl;

find (迭代器1,迭代器2,将要搜索的值);
       传递给find的前俩个参数是表示元素范围的迭代器,第三个参数时一个将要搜索的值,find将范围中每个元素与给定值进行比较,它返回第一个等于给定值的元素的迭代器,如果范围中无匹配元素,则find返回第二个参数来表示搜索失败,因此,我们可以通过比较返回值和第二个参数来判断搜索是否成功,find也可以用作于内置数组上,但是需要注意内置数组的迭代器有些不同。

int ia[] = { 27,210,12,34,54,33 };
auto result2 = find(begin(ia), end(ia), val);
if (result2 == end(ia))
    cout << "not exist" << endl;
else
    cout << *result2 << " is exist" << endl;
也可以在序列的子范围中查找,只需将指向子范围首元素和尾元素之后位置的迭代器(指针)传递给find
//在从ia[1]开始,直至(但不包含)ia[4]的范围内查找元素
auto result=find(ia+1,ia+4,val);

       泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作,这个特性带来了一个假定:算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素,标准库定义了一类特殊的迭代器,称为插入器(inserter),与普通迭代器只能遍历所绑定的容器相比,它们会在底层容器上执行插入操作,因此当一个算法操作这样一个迭代器时,迭代器可以完成向容器添加元素的效果,但算法自身永远不会做这样的操作。

  • 求和算法accumulate

一些算法只会读取输入范围内的元素,而不改变元素,例如find,accumulate也是只读算法在定义在#include <numeric> 中,
accumulate(迭代器1,迭代器2,和的初值);  前俩个参数为求和的元素范围,第三个参数时和的初值

vector<int> v{ 11,11,11,11 };
int sum = accumulate(v.cbegin(), v.cend(), 100);
cout << sum << endl;  //144

int a[]{ 22,22,22,22 };
int sum2 = accumulate(begin(a), end(a), 100);
cout << sum2 << endl; //188 

        accumulate将第三个参数作为求和起点,这蕴含着一个编程假定:将元素类型加到和的类型上的操作必须是可行的,即序列中元素的类型必须与第三个参数匹配,或者能够转换为第三个参数的类型,上例中vector中元素可以是int、double、long long或者其他可以加到int上的类型,string也定义了+运算符,所以也可以进行accumulate。

vector<string> v{ "aa","bb","cc","dd" };
string s = "pp";
string sum = accumulate(v.cbegin(), v.cend(),s);
cout << sum << endl;  //ppaabbccdd

注意第三个参数是利用事先定义好的string来填充,如果用的是字符串字面值则会导致编译出错

vector<string> v{ "aa","bb","cc","dd" };
string sum = accumulate(v.cbegin(), v.cend(), "pp");
cout << sum << endl;  //编译出错  const char*上没有定义+运算符

原因在于,我们传递了一个字符串字面值,用于保存和的对象的类型将是const char*,又因为此类型决定了使用哪个+运算符,由于const char*并没有+运算符,所以此调用将产生编译错误

  • equal算法

       另一个只读算法是equal,永不确定俩个序列是否保存相同的值,它将第一个序列中的每个元素与第二个序列中的对应元素进行比较,如果所有对应元素都相等,则返回true,否则返回false,此算法参数接受三个迭代器
equal(迭代器1,迭代器2,迭代器3) ,前俩个表示第一个序列中的元素范围,第三个表示第二个序列的首元素,由于equal利用迭代器完成操作,因此我们可以通过调用equal来比较俩个不同类型的容器中的元素,甚至元素类型也不必一样,只要我们能用==来比较俩个元素类型即可,比如vector<string>和list<const char*>。

vector<string> v3{ "aa","bb","cc","dd" };
list<const char*> l{ "aa","bb","cc","dd" };
if (equal(v3.cbegin(), v3.cend(), l.cbegin()))
	cout << "==" << endl;  //==
else
	cout << "!=" << endl;

但是equal是基于一个非常重要的假设它假定第二个序列至少与第一个序列一样长,如果第二个序列小于第一个序列,则会报错,即使第二个序列包含的元素比第一个序列多,但只要序列1中的元素在序列2都存在,且位置相同,则就会视为相等。那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长

vector<string> v{ "aa","bb","cc","dd" };
vector<string> v2{ "aa","bb","cc" ,"ee"};  //不相等
vector<string> v3{ "aa","bb","cc" };   //报错
vector<string> v4{ "aa","bb","cc" ,"dd","ee" };  //相等
vector<string> v4{ "aa","ee","cc" ,"dd","bb" };  //不相等
if (equal(v.cbegin(), v.cend(), v4.cbegin()))
	cout << "==" << endl;
else
	cout << "!=" << endl;

  • fill和fill_n算法

       一些算法将新值赋予序列中的元素,当我们使用这类算法时,必须主要确保序列原大小至少不小于我们要求算法写入的元素数目,算法fill接受俩个迭代器表示一个范围,还接受一个值作为第三个参数,fill将给定的这个值赋予输入序列中的每个元素。
fill (迭代器1,迭代器2,数值),由于fill向给定输入序列中写入数据,因此只要我们传递了一个有效的输入序列,写入操作就是安全的。

vector<int> v{ 1,2,3,4 };
fill(v.begin(), v.end(), 0);
for (auto i : v)
	cout << i << endl;   // 0 0 0 0

算法不检查写操作,使用时需要格外注意:

        一些算法接受一个迭代器来指出一个单独的目的位置,这些算法将新值赋予一个序列中的元素,该序列从目的位置迭代器指向的元素开始,例如函数fill_n接受一个单迭代器、一个计数值和一个值,它将给定值赋予迭代器指向的元素为始的几个元素,
fill_n(迭代器,个数,数值)    一定要注意的是这个迭代器所指向的容器中含有的元素个数必须超过,将要赋值的个数,否则这个调用时一个灾难!

vector<int> v1{1,2,3,4,5};   //只有5个元素
fill_n(v1.begin(), 10, 1);  //将赋值10个元素,超过原容器中元素数量
for (auto i : v1)
	cout << i << endl;  //打印时崩溃
  • 插入迭代器(insert iterator)

       一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器,插入迭代器是一种向容器中添加元素的迭代器,通常,我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素,而当我们通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中。
       back_inserter是定义在#include <iterator>中的一个函数,它接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。

vector<int> v;
auto it = back_inserter(v);
*it = 52;  //相当于将52添加到v中
*it = 99;
for (auto num : v)
	cout << num << endl;  //55 99

fill_n与back_inserter:

        我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用,例如fill_n与back_inserter的联合使用,在每步迭代中,fill_n向给定序列的一个元素赋值,由于我们传递的参数是插入迭代器,因此每次赋值都会在v上调用push_back,最终,这条fill_n调用语句向v的末尾添加了10个元素,每个元素的值都是0

vector<int> v;
fill_n(back_inserter(v), 10, 2);
for (auto i : v)
	cout << i << endl;  // 2 2 2 2 2 2 2 2 2 2
  • copy(拷贝)算法

        拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法,它接受三个迭代器,前俩表示一个输入范围,第三个表示目的序列的起始位置,此算法将输入范围中的元素拷贝到目的序列中,返回拷贝到目的序列的尾元素之后的位置,传递给copy的目的序列至少要包含与输入序列一样多的元素,否则会导致崩溃,这一点很重要。

vector<int> v{ 11 };
vector<int> v2{ 1,2,3,4 };
copy(v2.begin(), v2.end(), v.begin());  //v中的元素数量小于v2
for (auto i : v)
	cout << i << endl;  //系统崩溃

       多个算法都提供所谓的“拷贝”版本,这些算法计算新元素的值,但不会将它们放置在输入序列的末尾,而是创建一个新序列保存这些结果,例如replace算法 replace(v.begin(),v,end(),0,99);  此语句含义是将v中所有为0的元素改为99,如果我们希望保留原序列不变,可以调用replace_copy,此算法接受额外第三个迭代器参数,指出调整后序列的保存位置。

vector<int> v{ 1,2,3,4,0,5,0 };
vector<int> v2;
replace_copy(v.begin(), v.end(), back_inserter(v2), 0, 99); //使用插入迭代器按需要增长目标序列
for (auto i: v)
	cout << i << endl;  //1 2 3 4 0 5 0 
for (auto i2 : v2)
	cout << i2 << endl;  //1 2 3 4 99 5 99

此调用后,v并未改变,v2包含v的一份拷贝,不过原来在v中值为0的元素在v2中都变为99
 

  • sort重排容器元素的算法

调用sort会重排输入序列中的元素,使之有序,它是利用元素类型的<运算符来实现排序的,sort(迭代器1,迭代器2)

消除重复元素:
        为了消除重复元素,首先将vector排序,使得重复的元素都相邻出现,一旦vector排序完毕,我们就可以使用另一个称为unique的标准库算法来重排vector,使得不重复的元素出现在vector的开始部分,由于算法不能执行容器的操作,我们将使用vector的erase成员来完成真正的删除操作。值得注意的是,即使容器中没有重复单词,这样调用erase也是安全的,在此情况下,unique会返回v.end(),因此传递给erase的俩个参数具有相同的值,意味着传递给erase的元素范围为空,删除一个空范围没有什么不良后果。

void deleteRepeat(vector<string>&v)
{
	//按照字典顺序进行排序
	sort(v.begin(), v.end());
	//重排输入范围,使得每个单词只出现一次
	//排列在范围的前部,返回指向不重复区域之后的一个位置的迭代器
	auto end_unique = unique(v.begin(), v.end());
	//使用erase删除重复单词
	v.erase(end_unique, v.end());
}

int main()
{
	vector<string> v{ "aa","bb","aa","cc","dd","cc","ii" };
	deleteRepeat(v);
	for (auto i : v)
		cout << i << endl; //aa bb cc dd ii
}


调用unique后:

  • unique重排算法

       调用unique后,重新排序将相邻的重复项"消除",此处并不是真的删除,unique并不删除任何元素,它只是覆盖相邻的重复元素,使得不重复元素出现在序列开始部分,unique返回的迭代器指向最后一个不重复元素之后的位置,此位置之后的元素仍然存在,但我们不知道它们的值是什么

  • stable_sort算法

     stable_sort算法可保证相等(具体相等的条件可以自定义,例如长度相等,或者某一个字段相等)元素的原本相对次序在排序后保持不变。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值