C++(16)STL实践与分析之初探算法

STL实践与分析

--概述、初窥算法【上】



    标准库容器定义的操作非常少,并没有给容器添加大量的功能函数,而是选择提供一组算法,这些算法大都不依赖特定的容器类型,是“泛型”的,可作用在不同类型的容器和不同类型的元素上!

    所谓泛型算法:一是因为它们实现共同的操作,所以称之为“算法”;而“泛型”指的是它们可以操作在多种容器类型上——不但可作用于vectorlist这些标准库类型,还可用在内置数组类型、甚至其他类型的序列上,只要自定义的容器类型只要与标准库兼容,同样可以使用这些泛型算法

   大多数算法是通过遍历由两个迭代器标记的一段元素来实现其功能。典型情况下,算法在遍历一段元素范围时,操纵其中的每一个元素。算法通过迭代器访问元素,这些迭代器标记了要遍历的元素范围。


一、概述

[cpp]  view plain copy
  1. int searchVal = 110;  
  2. vector<int>::const_iterator iter = find(ivec.begin(),ivec.end(),searchVal);  
  3.   
  4. if (iter != ivec.end())  
  5. {  
  6.     cout << "The value " << *iter << " is present!" << endl;  
  7. }  
  8. else  
  9. {  
  10.     cout << "The value " << searchVal << " is not present!" << endl;  
  11. }  

     使用两个迭代器和一个值调用find函数,检查两个迭代器实参标记范围内的每一个元素。只要找到与给定值相等的元素,find就会返回指向该元素的迭代器。如果没有匹配的元素,find返回它的第二个迭代器实参,表示查找失败

由于find运算是基于迭代器的,因此可以在任意容器中使用相同的find函数查找值:

[cpp]  view plain copy
  1. list<int> iList;  
  2. for (list<int>::size_type index = 0; index != 100; ++index)  
  3. {  
  4.     iList.push_back(index);  
  5. }  
  6.   
  7. int searchVal = 13;  
  8. list<int>::const_iterator iter = find(iList.begin(),iList.end(),searchVal);  
  9.   
  10. if (iter != iList.end())  
  11. {  
  12.     cout << "The value " << *iter << " is present!" << endl;  
  13. }  
  14. else  
  15. {  
  16.     cout << "The value " << searchVal << " is not present!" << endl;  
  17. }  

除了容器类型与对象名称,几乎没有任何修改!

类似的,由于指针的行为与作用在内置数组上的迭代器一样,因此也可以用find来搜索数组:

[cpp]  view plain copy
  1. int ia[] = {27,210,12,476,109,83};  
  2. int searchVal = 109;  
  3. int *result = find(ia,ia+sizeof(ia)/sizeof(*ia),searchVal);  
  4.   
  5. if (result != ia+sizeof(ia)/sizeof(*ia))  
  6. {  
  7.     cout << "The value " << *result << " is present!" << endl;  
  8. }  
  9. else  
  10. {  
  11.     cout << "The value " << searchVal << " is not present!" << endl;  
  12. }  

如果需要一个子区间,则传递给这个子区间的第一个元素以及最后一个元素的下一位置的迭代器或指针。

[cpp]  view plain copy
  1. int *result = find(ia+2,ia+5,searchVal);  

标准算法固有的独立于类型

  这种算法,正如我们所指出的,与容器的类型无关:在前面的描述中,没有任何内容依赖于容器类型。这种算法只在一点上隐式地依赖元素类型:必须能够对元素做比较运算。该算法的明确要求如下:

    1)需要某种遍历集合的方式:能够从一个元素向前移到下一个元素。

    2)必须能够知道是否到达了集合的末尾。

    3)必须能够对容器中的每一个元素与被查找的元素进行比较。

    4)需要一个类型指出元素在容器中的位置,或者表示找不到该元素。

   大多数情况下,每个算法都需要使用(至少)两个迭代器指出该算法操纵的元素范围。第一个迭代器指向第一个元素,而第二个迭代器则指向最后一个元素的下一位置。第二个迭代器所指向的元素【超出末端迭代器】本身不是要操作的元素,而被用作终止遍历的哨兵

   如果元素类型不支持相等(==)操作符,或者打算用不同的测试方法来比较元素,则可使用第二个版本的 find函数。这个版本需要一个额外的参数:实现元素比较的函数名字

   这些算法从不使用容器操作,因而其实现与类型无关,元素的所有访问和遍历都通过迭代器实现。实际的容器类型未知(甚至所处理的元素是否存储在容器中也是未知的)

[cpp]  view plain copy
  1. //P338 习题11.1  
  2. int main()  
  3. {  
  4.     ifstream inFile("input");  
  5.     vector<int> iVec;  
  6.     int val;  
  7.   
  8.     while (inFile >> val)  
  9.     {  
  10.         iVec.push_back(val);  
  11.     }  
  12.   
  13.     int searchVal;  
  14.     while (cin >> searchVal)  
  15.     {  
  16.         cout << searchVal << " have present "  
  17.              << count(iVec.begin(),iVec.end(),searchVal)  
  18.              << " times" << endl;  
  19.     }  
  20. }  

[cpp]  view plain copy
  1. //习题11.2  
  2. int main()  
  3. {  
  4.     ifstream inFile("input");  
  5.     list<string> strList;  
  6.     string val;  
  7.   
  8.     while (inFile >> val)  
  9.     {  
  10.         strList.push_back(val);  
  11.     }  
  12.   
  13.     string searchVal;  
  14.     while (cin >> searchVal)  
  15.     {  
  16.         cout << searchVal << " have present "  
  17.              << count(strList.begin(),strList.end(),searchVal)  
  18.              << " times" << endl;  
  19.     }  
  20. }  

【关键概念:算法永不执行容器提供的操作】

泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现算法基于迭代器及其操作实现,而并非基于容器操作。【P338推荐!】


二、初窥算法【上】

   使用泛型算法必须包含algorithm头文件:

[cpp]  view plain copy
  1. #include <algorithm>  

   标准库还定义了一组泛化的算术算法,其命名习惯与泛型算法相同,使用这些算法必须包含numeric头文件:

[cpp]  view plain copy
  1. #include <numeric>  

除了少数例外情况,所有算法都在一段范围内的元素上操作,我们将这段范围称为“输出范围”。带有输入范围参数的算法总是使用头两个形参标记该范围。这两个形参是分别指向要处理的第一个元素和最后一个元素的下一位置迭代器


1、只读算法

accumulate的使用:

    一个简单的只读算法accumulate,该算法在numeric头文件中定义。

[cpp]  view plain copy
  1. int sum = accumulate(iVec.begin(),iVec.end(),0);  
  2. cout << sum << endl;  

    将sum设置为 vec的元素之和再加上0accumulate带有三个形参。头两个形参指定要累加的元素范围。第三个形参则是累加的初值。

    首先,调用该函数时必须传递一个起始值,否则,accumulate将不知道使用什么起始值。其次,容器内的元素类型必须与第三个实参的类型匹配,或者可转换为第三个实参的类型。在accumulate内部,第三个实参用作累加的起点;容器内的元素按顺序连续累加到总和之中。因此,必须能够将元素类型加到总和类型上。

   类似的,也可以使用accumulatestring类型的vector容器中的元素连接起来:

[cpp]  view plain copy
  1. string sum = accumulate(strVec.begin(),strVec.end(),string(""));  

【注意】

    程序显式地创建了一个string对象,用该函数调用的第三个实参。传递一个字符串字面值,将会导致编译时错误。因为此时,累加和的类型将是 constchar*,而 string的加法操作符所使用的操作数则分别是string和 constchar* 类型,加法的结果将产生一个string对象,而不是 constchar* 指针。


find_first_of的使用:

    这个算法带有两对迭代器参数来标记两段元素范围,在第一段元素范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素,如果找不到匹配元素,则返回第一个范围的end迭代器。

[cpp]  view plain copy
  1. //使用find_first_of统计有多少个数字在这两个容器中同时出现  
  2.     size_t cnt = 0;  
  3.     vector<int>::iterator iter = iVec1.begin();  
  4.   
  5.     //在while的第一次循环中,遍历整个iVec1范围。  
  6.     //第二次以及后续的循环迭代则只考虑iVec1中尚未匹配的部分  
  7.     while ((iter = find_first_of(iter,iVec1.end(),iVec2.begin(),iVec2.end())) != iVec1.end())  
  8.     {  
  9.         cout << *iter << endl;  
  10.         ++ cnt;  
  11.         ++ iter;  
  12.     }  
  13.     cout << "cnt = " << cnt << endl;  

【关键概念:迭代器实参类型,P340,值得仔细品读】

[cpp]  view plain copy
  1. //P341 习题11.3  
  2. int main()  
  3. {  
  4.     vector<int> iVec;  
  5.     ifstream inFile("input");  
  6.     int val;  
  7.   
  8.     while (inFile >> val)  
  9.     {  
  10.         iVec.push_back(val);  
  11.     }  
  12.   
  13.     int sum = accumulate(iVec.begin(),iVec.end(),0);  
  14.     cout << sum << endl;  
  15. }  


STL实践与分析

--初窥算法【下】



一、写容器元素的算法

    一些算法写入元素值。在使用这些算法写元素时一定要当心,必须确保算法所写的序列至少足以存储要写入的元素

1、写入输入序列的元素

    写入到输入序列的算法本质上是安全的——只会写入与指定输入范围数量相同的元素。

    写入到输入序列的一个简单算法是fill函数:

[cpp]  view plain copy
  1. fill(iVec.begin(),iVec.end(),10);  
  2. fill(iVec.begin(),iVec.begin()+iVec.size()/2,0);  

    fill带有一对迭代器形参用于指定要写入的范围,而所写的值是它的第三个形参的副本。如果输入范围有效,则可安全写入。这个算法只会对输入范围内已存在的元素进行写入操作


2、不检查写入操作的算法

   fill_n函数带有的参数包括:一个迭代器、一个计数器以及一个值。fill_n函数假定对指定数量的元素做写操作是安全的。

[cpp]  view plain copy
  1. vector<int> iVec;  
  2. /**Error 
  3. *但是编译器不会报错, 
  4. *很可能导致严重的运行时错误 
  5. */  
  6. fill_n(iVec.begin(),10,0);  

   对于指定数目的元素做写入运算,或者写到目标迭代器的算法,都不检查目标的大小是否足以存储要写入的元素。


3、引入back_inserter

    确保算法有足够的元素存储输出数据的一种方法是使用插入迭代器。在使用插入迭代器赋值时,会在容器中添加一个新元素,其值等于赋值运算的右操作数的值。

    使用back_inserter的程序必须包含iterator头文件。

   back_inserter函数是迭代器适配器。迭代器适配器使用一个对象作为实参,并生成一个适应其实参行为的新对象。在本例中,传递给back_inserter的实参是一个容器的引用back_inserter生成一个绑定在该容器上的插入迭代器在试图通过这个迭代器给元素赋值时,

   值运算将调用push_back在容器中添加一个具有指定值的元素。使用 back_inserter可以生成一个指向fill_n写入目标的迭代器:

[cpp]  view plain copy
  1. vector<int> iVec;  
  2. fill_n(back_inserter(iVec),10,0);  

效果相当于在vec上调用push_back,vec末尾添加10个元素,每个元素的值都是0


4、写入到目标迭代器的算法

    第三类算法向目标迭代器写入未知个数的元素。如:copy函数。copy带有三个迭代器参数:头两个指定输入范围,第三个则指向目标序列的一个元素传递给copy的目标序列必须至少要与输入范围一样大。假设ilst是一个存放int型数据的 list对象,可如下将它copy给一 个vector对象:

[cpp]  view plain copy
  1. vector<int> iVec;  
  2. copy(iList.begin(),iList.end(),back_inserter(iVec));  

copy从输入范围中读取元素,然后将它们复制给目标ivec

   当然,这个例子的效率比较差,最好应该对新构造容器的初始化式:

[cpp]  view plain copy
  1. vector<int> iVec(iList.begin(),iList.end());  

5、算法的_copy版本

    有些算法提供所谓的“复制(copying)”版本。这些算法对输入序列的元素做出处理,但不修改原来的元素,而是创建一个新序列存储元素的处理结果。

   replace算法就是一个很好的例子。该算法对输入序列做读写操作,将序列中特定的值替换为新的值。该算法带有四个形参:一对指定输入范围的迭代器和两个值。每一个等于第一值的元素替换成第二个值。

[cpp]  view plain copy
  1. replace(iList.begin(),iList.end(),0,42);  

  如果不想改变原来的序列,则调用replace_copy函数。这个算法接受第三个迭代器实参,指定要保存调整后序列的位置:

[cpp]  view plain copy
  1. vector<int> iVec;    replace_copy(iList.begin(),iList.end(),back_inserter(iVec),0,42);  

[cpp]  view plain copy
  1. //P343 习题11.6  
  2. int main()  
  3. {  
  4.     int ia[] = {1,3,5,7,9,2,4,6,8,10};  
  5.     fill_n(ia,sizeof(ia)/sizeof(*ia),0);  
  6.   
  7.     for (size_t index = 0;index != sizeof(ia)/sizeof(*ia); ++index)  
  8.     {  
  9.         cout << *(ia + index) << endl;  
  10.     }  
  11. }  

[cpp]  view plain copy
  1. //或者  
  2. int main()  
  3. {  
  4.     vector<int> iVec;  
  5.     iVec.resize(10);  
  6.     fill_n(iVec.begin(),iVec.size(),0);  
  7.   
  8.     for (vector<int>::iterator iter = iVec.begin(); iter != iVec.end(); ++iter)  
  9.     {  
  10.         cout << *iter << endl;  
  11.     }  
  12. }  

二、对容器元素重新排序的算法

问题:

    我们要分析一组儿童故事中所使用的单词。例如:它们使用了多少个由六个或以上字母组成的单词。每个单词只统计一次。要求以长度的大小输出这些单词,对于同样长的单词,则以字典顺序输出。

分析:

   为 了解此问题,要做下面几项操作:

    1.去掉所有重复的单词。

    2.按单词的长度排序。

    3.统计长度等于或超过6个字符的单词个数。

  上述每一步都可使用泛型算法实现。

样例:

   为了说清楚,使用下面这个简单的故事作为我们的输入:

[plain]  view plain copy
  1. the quick red fox jumps over the slow red turtle  

对于这个输入,我们的程序应该产生如下输出:

[plain]  view plain copy
  1. 1 word 6 characters or longer  

1)、去除重复

    假设我们的输入存储在一个名为words的 vector对象中,第一个子问题是将words中重复出现的单词去除掉:

[cpp]  view plain copy
  1. sort(words.begin(),words.end());  
  2. vector<string>::iterator iter = unique(words.begin(),words.end());  
  3.   
  4. words.erase(iter,words.end());  

   vector对象包含使用的所有单词。首先对此vector对象排序。sort算法带有两个迭代器实参,指出要排序的元素范围。这个算法使用默认的小于(<)操作符比较元素。在本次调用中,要求对整个vector对象排序。

此时,vector对象内:

fox

jumps

over

quick

red

red

slow

the

the

turtle

注意:redthe重复了。


2)、unique的使用

   unique算法带有两个指定元素范围的迭代器参数。该算法删除相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器,表示无重复的值范围的结束。

[cpp]  view plain copy
  1. unique(words.begin(),words.end());  

语句调用结束后:

fox

jumps

over

quick

red

slow

the

turtle

the

red

【注意:】

    words的大小并没有改变,依然保存着10个元素;只是这些元素的顺序改变了。unique实际上并没有删除任何元素,而是将无重复的元素复制到序列的前端,从而覆盖相邻的重复元素


3)、使用容器操作删除元素

    调用erase实现删除重复的项。这个函数调用从end_unique指向的元素开始删除,直到words的最后一个元素也删除掉为止。调用之后,words存储输入的 8个不相同的元素。

    算法不直接修改容器的大小。如果需要添加或删除元素,则必须使用容器操作。【好像有点不负责任的意思O(∩_∩)O~


4)、定义需要的实用函数

   两个接下来要使用的函数stable_sortcount_if。这两个是使用stable_sortcount_if算法的配套函数,称为谓词。谓词是做某些检测的函数,返回用于条件判断的类型,指出条件是否成立。

[cpp]  view plain copy
  1. bool isShorter(const string &s1,const string &s2)  
  2. {  
  3.     return s1.size() < s2.size();  
  4. }  
  5. bool GT6(const string &s)  
  6. {  
  7.     return s.size() >= 6;  
  8. }  

5)、排序算法

    除了sort之外,标准库还定义了stable_sort算法,stable_sort保留相等元素的原始相对位置。

    sort和 stable_sort都是重载函数。其中一个版本使用元素类型提供的小于(<)操作符实现比较。第二个重载版本带有第三个形参:比较元素所使用的谓词函数的名字这个谓词函数必须接受两个实参,实参的类型必须与元素类型相同,并返回一个可用作条件检测的值。如:

[cpp]  view plain copy
  1. stable_sort(words.begin(),words.end(),isShorter);  

调用后:


fox

red

the

over

slow

jumps

quick

turtle


6、统计长度不小于6的单词数

   使用count_if算法实现:

[cpp]  view plain copy
  1. vector<string>::size_type cnt = count_if(words.begin(),words.end(),GT6);  

执行count_if函数时,首先读取他的头两个参数所标记的范围内的元素,每读取一个元素,则将其传递给第三个实参表示的谓词函数。


7、将全部程序放在一起

[cpp]  view plain copy
  1. bool isShorter(const string &s1,const string &s2)  
  2. {  
  3.     return s1.size() < s2.size();  
  4. }  
  5. bool GT6(const string &s)  
  6. {  
  7.     return s.size() >= 6;  
  8. }  
  9.   
  10. int main()  
  11. {  
  12.     vector<string> words;  
  13.     ifstream inFile("input");  
  14.   
  15.     string word;  
  16.     while (inFile >> word)  
  17.     {  
  18.         words.push_back(word);  
  19.     }  
  20.   
  21.     sort(words.begin(),words.end());  
  22.     vector<string>::iterator end_unique = unique(words.begin(),words.end());  
  23.   
  24.     words.erase(end_unique,words.end());  
  25.   
  26.     stable_sort(words.begin(),words.end(),isShorter);  
  27.   
  28.     vector<string>::size_type wc = count_if(words.begin(),words.end(),GT6);  
  29.     cout << wc << " words 6 characters or longer." << endl;  
  30.   
  31.     cout << "Words:" << endl;  
  32.     for (vector<string>::iterator it = words.begin(); it != words.end(); ++it)  
  33.     {  
  34.         cout << *it << endl;  
  35.     }  
  36. }  

[cpp]  view plain copy
  1. //P347 习题11.9  
  2. bool GT4(const string &str)  
  3. {  
  4.     return str.size() >= 4;  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     ifstream inFile("input");  
  10.     vector<string> strVec;  
  11.     string str;  
  12.   
  13.     while (inFile >> str)  
  14.     {  
  15.         strVec.push_back(str);  
  16.     }  
  17.   
  18.     sort(strVec.begin(),strVec.end());  
  19.     vector<string>::iterator end_unique =  
  20.         unique(strVec.begin(),strVec.end());  
  21.   
  22.     vector<string>::size_type word_cnt = count_if(strVec.begin(),end_unique,GT4);  
  23.   
  24.     cout << "Now! Have " << word_cnt << " words made of 4 characters or longer." << endl;  
  25.     for (vector<string>::iterator iter = strVec.begin(); iter != end_unique; ++iter)  
  26.     {  
  27.         cout << *iter << endl;  
  28.     }  
  29. }  

[cpp]  view plain copy
  1. //习题11.10  
  2. bool GT6(const string &str)  
  3. {  
  4.     return str.size() >= 6;  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     ifstream inFile("input");  
  10.     vector<string> strVec;  
  11.     string str;  
  12.   
  13.     while (inFile >> str)  
  14.     {  
  15.         strVec.push_back(str);  
  16.     }  
  17.   
  18.     vector<string>::size_type word_cnt = 0;  
  19.     vector<string>::iterator first = strVec.begin();  
  20.   
  21.     while ((first = find_if(first,strVec.end(),GT6)) != strVec.end())  
  22.     {  
  23.         ++ word_cnt;  
  24.         ++ first;  
  25.     }  
  26.   
  27.     cout << word_cnt << endl;  
  28.     cout << "WORD:" << endl;  
  29.     for (vector<string>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)  
  30.     {  
  31.         cout << *iter << endl;  
  32.     }  
  33. }  

你可能感兴趣的:(C++(16)STL实践与分析之初探算法)