STL标准模板库
内容:
- 常见的数据结构:将常见数据结构封装
- 线性结构
- 特殊线性结构
- 二叉树结构(搜索树)
- 哈希
- 通用算法:与类型无关的算法,与数据结构无关,用户可定制
特点:
- 通用性强
- 效率高
六大组件
容器
线性结构的容器(序列式容器)
- string(动态顺序表,char型,也叫字符串)
- array(静态顺序表)(C++11添加进STL的)
- vector(动态的顺序表)
- list(带头节点的双向循环链表)
- forward_list(带头节点的循环单链表)(C++11添加进STL的)
- deque(双端队列)(类似于二维数组,其底层是假象的连续的空间)
- 关键式容器
- 键值对(底层为红黑树)
- 适配器(配接器):底层用容器的接口实现的模型
- stack(栈)(属于容器适配器,用 其他的结构的接口来实现)(底层是deque)
- queue(队列)(也属于容器适配器)(底层是deque)
- priority_queue(优先级队列)(底层就是一个vector+堆算法,默认为大堆,可以通过定义模板的第三个参数来调整是大堆还是小堆)
迭代器:可以让算法与数据结构无关,一般都是进行对++、–、*、->、[]等指针的操作运算符进行重载,用于遍历或者自由某个模型(vector、list等),相当于模型层面的指针
- 原生态的指针(vector迭代器)
- 封装之后中的迭代器(List迭代器)
仿函数:将对象当作函数来使用
- 定义一个类,重写了函数调用运算符来执行一个函数要执行的动作
- 使用的时候可以先构造一个对象,然后直接调用重写的运算符,即将一个对象当作一个函数来使用
迭代器
迭代器可以认为是一种设计模式,按照某种方式遍历容器
一般由实现用于迭代的对象的实现者来实现简单来说就是,例如vector的迭代器应该让实现vector的人来实现,因为其知道vector的底层模型
封装迭代器的步骤:首先将迭代器用迭代的对象的数据结构封装为迭代器,然后在原类中进行改名(注意是在public访问权限下(使用typedef,并且改为Iterator)改名,这样在外面就可以创建迭代器了),最后需要在原类中实现几个返回迭代的初始位置这样的成员函数,这样迭代器构造好对象之后就可以使用这些成员函数来赋值了
注意迭代器失效:迭代器的指针所引用的空间已经被释放
- vector的插入操作:开辟新空间,并拷贝元素,释放旧空间之后没有将迭代器即时更新
序列式容器之deque(双端队列)
可以进行头插、头删、尾插和尾删的一个伪vector
三个重要的数据结构(类)来控制deque
- 中控器
- 缓冲区
- 迭代器
双端队列相当于将多个小的定量的数组进行连接,并进行维护使在外部使用的时候像一个可变的数组一样,但是操作的时候不能用指针直接进行操作,因为指针操作的是连续的空间,而双端队列底层却是一段一段的,所以双端队列的核心技术就是封装一个完美的迭代器,用来控制访问。
数据结构模型图:
中控器(map):
- 存放的是已经开辟的各组的连续空间(数组)的顺序
- 底层可以是一个链表,也可以是一个动态顺序表
缓冲区:
- 真正存数据的地方
- 是很多段连续的空间,并且每段的空间大小都是一样的
- 每一段之间可以是相互连续的,也可以是相互不连续的(基本没有可能是连续的)
迭代器
- 相当于封装之后的指针,用来维护数据的访问
- 核心由四个字段来控制
- cur:指向当前访问的位置
- first:指向的是当前访问位置所在段连续空间的起始位置,用来控制临界区的访问
- last:指向的是当前访问位置所在段连续空间的末尾位置,用来控制临界区的访问
- node:可以认为指向map中的第几个段
- 通过者四个字段可以控制访问空间的顺序,例如当前访问的如果是1号段的末尾位置,然后迭代器
++
的时候会将访问的位置转到2号段的起始位置,如果当前访问的位置是3号段的起始位置,那么当迭代器--
操作的时候会将访问的位置转到2号段的末尾位置
双端队列不支持随机访问
- 首先是为了符合队列的性质
- 其次要实现随机访问,会将队列的数据结构复杂化
stl中栈结构和队列结构为什么要使用双端队列来实现?
- 使用顺序表的情况下,在扩容的时候需要将原数据全部拷贝至新的空间,而双端队列子啊扩容的时候并不会改变原来存放数据的位置
- 使用链表的情况下,在连续访问的时候可能访问的内存的空间不是连续的,所以效率比低,而双端队列是基本连续的,并且存储的局部性原理,使双端队列的效率大大提高
关联式容器:存键值对(key,value)
红黑树(排序效率高)
平衡因子:左右子树的高度之差
每个结点都有自己的平衡因子
AVL树(平衡二叉树): 每个结点的左右子树高度差不超过1
红黑树的性质:
- 每个结点不是红色就是黑色
- 根节点式黑色的
- 如果一个结点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
- 最长路径的结点个数不超过最短路径的结点个数的两倍(所以红黑树也是近似的平衡二叉树)
底层为红黑树的关联式容器:
- 键值对< key,data >
- map:key是唯一的
- multimap:key可以重复
- 伪键值对< data,data >
- set:data是唯一的
- multiset:data是可以重复的
基本操作
void TestMultimap()
{
multimap<string, string> m;
m.insert(pair<string, string>("李逵", "黑旋风"));
m.insert(pair<string, string>("李白", "诗仙"));
m.insert(pair<string, string>("李易峰", "大帅哥"));
m.insert(make_pair("李小龙", "快使用双截棍"));
m.insert(pair<string, string>("李逵","铁牛"));//multimap是支持key值重复的
multimap<string, string>::iterator it;//因为Multimap的key值是允许重复的,所以不能使用方括号进行查找,但可以使用迭代器来进行遍历
cout << "遍历:" << endl;
for (it = m.begin(); it != m.end(); ++it)
{
cout << it->second << " ";
}
cout << endl;
cout << "key值为李逵的value为:" << endl;
for (it = m.begin(); it != m.end(); ++it)//要想知道key值对应的所有的value,就需要用迭代器来遍历整个multimap
{
if (it->first == "李逵")
{
cout << '\t' << it->second << endl;
}
}
cout << endl << "查找:" << (m.find("李逵"))->second << endl;//通过find成员函数来进行查找
}
void TestMap()
{
map<string, string> m;
m.insert(pair<string, string>("李逵", "黑旋风"));//pair是键值对的模板类,map插入的参数是键值对
m.insert(pair<string, string>("李白", "诗仙"));
m.insert(pair<string, string>("李易峰", "大帅哥"));
m.insert(make_pair("李小龙", "快使用双截棍"));//make_pair另一种创建键值对的方式
m["李玉刚"] = "天籁之音";//直接进行插入
cout << m["李逵"] << endl;//通过key来查看value
}
void TestSet()
{
set<int> s;
int arr[] = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
s.insert( arr[i] );
}
cout << s.size() << endl;
set<int>::iterator it;
it = s.begin();
cout << "遍历:";
while (it != s.end())//结果为去重之后的arr数组,说明set具有去重功能
{
cout << *it << " ";
it++;
}
cout << endl;
}
void TestMultiset()
{
multiset<int> s;
int arr[] = { 0, 3, 1, 1, 6, 2, 2, 3, 4, 4, 5, 5, 0, 6, 7, 7, 8, 8, 9, 9 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
s.insert(arr[i]);
}
cout << s.size() << endl;
set<int>::iterator it;
it = s.begin();
cout << "遍历:";
while (it != s.end())//结果为去重之后并排序的arr数组,说明multiset具有去重并排序的功能
{
cout << *it << " ";
it++;
}
cout << endl;
}
注意插入的情况:
map的插入都是先寻找key值所在树的位置
- map:如果key不存在,插入新节点,并返回一个pair< iterator,bool >,iterator为新节点的迭代器,bool为1,代表插入成功,如果key值存在,则会返回一个pair< iterator,bool >iterator为与key值相同的结点的迭代器,bool为0,代表插入失败
- multimap:不管key值存不存在,直接插入新节点,返回新插入的结点的迭代器
注意查找的情况:
map里面的方括号所用的是insert来重载的,并不是用find方法,因为如果所查询的key值不存在,使用find将无法返回,并且使用insert的话,如果key值不存在,它就会将这个key值插入map,并且插入一个默认的value
哈希桶(查找效率高)
- 键值对< key,data >
- unoredere_dmap:key是唯一的
- unordered_multimap:key可以重复
- 伪键值对< data,data >
- unordere_dset:data是唯一的
- unordered_multiset:data是可以重复的
map和unordered_map的区别:
- 底层结构: map——>红黑树结构,unordered_map——–>哈希桶
- 存取的数列:map——>有序序列, unordered_map——–>无序序列
- 查找效率:map——>O(lgn), unordered_map——–>O(1)
- 搜索性质:map—–>二叉树的搜索性质,unoredered_map—->哈希的方式(直接定位)
- 增容:map—->需要增容,unordered_map—–>不需要增容
- 冲突:map—->不会发生冲突,unordered_map—->会发生冲突
- 空间:map比unordered_map节省空间