STL模板库

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节省空间

你可能感兴趣的:(C++)