目录

 

0.什么是STL?

1. STL的组成

2.容器

0.容器分类

0.1  名字空间

1.array容器:数组容器

2.string 容器:

string的构造

string类的一些常用方法、查找,比较 ,拼接,截取

3.vector容器:向量容器

应用 :

注意:clear() 、erase()、shrink_to_fit()和swap(),他们在清除元素时内存也释放了吗

现在我们要vector的数据排序,求和、等怎么做呢?

         vector的内存机制

         vector迭代器失效问题

            什么情况下会失效?以及失效的原因

4.deque容器:双端队列容器

5.stack容器

6.queue容器

7.list容器:链表容器 

list容器的创建

使用过程中的坑


 

8.map/mutimap容器

9.set/mutiset容器

3.迭代器(,和)

4.算法(,和)

 


0.什么是STL?

C++ STL(标准模板库,STL 是 C++ 标准库的一部分)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数。

要点

  • STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离(重要特点。这种分离确实使得STL变得非常通用。例如,由于STL的 sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组。
  • STL 已完全被内置到支持 C++ 的编译器中,无需额外安装(提供源码)
  • STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)——OOP的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效

C++ 标准模板库的核心包括以下三个组件:

组件描述容器(Containers)容器是用来管理某一类对象的集合,本质上就是封装有数据结构的模板类。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。算法(Algorithms)算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。迭代器(iterators)迭代器用于访问容器中的元素

这三个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务。

  • STL可说的优点
  • 高可重用性:STL 中几乎所有的代码都采用了模板类和模版函数的方式实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
  • 高性能:如 map 可以高效地从十万条记录里面查找出指定的记录,因为 map 是采用红黑树的变体实现的。
  • 高移植性


1. STL的组成

  •  通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的

STL的组成含义容器一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。算法STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 <algorithm> 中,少部分位于头文件 <numeric> 中。迭代器在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。函数对象如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。适配器可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。内存分配器为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。

  • 容器的使用

    大致有下面6个步骤:

        1.添加相应的头文件(如 #include <list> )

        2.添加std命名空间(用 using namespace std; )

        3.赋予模板具体的使用类型(如 typedef list<string> LISTSTR; )

        4.实例化模板(如 LISTSTR test; )

        5.实例化游标(如 LISTSTR::iterator i; )

        6.通过迭代器对象访问模板对象,例如

            // 逐个输出链表test中的元素
            for ( i =  test.begin(); i != test.end(); ++i )
                cout << *i << " ";

  • 容器模板中的常用函数成员

函数成员函数功能begin()返回指向容器中第一个元素的迭代器。end()返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。rbegin()返回指向最后一个元素的迭代器。rend()返回指向第一个元素所在位置前一个位置的迭代器。cbegin() /crbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。cend() /crend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。size()返回实际元素个数。resize()设置大小(size),改变实际元素的个数。capacity()返回当前容量。empty()判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。reserve()设置容量(capacity),增加容器的容量。如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector<T> 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。shrink _to_fit()将内存减少到等于当前元素实际所使用的大小。front()返回第一个元素的引用。back()返回最后一个元素的引用。data()返回指向容器中第一个元素的指针。assign()用新元素替换原有内容。push_back()在序列的尾部添加一个元素。pop_back()移出序列尾部的元素。insert()在指定的位置插入一个或多个元素。erase()移出一个元素或一段元素。(也需注意迭代器失效问题)clear()移出所有的元素,容器大小变为 0。swap()交换两个容器的所有元素。emplace()在指定的位置直接生成一个元素。emplace_back()在序列尾部生成一个元素。

2.容器

0.容器分类

stl头文件 容器分类特点优缺点底层实现方式<vector> 序列式容器以线性排列(类似普通数组的存储方式)来存储某一指定类型的数据  <list> <deque> <stack> 适配器容器本质上也属于序列容器,只不过它们都是在 deque 容器的基础上改头换面而成,通常更习惯称它们为容器适配器  <queue> <set> 关联式容器非线性的树结构,更准确的说是二叉树结构。此类容器在存储元素值的同时,还会为各元素额外再配备一个值(又称为“键”),它的功能是在使用关联式容器的过程中,如果已知目标元素的键的值,则直接通过该键就可以找到目标元素(默认按键值升序排列)优点:快速查找、读取或者删除所存储的元素,同时该类型容器插入元素的效率也比序列式容器高红黑树<map> <utility> <memory> <unordered_map> 无序关联容器( C++ 11 标准才正式引入)无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键 哈希表的存储结构,当数据存储位置发生冲突时,解决方法选用的是“链地址法”unordered_multimap unordered_set unordered_multiset <functional>     <iterator>     <numeric>     <algorithm>       

0.1  名字空间

STL的sort()以及其他标志符都封装在名字空间std中。STL的sort()算法编译为std::sort(),从而避免了名字冲突

为了使用STL,可以将下面的指示符插入到你的源代码文件中,典型地是在所有的#include指示符的后面:

using namespace std;

1.array容器:数组容器

2.string 容器:

string的构造

#include<iostream> #include<string> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { string strs;//生成空字符串 char ch[] = "abcdefghijk"; string str("1234567890"); string s0(str); string s(str,3);//将字符串str中始于3的部分作为构造函数的初值 string s1(str,3,6);//将字符串str中始于3、长度为6的部分作为字符串初值 string s2(str,3,6); string s3(ch,3,6);//将字符串str中始于3、长度为6的部分作为字符串初值 string s4(5,'w'); string s5(ch,6);//将字符串ch的前6个元素赋值给s3 string s6(str.begin(),str.end());//string 类支持迭代器 cout <<"strs: " <<strs << endl; cout <<"str: " << str << endl ; cout <<"s0: " << s0 << endl; cout <<"s1: " << s1 << endl; cout <<"s2: " << s2 << endl; cout <<"s3: " << s3 << endl; cout <<"s4: " << s4 << endl; cout <<"s5: " << s5 << endl; cout <<"s6: " << s6 << endl; return 0; }

 

string类的一些常用方法、查找,比较 ,拼接,截取

注意:一些基本容器的增删减等方法不再赘述(如果相知细节,请翻看下面vector的描述,超详细) 

  •   replace() //替换字符
  •   + //串联字符串
  •  ==,!=,<,<=,>,>=,compare()  //比较字符串
  •  copy() //将某值赋值为一个C_string
  •   c_str() //将内容以C_string返回
  • substr() //返回某个子字符串

#include<iostream> #include<string> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { string str("1234567890"); //1.size() 和 length()``这两个函数会返回 string 类型对象中的字符个数,且它们的执行效果相同 int size = str.size(); int lenth = str.length(); cout <<"str.size: " << size << endl; cout <<"str.length: " << lenth << endl; cout <<"str.capacity: " << str.capacity() << endl; cout <<"str.max_size: " << str.max_size() << endl; //2.获取字符串元素 cout <<"str[2]: " << str[2] << endl; cout <<"str.at(2): " << str.at(2) << endl; //3.字符串比较 如果相比较的两个子串相同,compare() 函数返回 0,否则返回非零值,能区分字母的大小写。 string A ("aBcdef"); string B ("AbcdEf"); string C ("123456"); string D ("123dfg"); int m=A.compare (B); //完整的A和B的比较 int n=A.compare(1,5,B,4,2); //"Bcdef"和"AbcdEf"比较 int p=A.compare(1,5,B,4,2); //"Bcdef"和"Ef"比较 int q=C.compare(0,3,D,0,3); //"123"和"123"比较 cout << "m = " << m << ", n = " << n <<", p = " << p << ", q = " << q << endl; //4.字符串查找函数 find() rfind()实现逆向查找 //若查找 find() 函数和其他函数没有搜索到期望的字符(或子串),则返回 npos;若搜索成功, //则返回搜索到的第 1 个字符或子串的位置。其中,npos 是一个无符号整数值,初始值为 -1 //[1]搜索单个字符、搜索子串; //[2]实现前向搜索、后向搜索; //[3]分别实现搜索第一个和最后一个满足条件的字符(或子串); string x ("Hi, Peter, I'm sick. Please bought some drugs for me."); int y = x.find('P'); int ry = x.rfind('P'); cout << "find()查找的P在第 " << y << " 位"<< endl;//从0开始 cout << "rfind()查找的P在第 " << ry << " 位"<< endl; int z = x.find("some",0); int rz= x.rfind("some",0); cout << "find()查找的some在第 " << z << " 位"<< endl; cout << "rfind()查找的some在第 " << rz << " 位"<< endl; int l = x.find (" drugs", 0, 5); int rl = x.rfind (" drugs", 0, 5); cout << "find()查找的' drugs'在第 " << l << " 位"<< endl; cout << "rfind()查找的' drugs'在第 " << rl << " 位"<< endl; string e ("sick"); int w = x.find (e, 0); int rw = x.rfind (e, 0); cout << "find()查找的y字符串在第 " << w << " 位"<< endl; cout << "rfind()查找的y字符串在第 " << rw << " 位"<< endl; //5.c_str() cout << "x.c_str : "<<x.c_str()<<endl; //转为c风格字符串 //6.截取子字符串 //s.substr(pos, n):从pos开始的n个字符的拷贝 cout << "x.substr : "<<x.substr(3,8)<<endl; //7.拼接字符串 : + cout <<"A+B = "<< A+B<< endl; return 0; }

容器淘汰了_容器淘汰了

 

3.vector容器:向量容器

向量(Vector)是一个封装了动态大小数组的顺序容器。就可以认为向量(Vector)是一个能够存放任意类型的动态数组

即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

特点: 

  •  尾部插入或删除元素

std::vector<double> values;

应用 :

  • 头文件:#include <vector>

     using namespace std;

  • 1.创建vector

1.vector<T> v; //采用模板实现类实现,默认构造函数 std::vector<double> values;//空vector容器 2.std::vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};//指定初始值以及元素个数 3.vector(n, elem);//构造函数将n个elem拷贝给本身。 std::vector<double> values(20); //指定元素个数,默认初始值都为 0 std::vector<double> values(20, 1.0);//指定其它默认值, 20 个元素的值都是 1.0 4.vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。 int array[]={1,2,3}; std::vector<int>values(array, array+2);//values 将保存{1,2} 5.vector(const vector &vec);//拷贝构造函数。 std::vector<char>value1(5, 'c'); std::vector<char>value2(value1);

注意:圆括号 () 中的 2 个参数,既可以是常量,也可以用变量来表示

int num=20; double value =1.0; std::vector<double> values(num, value);

  • 2.vector容器包含的成员函数

函数成员函数功能begin()返回指向容器中第一个元素的迭代器。end()返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。rbegin()返回指向最后一个元素的迭代器。rend()返回指向第一个元素所在位置前一个位置的迭代器。cbegin() /crbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。cend() /crend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。size()返回实际元素个数。resize()设置大小(size),改变实际元素的个数。capacity()返回当前容量。empty()判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。reserve()设置容量(capacity),增加容器的容量。如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector<T> 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。shrink _to_fit()将内存减少到等于当前元素实际所使用的大小。[ ]重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。at()使用经过边界检查的索引访问元素。front()返回第一个元素的引用。back()返回最后一个元素的引用。data()返回指向容器中第一个元素的指针。assign()用新元素替换原有内容。push_back()在序列的尾部添加一个元素。pop_back()移出序列尾部的元素。insert()在指定的位置插入一个或多个元素。erase()移出一个元素或一段元素。clear()移出所有的元素,容器大小变为 0。swap()交换两个容器的所有元素。emplace()在指定的位置直接生成一个元素。emplace_back()在序列尾部生成一个元素。

 

示例代码:

#include <iostream> #include <vector> using namespace std; int main() { //初始化一个空vector容量 vector<char>value; //向value容器中的尾部依次添加 S、T、L 字符 value.push_back('S'); value.push_back('T'); value.push_back('L'); //调用 size() 成员函数容器中的元素个数 printf("元素个数为:%d\n", value.size()); //使用迭代器遍历容器 for (auto i = value.begin(); i < value.end(); i++) cout << *i << " "; cout << endl; //向容器开头插入字符 value.insert(value.begin(), 'C'); for (auto i = value.begin(); i < value.end(); i++) cout << *i << " "; cout << endl; //向容器下标3插入字符B value.insert(value.begin()+2, 'B'); cout << value.at(2) << endl; cout << value[2] << endl; for (auto i = value.begin(); i < value.end(); i++) cout << *i << " "; cout << endl; //使用迭代器逆序输出 for (auto i = value.rbegin(); i != value.rend(); i++) cout << *i << " "; cout << endl; //返回当前容量 cout << value.capacity() << endl; //这个 front() 和back()到底可以用来干什么呢? cout << "value.front() = " << value.front() << endl; cout << "value.back() = " << value.back() << endl; //返回的是reference类型。 就是C++里面的变量引用,就是为了方便的读写,不知道是否还有其他用途,日后遇到再来补充 //data() cout << "data() = " << *value.data() << endl; //不知道要返回第一元素指针干嘛! //vector<char> value1 = {'L','O','V','E'};//VS2010还不支持C++11的这个特性 vector<char> value1; value1.push_back('L'); value1.push_back('O'); value1.push_back('V'); value1.push_back('E'); value1.push_back('L'); value1.push_back('H'); //swap() 尽管大小可能有所不同,但两个容器对象必须具有相同的类型(相同的模板参数)。 value1.swap(value); cout << "value: "; for (auto i = value.begin(); i != value.end(); i++) cout << *i << " "; cout << endl; cout << "value1: "; for (auto i = value1.begin(); i != value1.end(); i++) cout << *i << " "; cout << endl; //assign() value.assign(value1.begin(),value1.end()); cout << "value: "; for (auto i = value.begin(); i != value.end(); i++) cout << *i << " "; cout << endl; //erase() //(1)erase( pos, n); 删除从pos开始的n个字符,例如erase( 0, 1),删除0位置的一个字符,即删除第一个字符 //(2)erase( position); 删除position处的一个字符(position是个string类型的迭代器) //(3)erase(first,last);删除从first到last之间的字符,(first和last都是迭代器) value.erase(value.begin(),value.end()-2); cout << "erase() value: "; for (auto i = value.begin(); i != value.end(); i++) cout << *i << " "; cout << endl; //clear() value1.clear(); cout << "clear() value1: "; for (auto i = value1.begin(); i != value1.end(); i++) cout << *i << " "; cout << endl; return 0; }

运行结果: 

    

容器淘汰了_迭代器_02

注意:clear() 、erase()、shrink_to_fit()和swap(),他们在清除元素时内存也释放了吗

以下示例可以很清晰看到shrink_to_fit()和swap()才是释放了内存,从capacity的值可得。

容器淘汰了_容器淘汰了_03

容器淘汰了_迭代器_04

容器淘汰了_#include_05

容器淘汰了_字符串_06

shrink_to_fit() 说明文档:

http://www.cplusplus.com/reference/vector/vector/shrink_to_fit/ 

之前说STL,最大的特点就是做了数据和算法的分离,

现在我们要vector的数据排序,求和、等怎么做呢?

自然有其通用的算法了。这些算法基本在这几个头文件里<algorithm>,<numeric>和<functional>。后面算法部分会详细讲解。

  •  求和
        

容器淘汰了_容器淘汰了_07

  • 排序
        

容器淘汰了_字符串_08

vector的内存机制

  • 每次扩容原来的百分之五十(vs2010测试所得)

#include<iostream> #include<vector> using namespace std; int main() { vector<int> v; for (int i = 1; i < 100; i++) { cout << "capacity:" << v.capacity() << ", " << "size" << v.size() << endl; v.push_back(i); } return 0; }

容器淘汰了_#include_09

vector迭代器失效问题

什么情况下会失效?以及失效的原因

vector是个连续内存存储的容器,如果vector容器的中间某个元素被删除或从中间插入一个元素, 有可能导致内存空间不够用而重新分配一块大的内存

  • 造成失效的其中一原因:

        是因为内存的重新分配, 保留下来的迭代器不再指向容器中原来的元素

  • 还有一种是删除元素,迭代器指向的空间自然就是一个无效的地址,无法再使用

 使得vector迭代器失效的操作有:

  • (1)执行erase方法时,指向删除节点及其之后的全部迭代器均失效;

#include<iostream> #include<vector> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { int values1[10] = {1,2,0,3,4,0,5,6}; vector<int> values4(values1,values1+7);//拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器 for (auto iter = values4.begin(); iter != values4.end();iter++) { if(*iter == 0) //values4.erase(iter); //如果不给迭代器重新赋值,会报错 //为什么? //values4.erase(it)之后it指向的节点已经被删除释放了,it指向的空间自然就是一个无效的地址, //之后再it++使用it访问一个无效的地址自然会程序崩溃 //应该是如下: iter = values4.erase(iter);//erase方法可以返回下一个有效的iterator //还可以是 //values4.erase(iter++);就需要去掉for循环里的iter++ //这种是为什么呢? //1、先把it的值赋值给一个临时变量做为传递给erase的参数变量 //2、因为 参数处理优先于函数调用!!!,所以接下来执行了it++操作,也就是it现在已经指向了下一个地址。 //3、再调用erase函数,释放掉第一步中保存的要删除的it的值的临时变量所指的位置。 就不会造成访问非法内存而程序崩溃 } return 0; }

 

  • (2)执行push_back方法时,end操作返回的迭代器失效;

#include<iostream> #include<vector> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { int values1[10] = {1,2,0,3,4,0,5,6}; vector<int> values4(values1,values1+7);//拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器 values4.push_back(7); vector<int>::iterator iter1 = values4.end(); cout<< *iter1<<endl;//执行会报错 return 0; }

  • (3)插入一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效;(capacity是指在发生realloc前能允许的最大元素数,即预分配的内存空间)

#include<iostream> #include<vector> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { int values1[10] = {1,2,0,3,4,0,5,6}; vector<int> values4(values1,values1+7);//拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器 auto iter2 = values4.begin(); values4.insert(values4.begin()+4,9);//若有重新分配空间,插入会导致原来的begin() end()迭 代器失效,需重新获取 for(;iter2!=values4.end();iter2++) //执行这个会报错 { cout<< *iter2; } return 0; }

  • (4)插入一个元素后,如果空间未重新分配,指向插入位置之前的元素的迭代器仍然有效。

注意 :总之避免迭代器失效,就是及时更新迭代器

4.deque容器:双端队列容器

5.stack容器

6.queue容器

7.list容器:链表容器 

  • 底层实现:双向链表容器
  • 一些通用成员函数不再细说(详见vector)

list容器的创建

#include<iostream> #include<list> #include <numeric> //求和 #include <algorithm> //algorinthm 排序 using namespace std; int main() { list<int> values;//创建一个没有任何元素的空 list 容器 list<int> values1(10);//创建包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0) list<int> values2(10,5);//创建了一个包含 10 个元素并且值都为 5 个 values 容器 list<int> values3(values2);//拷贝该容器可以创建新的 list 容器 list<int> values4(values1.begin(),values1.end());//拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器 return 0; }

使用过程中的坑(迭代器失效问题)

  • list的底层结构为带头节点的双向循环链表,因此在list中进行插入的时候是不会导致list的迭代器失效的,只有在删除的时候才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

void TestListIterator() { int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; list<int> l(arr, arr + sizeof(arr) / sizeof(arr[0])); auto it = l.begin(); while (it != l.end()) { // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值 l.erase(it); ++it; } }

。。。未完

8.map/mutimap容器

9.set/mutiset容器

   特点

set是STL中一种标准关联容器:它底层使用红黑树(二叉平衡查找树)

    插入删除操作时仅仅需要指针操作节点即可完成,不涉及到内存移动和拷贝,所以效率比较高。

set,顾名思义是“集合”的意思,在set中元素都是唯一的,而且默认情况下会对元素自动进行升序排列,支持集合的交(set_intersection),差(set_difference) 并(set_union),对称差(set_symmetric_difference) 等一些集合上的操作,如果需要集合中的元素允许重复那么可以使用multiset,set和multiset类提供了控制数值集合的操作,其中数值是关键字。

红黑树(二叉平衡查找树),插入元素时,它会自动调整二叉树的排列,把元素放到适当的位置,以保证每个子树根节点键值大于左子树所有节点的键值,小于右子树所有节点的键值;另外,还得保证根节点左子树的高度与右子树高度相等。
平衡二叉检索树使用中序遍历算法,检索效率高于vector、deque和list等容器,另外使用中序遍历可将键值按照从小到大遍历出来。
构造set集合主要目的是为了快速检索

自动排序 

优点:是使得搜寻元素时具有良好的性能,具有对数时间复杂度。

缺点就是:不能直接改变元素值。因为这样会打乱原有的顺序。故有以下特点

  •   改变元素值的方法是先删除旧元素,再插入新元素。
  •   存取元素只能通过迭代器,从迭代器的角度看,元素值是常数

  set、mutiset的区别

  • 1) MultiSet

      可以插入完全相同的两条记录(multiset不会检测数据,因此可以插入重复数据)

  • 2) Set

      不可以插入完全相同的两条记录(set插入数据的同时会返回插入结果,表示插入是否成功)

      保证记录的唯一性(由于需要查重处理,会降低数据插入的速度),可以作为一种去重的方法

  • 什么时候需要用multiset?

        当然是需要用set,但是又允许重复key存在的时候了。

  • 什么时候用set?

        需要随时往容器中插入元素,随时对元素进行快速查找,又需要按某种顺序对元素进行遍历的时候.

  头文件、及类模板内容

  • 头文件: #include <set>
  • set和multiset都是定义在std空间里的类模板:

template<class _Kty,     class _Pr = less<_Kty>,     class _Alloc = allocator<_Kty> > class multisett emplate<class _Kty,     class _Pr = less<_Kty>,     class _Alloc = allocator<_Kty> > class set 只要是可复赋值、可拷贝、可以根据某个排序准则进行比较的型别都可以成为它们的元素。 第二个参数用来定义排序准则。缺省准则less是一个仿函数,以operator<对元素进行比较

  •   排序准则就是型别的一部分。型别系统确保只有排序准则相同的容器才能被合并。

//声明一个set,按升序排列,默认升序排列 set<int>set1; 或 set<int,less<int>>set1; //用typedef生成用函数对象less<int>按升序排列的整型multiset类型, //这个新类型用于实例化一个整型multiset对象set1 typedef multiset<int, less<int>> ims; ims set1; //声明一个multiset,按降序排列 multiset<int, greater<int>>set1; 或 typedef multiset<int, greater<int>> ims; ims set1;

  • 自定义比较函数; 

include<set> typedef struct { 定义类型  }ss(类型名); struct cmp { bool operator()( const int &a, const int &b ) const { 定义比较关系< } }; (运算符重载,重载<) set<ss> base; ( 创建一个元素类型是ss,名字是base的set ) 注:定义了<,==和>以及>=,<=就都确定了,STL的比较关系都是用<来确定的,所以必须通 过定义< --“严格弱小于”来确定比较关

 

 

4.需要注意的地方

  • 1.multiset::count(key)的返回值可能大于1。(因为插入了多个关键值)
  • 2.multiset::size()的返回值是多重集合的势(cardinality),即multiset中元素的个数,而不是值的个数。比如,{1, 1, 2}的size是3,而不是2。
  • 3.multiset::erase(key)会将对应的key全部删掉,所以对{1, 1, 2}调用erase(1)之后,它就变成了{2}。如果只想删除对应位置的key,erase的参数可以设置为迭代器,比如erase(lower_bound(n)),意味删除大于或等于值为n的第一个元素。
  • 4.只要key存在于集合中,set::equal_range(key)的返回值pair<iterator1, iterator2>总是会有++iterator1 == iterator2。但是对multiset来说就不一定了。

3.迭代器(<utility>,<iterator>和<memory>)

为什么使用迭代器?模板使得算法独立于存储的数据类型,而迭代器使得算法独立于使用的容器类型

迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通过迭代器存取元素序列进行工作的

<utility> <iterator> <memory> 

 

  • 迭代器种类:

迭代器功能描述输入迭代器提供对数据的只读访问只读,支持++、==、!=输出迭代器提供对数据的只写访问只写,支持++前向迭代器提供读写操作,并能向前推进迭代器读写,支持++、==、!=双向迭代器提供读写操作,并能向前和向后操作读写,支持++、–,随机访问迭代器提供读写操作,并能以跳跃的方式访问容器的任意数据,是功能最强的迭代器读写,支持++、–、[n]、-n、<、<=、>、>=

 

4.算法(<algorithm>,<numeric>和<functional>)

<algorithm>sort()、<numeric>accumulate()、<functional>