博客备份

知识点整理——STL与容器

收录时间:2022.03.08

点击此处展开查看内容 [TOC]

1. \(\tt{STL}\) 概论

STL容器是指由官方提供的用于存放数据的类模板。为了加强其可用性,一般会集成众多成员函数,故它们都具有极强的实用性与拓展性,是平时竞赛中常会被使用到的工具。下文就一部分STL容器及其功能函数做出解释。


2. 通用方法之比较

任何两个容器对象,只要它们的类型相同,就可以用< <= > >= == != 进行比较运算。规则如下:

  • a == b:若 a 和 b 中的元素个数相同,且对应元素均相等,则a == b的值为 true,否则值为 false。
  • a < b :规则类似于词典中两个单词比较大小,从头到尾依次比较每个元素,如果
    • \(a\) 中元素小于 \(b\) 中元素,则 \(a < b\) 的值为 \(true\)
    • \(a\) 中元素个数比 \(b\) 少,\(a < b\) 的值也为 \(true\)
    • 其他情况下值为 \(false\)

3. 通用方法之成员函数

通用的构造函数

  • 容器类型<元素类型> 容器名称;:直接构造。构造空容器。
  • 容器类型<元素类型> 容器名称 ({元素内容, 元素内容, ……});:利用数组进行构造。
  • 容器类型<元素类型> 容器名称 (另一个已有容器的名称);:复制构造。利用另一个已有的同类型容器构造一个独立复制品。

通用的容量操作

  • \(\tt{}size()\) :返回容器中元素的数量。
  • \(\tt{}empty()\) :判断容器对象是否为空。

通用的遍历操作

对于顺序容器( \(\tt{}vector\) )、关联容器( \(\tt{}set、multiset、map、multimap\) ),均有以下功能函数:

  • \(\tt{}begin()\) :返回第一个元素的迭代器。
  • \(\tt{}end()\) :返回容器的尾部。注意:直接使用 *a.end() 是越界访问。

通用的元素操作之读取

  • \(\tt{top()}\) :对于栈,返回头部元素。
  • \(\tt{}front()\) :对于队列、关联容器,返回第一个元素。
  • \(\tt{}back()\) :对于队列、关联容器,返回最后一个元素。

通用的元素操作之插入

  • \(\tt{}push()\) :对于栈、队列这种数据结构,用于在头部向容器内压入元素。
  • \(\tt{}insert()\) :对于顺序容器, \(insert()\) 允许你在任何位置进行插入操作;而对于关联容器,这一函数会先寻找合适位置,再将数据插入(保证容器有序)。
  • \(\tt{}push\_back()\) :对于顺序容器,用于尾部插入。
  • \(\tt{}emplace()\) :【C++11】这一函数是 \(insert()\)\(push()\) 的完全上位替代。用法一致,效率高一倍。
  • \(\tt{}emplace\_back()\) :【C++11】这一函数是 \(push\_back()\) 的完全上位替代。

通用的元素操作之删除

  • \(\tt{}pop()\) :对于栈、队列这种数据结构,用于弹出头部元素。
  • \(\tt{}erase()\) :对于顺序容器, \(insert()\) 允许你进行单点及区间删除;而对于关联容器,只能对单点删除。
  • \(\tt{}pop\_back()\) :对于顺序容器,用于尾部删除。
  • \(\tt{}clear()\) :清空容器。

4. 迭代器 \(\tt{iterator}\)

迭代器可以指向容器中的某个元素,借助迭代器可以读写它指向的元素。与指针类似。

  • 容器名::iterator 迭代器名;:为容器创建一个正向迭代器。对其进行++操作时,迭代器指向下一个元素。
  • 容器类名::reverse_iterator 迭代器名;:为容器创建一个反向迭代器。对其进行++操作时,迭代器指向上一个元素。

例如以下代码块,即是利用迭代器,完成了对vector容器内所有元素的遍历:

vector<int>::iterator it;
for(it = v.begin(); it != v.end(); it ++) {
    cout << *it << endl;
}

5. 使用 \(\tt{auto}\) 关键字代替迭代器

注意:需要C++11及以上版本的编译器才支持这一函数

在C++11及以上版本中, \(auto\) 关键字的加入使得迭代器的使用被大大简化。使用 \(auto\) 翻译上方迭代器代码,格式如下:

for(auto it = v.begin(); it != v.end(); it ++) {
    cout << *it << endl;
}

以上的代码块还能被进一步简化,如下:

for(auto it : v) {
    cout << it << endl;
}

6. \(\tt{pair}\) 的用法

类似于一个结构体,让你能够快速的将两个单独的元素绑在一起成为一个合成元素。
常常用于 \(\tt{}map\) 容器的“下标-元素”对。详见下文 \(\tt{}map\) 容器详解。

构造函数

  • pair<元素类型, 元素类型> P;构造空 \(\tt{}pair\)
  • pair<元素类型, 元素类型> P (元素内容, 元素内容);:数据构造。
  • 使用花括号可以替代 make_pair 用法,如下例。
vector<pair<string, int> > v;
v.push_back({"WIDA", 23});

元素操作:读取

\(\tt{}pair\) 中只有两个元素,分别是 \(\tt{}first\)\(\tt{}second\) ,只需要按照正常读取结构体的方式读取即可。

【C++17】也可以直接进行读取(同 \(\tt{}tuple\) )。


元组 \(\tt{tuple}\) 的用法

【C++11】在C++11版本引入的全新类型,可类比于 \(\tt{pair}\) ,差别在于 \(\tt{pair}\) 只支持两个元素,而元组在理论上支持 \(3\) 到无穷多个元素;亦可类比于结构体,较之更简洁、直观。

构造函数

  • tuple<int, int, int, int> T;:构造新元组。

元素操作:读取

使用 get<index>(obj) 可以获取 \(\tt{obj}\) 对象中的第 \(obj\) 个元素,如下例。但是需要注意的是, \(\tt{index}\) 只能手动输入,而不支持动态输入(例如,用 \(\tt{for}\) 循环输入是不可以的)。

【C++17】使用 \(\tt{auto}\) 可以取出容器中的三元组,如下例。


7. 容器适配器——栈 \(\tt{}stack\)

遵循先进后出(FILO)的数据结构。只能在一端(称栈顶,top)对其内部元素进行操作。

不允许遍历行为。若需清空栈,需要逐个弹出。

构造函数

  • stack<元素类型> S;:构造空容器。
  • stack<元素类型> S ({元素内容, 元素内容, ……});:数组构造。在后面的元素会先被弹出。
  • stack<元素类型> S1 (S);:复制构造。生成容器 \(S\) 的独立复制品 \(S1\)。在此句之前,应当已定义过 \(S\)

容量操作

  • int len=S.size();:返回容器大小。
  • S.empty():判断是否为空。

元素操作

  • S.push(元素内容):向栈顶添加一个元素。
  • S.emplace(元素内容):向栈顶添加一个元素。相较 \(\tt{}push()\) 效率更高。
  • S.top();:返回栈顶元素。
  • S.pop():弹出栈顶元素。

8. 容器适配器——循环队列 queue

遵循先进先出(FIFO)的数据结构。元素自队尾进入,队头删除。

不允许遍历行为。若需清空队列,需要逐个弹出。

构造函数

  • queue<元素类型> Q;:构造空容器
  • queue<元素类型> Q ({元素内容, 元素内容, ……});:数组构造。
  • queue<元素类型> Q1 (Q);:复制构造。生成容器 \(Q\) 的独立复制品 \(Q1\) 。在此句之前,应当已定义过 \(Q\)

容量操作

  • int len=Q.size();:返回容器大小
  • Q.empty():判断是否为空

元素操作

  • Q.push(元素内容):向头添加一个元素.
  • Q.emplace(元素内容):向头添加一个元素。相较 \(push()\) 效率更高。
  • Q.front();:返回头部元素。
  • Q.back();:返回尾部元素。
  • Q.pop():弹出队头元素。

9. 容器适配器——优先队列 \(\tt{}priority\_queue\)

与普通队列 \(queue\) 不同的是, \(priority\_queue\) 是有序的,元素放入后会按照优先度(默认升序)进行排序。内置的 \(int\)\(string\) 类型本身就可以比较大小,若要使用自定义的结构体类型,则需要重载运算符 “<”。

构造函数

  • priority_queue<元素类型> P;:构造升序空容器
  • priority_queue<int, vector<int>, less<int> > P;:内置升序排序。
  • priority_queue<int, vector<int>, greater<int> > P;:内置降序排序。
  • priority_queue<元素类型> P1 (P);:复制构造。

容量操作

  • int len=P.size();:返回容器大小
  • P.empty():判断是否为空

元素操作

  • P.push(元素内容):向队首添加一个元素
  • P.emplace(元素内容):向队首添加一个元素。相较 \(push()\) 效率更高。
  • P.top();:返回队首元素。
  • P.pop():弹出队首元素。

重载运算符


10. 序列式容器——双向队列 deque

可以看作是普通队列 \(queue\) 的加强版,双向队列可以对头尾元素快速地进行插入和删除操作。

像数组、 \(vector\) 容器一样,支持随机访问(即支持 [] 操作符和 at() 操作, \(O(1)\) ),但性能没有 \(vector\),且相较于 \(vector\) 占用更多内存

允许在内部进行插入和删除操作,但性能不及 \(list\)

构造函数

  • deque<元素类型> D;:构造空容器
  • deque<元素类型> D (N, 元素内容);:数据构造。 \(D\) 容器初始含有 \(N\) 个相同元素。
  • deque<元素类型> D ({元素内容, 元素内容, ……});:数组构造。在前面的元素会先被弹出。
  • deque<元素类型> D1 (D);:复制构造。生成容器 \(D\) 的独立复制品 \(D1\) 。在此句之前,应当已定义过 \(D\)

容量操作

  • int len=D.size();:返回容器大小
  • D.empty():判断是否为空

元素操作

  • D.pop_back(元素内容):弹出尾部元素。
  • D.pop_front(元素内容):弹出头部元素。
  • D.push_front(元素内容):向头添加一个元素。
  • D.emplace_front(元素内容):向头添加一个元素。相较 \(push()\) 效率更高。
  • D.push_back(元素内容):向尾添加一个元素。
  • D.emplace_back(元素内容):向尾添加一个元素。相较 \(push()\) 效率更高。
  • D.clear():清空。

11. 序列式容器——可变长度数组 \(\tt{}vector\)

相对普通数组, \(\tt{}vector\) 数组的长度不定,故对于随机数据,其内存占用会大大下降。
就CF日常赛来看,世界顶级大佬多数都是使用 \(\tt{}vector\) 数组来完全替代普通数组。大家也可以尝试逐步替代。

构造函数

  • vector<元素类型> V;:构造空容器。
    vector<long long> V
  • vector<元素类型> V (N, 元素内容);:数据构造。初始含有 \(N\) 个相同元素,且初值给定。
    vector<bool> V (10, true);
  • vector<元素类型> V ({元素内容, 元素内容, ……});:数组构造。
  • vector<元素类型> V ({元素内容, 元素内容, ……});:数组构造。

  • vector<元素类型> V1 (V):复制构造。生成容器 \(V\) 的独立复制品 \(V1\)。在此句之前,应当已定义过 \(V\)

  • vector<元素类型> V2 (V.开始位置, V.结束位置);:范围复制构造。生成 \(V\) 容器 \([begin,end]\) 区间的独立复制品 \(V2\)

  • 容量操作

  • int len = V.size();:返回容器大小。

  • V.empty();:判断容器是否为空。

    元素操作:插入

  • V.push_back(元素内容);:尾部插入。

  • V.emplace_back(元素内容);:尾部插入。相较 \(push\_back()\)\(insert()\) 效率更高。

  • V.emplace(插入位置,元素内容);:在指定位置处插入。相较 \(push\_back()\)\(insert()\) 效率更高。

  • V.insert(插入位置, 元素内容);:在指定位置处插入。

  • V.insert(插入位置, 重复数量, 元素内容);:在指定位置处插入 \(N\) 个相同元素。

    元素操作:读取

  • V.front() :返回第一个元素,等价于 *V.begin()V[0]

  • V.back() :返回最后一个个元素,等价于 *--V.end()V[V.size()-1]

    元素操作:删除

  • V.pop_back();:尾部删除。

  • V.erase(删除位置);:单点删除。删除指定位置处元素。

  • V.erase(删除位置1, 删除位置2);:区间删除。删除 \([begin,end]\) 区间的元素。

  • V.clear();:一键清空。


12. 字符串 string 容器

构造函数

  • string S;:构造空容器。
  • string S (元素内容);:数据构造。
  • string S1 (S);:复制构造。生成容器 \(S\) 的独立复制品 \(S1\)。在此句之前,应当已定义过 \(S\)
  • string S1 (S.开始位置, S.结束位置);:范围复制构造。生成 \(S\) 容器 \([begin,end]\) 区间的独立复制品 \(S2\)
  • string S1 (S, 开始位置下标, 长度);:范围复制构造。生成字符串 \(S[pos,pos+len)\) 区间的独立复制品 \(S2\)
  • S="xxx";:重赋值.

容量操作

  • int len=S.size();:返回容器大小。

  • int len=S.length();:返回容器大小。

  • S.empty();:判断容器是否为空。

    元素操作:插入

  • S.push_back(元素内容);:尾部插入单个字符

  • S.insert(插入位置, 元素内容);:在指定位置处插入单个字符

  • S.insert(插入位置下标, 元素内容);:在指定位置处插入字符串。

  • S.insert(插入位置, 重复数量, 元素内容);:在指定位置处插入 \(N\)字符

  • S.append(元素内容);:将元素(字符串或字符)拼接到尾部。

  • S.append(重复数量, 元素内容);:将 \(N\) 个字符拼接到尾部。

    元素操作:删除

  • S.erase(删除位置);:单点删除。删除指定位置处字符

  • S.erase(删除位置下标);:区间删除。删除指定位置及其之后的全部元素。

  • S.erase(删除位置1, 删除位置2);:区间删除。删除 \([begin,end]\) 区间的元素。

    元素操作:查找

  • int index=S.find(元素内容);:查找元素(字符串或字符),并返回第一次出现的下标。若不存在,返回字符串最大可开大小。

  • int index=S.find(元素内容, 开始位置下标);:从指定位置开始查找元素(字符串或字符),并返回第一次出现的下标。若不存在,返回字符串最大可开大小。

  • int index=S.rfind(元素内容);:逆序查找元素(字符串或字符),并返回第一次出现的正序下标。若不存在,返回字符串最大可开大小。

  • int index=S.rfind(元素内容, 开始位置下标);:从指定位置开始向前查找元素(字符串或字符),并返回第一次出现的正序下标。若不存在,返回字符串最大可开大小。

    元素操作:其他

  • sort(S.开始位置, S.结束位置);:对 \([pos1,pos2]\) 区间的元素快速排序。


13. 关联式容器——有序集合 set

\(set\) 容器基于红黑树,故其能时刻对数据自动排序(默认为升序),保证数据有序。搜索的时间复杂度仅为 \(Olog_2n\) ,十分的高效有用。

\(set\) 容器的第二个特点是其保证元素的唯一性,置入其中的元素会被自动去重

除此之外,与 \(map\) 这种关联容器不同, \(set\) 容器只能对单点操作(不允许手写 S.begin()+3 这种涉及迭代器加减法的式子),故相较于前面的一些容器, \(set\) 容器少了非常多成员函数。请详细阅读下文介绍,并将其与其他容器相区分。

构造函数

  • set<元素类型> S;:构造空容器。
  • set<元素类型,greater<> > S;:内置降序排序。
  • set<元素类型> S ({元素内容, 元素内容, ……});:数组构造。
  • set<元素类型> S1 (S):复制构造。生成容器 \(V\) 的独立复制品 \(V1\)。在此句之前,应当已定义过 \(V\)

容量操作

  • int len=S.size();:返回容器大小。
  • S.empty();:判断容器是否为空。

元素操作:插入

  • S.insert(元素内容);:尾部插入。时间复杂度为 \(O(log_2n)\)
  • S.emplace(元素内容);:尾部插入。相较 \(insert()\) 效率更高。

元素操作:删除

  • S.clear();:一键清空。

  • S.erase(迭代器);:单点删除。删除迭代器指向的元素。

  • S.erase(元素内容);:单点删除。

  • 连续头部删除:使用滚动 \(\tt{}auto\ it\) 实现,如下例:

    错误用法如下:

元素操作:查找

  • S.find(元素内容);:查找元素,并返回第一次出现时的迭代器。若不存在,返回 \(S.end()\)
  • S.rfind(元素内容);:逆序查找元素,并返回第一次出现时的迭代器。若不存在,返回 \(S.end()\)
  • S.lower_bound(元素内容);:返回第一个 \(\geqslant\) 目标元素的迭代器。
  • S.upper_bound(元素内容);:返回第一个 \(>\) 目标元素的迭代器。

元素操作:读取

  • S.begin();:返回容器中最小元素的迭代器。
  • S.end();:返回容器中最大元素的下一个位置的迭代器。

元素操作:其他

  • S.count(元素内容):查询容器中是否含有这一元素。
  • S.swap(S1):容器元素互换。

迭代器

\(set\)\(multiset\) 迭代器比较特殊,称之为“双向访问迭代器”,其不支持随机访问,仅支持 “++” 和 “--” 两个算术运算操作。如果将迭代器 it++ ,则 it 会指向经过排序之后的下一个元素,而非简单的遍历。这两个算术运算操作的时间复杂度均为 \(O(log_2n)\)

自定义排序函数重载

struct cmp{
    bool operator()(const int &a,const int &b){
        return a>b;//按元素值降序排序
    }
};
set <int,cmp> S;

14. 有序多重集合 multiset 容器

与普通集合 \(set\) 最大的不同是, \(multiset\) 不去重
\(set\) 存在不同的函数

  • S.erase(元素内容):删除容器中所有目标元素。
  • S.count(元素内容):返回容器中目标元素数量。

15. unordered_set 容器

注意:需要C++11及以上版本的编译器才支持这一函数
与普通集合 \(set\) 的不同是, \(unordered\_set\) 内部元素是无序的,且函数时间复杂度更低【平均可达 \(O(1)\) ,最坏为 \(O(n)\) 】,而相应的代价是会消耗较多内存。
在底层代码层面上,由于 \(unordered\_set\) 基于哈希表,不能直接和不带哈希函数的其他容器共用(如 \(vector\) ),对 \(vector\) 定义哈希函数代码如下:

namespace std {
    template<>
    struct hash<std::vector<int>> {
        size_t operator()(const vector<int> &v) const {
            std::hash<int> hasher;
            size_t seed = 0;
            for (int i : v) {
                seed ^= hasher(i) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
            }
            return seed;
        }
    };
}
int main(){
    unordered_set<vector<int> > S;
    return 0;
}

16. unordered_multiset 容器

注意:需要C++11及以上版本的编译器才支持这一函数
与普通集合 \(set\) 的不同是, \(unordered\_multiset\) 不排序、不去重


17. bitset 容器

可以看作其为一个多位二进制数(对二进制进行状态压缩),这一容器将会把你的数据自动转换成二进制(每一位均为 \(0\)\(1\) 的整数,而不是字符串)并进行进一步运算。一般来说,使用 \(bitset\) 容器可以是你的算法复杂度 \(/64\) (如果你的电脑是64位)。而对于评测机,这一理论值甚至可以减少到 \(/128\)

构造函数

\(bitset\) 容器可以接受仅带 \(0\) \(1\) \(string\) 类型和整数类型。

  • bitset<容器长度> B; :构造空容器。

  • bitset<容器长度> B (字符串); :基于您给出的字符串构造容器。

  • bitset<容器长度> B = 整型 :基于您给出的整型构造容器。

  • bitset<容器长度> B (整型) :基于您给出的整型构造容器。

  • 应当注意的是,进入容器后会被倒序排列,并且会在高位补上 \(0\)bitset<6> B = 8 ,数组 \(B[5]\)\(B[0]\) 输出依次为 0 0 0 1 0 0

    容量操作

  • B.size();:返回容器大小(位数)。

    元素操作

  • ~B:将容器内全部元素按位取反。

  • B.set():将所有位变 \(1\)

  • B.set(下标内容):将指定位变 \(1\)

  • B.set(下标内容, 元素内容):将指定位变指定内容。

  • B.reset():将所有位变 \(0\)

  • B.reset(下标内容):将指定位置 \(0\)

  • B.flip():将容器内全部元素取反。

  • B.filp(下标内容):对指定位置取反。

  • B.to_ulong():重新转换为 \(unsigned\ long\) 类型的结果,如果超出范围则报错。

  • B.to_ullong() 重新转换为 \(unsigned\ long\ long\) 类型的结果,如果超出范围则报错

  • B.to_string() 重新转换为 \(string\) 类型的结果,如果超出范围则报错。

  • []:可以用于取值,也可以赋值。

  • B.count():返回有多少位为 \(1\)

    位运算

    除了上述提到的所有元素操作函数之外, \(bitset\) 还支持全部的位运算,但是需要位数相同。

举例如下:

int main(){
  bitset<23>B1 (string("11101001"));
  bitset<23>B2 (string("11101000"));
  cout<<(B1 ^ B2)<<"\n";//按位异或
  cout<<(B1 | B2)<<"\n";//按位或
  cout<<(B1 & B2)<<"\n";//按位与
  cout<<(B1 << 5)<<"\n";//左移5位
  cout<<(B2 >> 5)<<"\n";//右移5位
  cout<<(B1 == B2)<<"\n";//比较是否相等
  return 0;
}

18. 关联式容器— \(\tt{}map\)

相较于普通数组, \(\tt{}map\) 容器允许你使用任何类型的数据作为下标

\(\tt{}set\) 容器相同, \(\tt{}map\) 同样建立于红黑树之上,只能对单点操作,保证所有数据有序(默认按照下标升序排序),且保证不存在重复元素。查询时间复杂度为 \(\mathcal{O}log_2n\)

注意:一旦 \(map\) 中的一个元素被使用 [] 访问,无论此前是否被赋值,它都被视为已经存在。例如:使用 if(M["abc"]); 查询元素是否存在,则会被自动生成一个二元组 ("abc",zero),此时再使用 cout<<M.count("abc"); 答案是存在。时间一长,就会出现非常多“零值二元组”,白白占用了空间。强烈建议在使用 [] 访问前,先用 find() 方法检查key值的存在性

构造函数

  • map<下标类型, 元素类型> M;:构造空容器。
  • map<下标类型, 元素类型> M3({{下标,元素},{下标,元素},{……}});:数组构造。
  • map<下标类型, 元素类型> M1 (M);:复制构造。
  • map<下标类型, 元素类型> M2 (M.开始位置, M.结束位置);:范围复制构造。

容量操作

  • int len=M.size();:返回容器大小
  • M.empty():判断是否为空

元素操作:插入

注意:由于 \(\tt{}map\) 数组元素唯一性,在使用 \(\tt{}insert\) 进行插入操作时,若新的下标已经在容器内,则直接无视这次操作;而用数组的方式则可以。

  • M.insert({下标内容,元素内容});:使用花括号构建二元组插入(也支持 \(\tt{}pair\) ,此处略)。当下标重复时无法使用(如下图)

  • M.emplace(pair<下标类型, 元素类型>(下标内容,元素内容));:使用 \(\tt{}pair\) 插入,但不支持花括号构建二元组。相较 \(\tt{}insert()\) 效率更高。当下标重复时无法使用

元素操作:直接修改

  • 如同普通数组, \(\tt{}map\) 可以使用方括号 \([]\) ,该操作可以直接覆盖已有下标(与函数插入不同)。

元素操作:删除

  • M.earse(下标值);:通过下标删除元素。

  • M.clear();:清空。

元素操作:查找

  • S.find(元素内容);:查找元素,并返回第一次出现时的迭代器。若不存在,返回结尾地址( \(\tt{}S.end()\) )。

  • S.rfind(元素内容);:逆序查找元素,并返回第一次出现时的迭代器。若不存在,返回结尾地址。

  • M.lower_bound(元素内容);:返回第一个 \(\geq\) 目标元素的迭代器。

  • M.upper_bound(元素内容);:返回第一个 \(>\) 目标元素的迭代器。

元素操作:其他

  • M.count(元素内容):查询容器中是否含有这一元素。
  • M.swap(M1);:容器元素互换。

迭代器

\(set\) 容器的迭代器一样,也是“双向访问迭代器”。

重载函数为按下标降序排序

struct cmp{
    bool operator()(const int x,const int y){
        return x>y;//按照下标值降序排列
    }
};
map <int,char,cmp> M;

重载函数为按元素值大小排序

bool cmp_by_value(const PAIR &x, const PAIR &y) {
    return x.second>y.second;//按元素值降序排列
    //return x.second<y.second;//按元素值升序排列
}
sort(v.begin(), v.end(), cmp_by_value);

19. multimap 容器

与普通 \(map\) 的不同是, \(multimap\) 不去重


20. unordered_map 容器

与普通 \(map\) 的不同是, \(unordered\_map\) 不排序,使用哈希实现。单次操作时间接近 \(\mathcal{O}(1)\)


21. unordered_multimap 容器

与普通 \(map\) 的不同是, \(unordered\_map\) 不排序、不去重


22. STL算法——快速排序 sort

库自带的快速排序。可以使用 \(\tt{Lambda}\) 函数简化自定义函数,如下例,即达到了降序排序的效果。

vector<int> V(100);
for(auto &it : V) cin >> it;
sort(a.begin(), a.end(), [] (int x, int y) {
    return x > y;
});

23. STL算法——查找有序序列的 lower_bound 和 upper_bound

其原理是在给定的范围内进行二分查找。与容器中的功能函数类似,区别在于增加了开始和结束参数,需要自己设定查找范围;且需要保证区间单调默认为不下降序列

若在数组中使用,为了得到数组下标而不是地址,在末尾需要减去起始位置。

因为查找区间是前开后闭的,当区间内全部内容全部相同时,使用 upper_bound() 会返回结束位置,有可能出现越界情况。

构造函数

  • lower_bound(开始位置, 结束位置, 元素内容):在 [开始位置, 结束位置) 区间内查找,返回第一个 \(\geq\) 目标元素的地址。
  • upper_bound(开始位置, 结束位置, 元素内容):在 [开始位置, 结束位置) 区间内查找,返回第一个 \(>\) 目标元素的地址。

重载函数为小于、小于等于目标元素

  • lower_bound(begin, end, num, greater<type>())
  • upper_bound(begin, end, num, greater<type>())

使用方法

  • cout<<lower_bound(a,a+n,x)-a<<endl;:在 \(a\) 数组中查找 \(x\) 第一次出现时的迭代器,如要得到下标,需要减去起始位置(即数组名字)。

常见玩法

  • upper_bound(a,a+n,k)-lower_bound(a,a+n,k):查找 \(k\) 在有序数组 \(a\) 中出现了几次

24. STL算法——全排列算法 next_permutation 和 prev_permutation

在提及这个函数时,我们先需要补充几点字典序相关的知识。

对于三个字符所组成的序列{a,b,c},其按照字典序的6种排列分别为:
{abc}{acb}{bac}{bca}{cab}{cba}
其排序原理是:先固定 a (序列内最小元素),再对之后的元素排列。而 b < c ,所以 abc < acb 。同理,先固定 b (序列内次小元素),再对之后的元素排列。即可得出以上序列。

next_permutation 算法,即是按照字典序顺序输出的全排列;相对应的, prev_permutation 则是按照逆字典序顺序输出的全排列。可以是数字,亦可以是其他类型元素。其直接在序列上进行更新,故直接输出序列即可。

使用方法1:输出一组数据的全排列

#include<bits/stdc++.h>
using namespace std;
int a[4]={1,2,3,4};
int main(){
    sort(a,a+4);//注意,此行的存在是为了保证输出完整的全排列,若只需要给定排序之后(或之前)的一部分,则删去此行。
    do{
        for(int i=0;i<4;i++) cout<<a[i]<<" ";
        cout<<endl;
    }while(next_permutation(a,a+4));
    return 0;
}

使用方法2:输出一组数据的第 \(k\) 个排列

#include<bits/stdc++.h>
using namespace std;
int a[7]={1,2,3,4,5,6,7},k,num;
int main() {
    sort(a,a+7);
    cin>>k;
    do{
        if(num==k){
            for(int i=0;i<7;i++) cout<<a[i]<<" ";
            cout<<endl;
            break;
        }
        num++;
    }while(next_permutation(a,a+7));
    return 0;
}

25. STL算法——数组反转函数 reverse、随机打乱函数 random_shuffle

将一个数组、顺序容器中的全部元素反转、打乱。

使用方法

  • reverse(a, a+n);:反转数组。
  • reverse(V.begin(), V.end());:反转 \(vector\) 容器内的数据。

26. STL算法——数组去重函数 unique

此函数在使用前,需确保对象已经排序

其作用是,对于区间 [开始位置, 结束位置)不停的把后面不重复的元素移到前面来,也可以说是用不重复的元素占领重复元素的位置。并且返回去重后容器中不重复序列的最后一个元素的下一个元素。所以在进行操作后,数组、容器的大小并没有发生改变。

参见下方图片:
image

使用方法

  • unique(a, a+n);:对数组去重。
  • unique(V.begin(), V.end());:对 \(vector\) 容器内的数据去重。

常见玩法

可以将 \(unique\) 函数和 \(earse\) 函数相结合,达到完全删去重复元素的目的,如下方代码

    auto it=unique(V.begin(), V.end());
    V.erase(it,V.end());

image


27. C++位运算函数——_builtin

GCC提供了一系列的builtin函数,可以实现一些简单快捷的功能来方便程序编写。

使用方法

  • __builtin_popcount(unsigned int x):返回二进制下 \(1\) 的个数。
    int n = 15; //二进制为1111
    cout<<__builtin_popcount(n)<<endl;//输出4
    
  • __builtin_ffs(unsigned int x):返回二进制下最后一个 \(1\) 的位置(从1开始计算)。
    int n = 1;//1
    int m = 8;//1000
    cout<<__builtin_ffs(n)<<endl;//输出1
    cout<<__builtin_ffs(m)<<endl;//输出4
    
  • __builtin_ctz(unsigned int x):返回二进制下后导 \(0\) 的个数(当x为0时,和x的类型有关)。
    int n = 1;//1
    int m = 8;//1000
    cout<<__builtin_ctzll(n)<<endl;//输出0
    cout<<__builtin_ctz(m)<<endl;//输出3
    

注:以上函数的 \(long\ long\) 版本只需要在函数后面加上 ll 即可, \(unsigned\ long\ long\) 同理。


28. 字符串转换为数字函数——sto

【C++11】C++11提供了一些新的字符串转换函数,可以快捷的将一串字符串转换为指定进制的数字

使用方法

  • stoi(字符串, 0, x进制) :将一串 \(x\) 进制的字符串转换为 \(int\) 型数字。

  • stoll(字符串, 0, x进制) :将一串 \(x\) 进制的字符串转换为 \(long\ long\) 型数字。
  • stoull,stod,stold同理。

附录:参考

[1]. Bitset用法小结
[2]. pair常见用法详解
[3]. C++11中emplace的使用
[4]. C++11新特性emplace操作
[5]. 关于lower_bound( )和upper_bound( )的常见用法
[6]. 【用法总结】C++ STL中 next_permutation函数的用法
[7]. C++ reverse函数的用法
[8]. C++STL中的unique函数解析
[9]. c++高效位运算函数之 _builtin
[10]. [洛谷日报第79期]二进制与位运算
[11]. c++中的atoi()和stoi()函数的用法和区别


本文尚处于施工修改中,各位过路的大佬神犇,若有任何的批评建议,欢迎于站内私信或各大社交媒体平台向我反馈,感激不尽!

文 / WIDA
2022.02.??优化了全文框架,增加了一定的图片样例
2022.01.17优化了 \(\tt{stoi()}\) 的内容
2021.12.18修改了bitset容器的部分内容
2021.11.06新增位运算函数builtin
2021.11.04修改部分内容
2021.09.05修改部分内容
2021.08.18成文
首发于WIDA个人博客,仅供学习讨论

posted @ 2022-03-08 20:36  hh2048  阅读(44)  评论(0)    收藏  举报