【C++ 第十七章】封装 unordered_map / unordered_set

2024-08-27 21:20

本文主要是介绍【C++ 第十七章】封装 unordered_map / unordered_set,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


声明:上一章讲解了 哈希结构的原理与实现,本章主要将上一章学过的拉链法的哈希结构封装进 unordered_map / unordered_set,所以需要先学过相关知识,才能更好吸收本章知识

上一章的链接:【C++ 第十六章】哈希

1. unordered_map / unordered_set 的 基本结构

之前封装 map 和 set 时,因为 map 的数据类型为 pair<key, value>,set 的数据类型为 key

则底层红黑树节点的数据类型为 T:T 可以是 pair<key, value>,也可以是 key,以此同时适配 map 和 set

在比较程序中,需要将节点数据的 key 和 其他节点的 key 比较,data < key

当 T 为 key 时,变量 T data,data 就是 key 类型数据:可以 data < key

当 T 为 pair<key, value> 时,变量 T data,data.first 才是 key 类型数据:不可以直接 data < key

因此需要各自在 map 和 set 结构中,设计仿函数 KeyOfT

(1) unordered_set 的 基本结构

namespace my
{template<class K>class unordered_set{// 仿函数:将 data 转换成 keystruct set_KeyOfT {const K& operator()(const K& key) {return key;}};public:private:hash_bucket::HashTable<K, K, set_KeyOfT> _ht;   // 直接封装一个哈希结构};
}

(2) unordered_map 的 基本结构

namespace my
{template<class K, class V>class unordered_map{// 仿函数:将 data 转换成 keystruct map_KeyOfT{const K& operator()(const pair<K, V>& kv) {return kv.first;}};public:private:// 注意要加上类域指定,否则编译器报奇怪的错误hash_bucket::HashTable<K, pair<K, V>, map_KeyOfT> _ht; m  // 直接封装一个哈希结构};
}

2. 设计哈希表迭代器

因为我们哈希表使用 拉链法的结构,因此迭代器实际上是封装链表节点指针

2.1 前置++ 功能

实现思路:哈希表迭代器 ++,即从当前有效数据节点到下一个有效数据节点,这有两种情况,一是当前链表节点不是尾节点,next 即为下一个有效数据节点;二是当前节点为尾节点,next为空,则需要往后遍历,跳到下一个单链表,继续寻找有效数据节点

如何从当前链表尾节点,到下一条链表?



我们这里采取的方法:将 “哈希表” 传入迭代器类中,通过 除留余数法 获取当前哈希桶位置 hashi(这就是哈希表数组下标),hashi ++ 就可以直接跳到下一个哈希桶中了,

伪代码:讲解思路

if (下一个节点不为空) {直接到下一个节点
}
else if (下一个节点为空) {除留余数法 获取当前哈希桶位置 hashihashi++  跳到下一个桶while (hashi < 哈希表的size) {如果下一个桶是空的,就需要一直循环,直到找到非空桶}if (hashi >= 哈希表的size:说明到最后都没有非空桶) {指针直接指向 nullptr}else if(找到非空桶){指针指向新链表}
}

实际代码

// HT 是 哈希表指针
Self& operator++() {assert(_pNode);// 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点if (_pNode->_next != nullptr) {_pNode = _pNode->_next;}else {// 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来KeyOfT kot;Hash hash;size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());hashi++;  // 这样就跳到下一个桶了// 如果下一个桶是空的,就继续跳while (hashi < (HT->_table.size())) {if (HT->_table[hashi]) break;hashi++;  // 这样就跳到下一个桶了}if (hashi >= (HT->_table.size())) {_pNode = nullptr;}else {_pNode = HT->_table[hashi];}}return *this;
}

2.2 迭代器 完整代码

一些基本的功能接口这里就不赘诉,主要是理解 前置++ 函数

// 前置声明,迭代器类中使用哈希表,程序会向上查找是否有哈希表代码实现,否则识别不了,需要提前声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;// 迭代器
template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>
class HashIterator
{typedef HashNode<T> Node;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;public:Node* _pNode;   // 链表节点指针HashTable<K, T, KeyOfT, Hash>* HT; // 获取哈希表指针// 基本的功能接口Ref operator*() {return _pNode->_data;}Ptr operator->() {return &(_pNode->_data);}bool operator!=(const Self& it) {return _pNode != it._pNode;}bool operator==(const Self& it) {return _pNode == it._pNode;}// 前置++Self& operator++() {assert(_pNode);// 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点// 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来if (_pNode->_next != nullptr) {_pNode = _pNode->_next;}else {KeyOfT kot;Hash hash;size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());hashi++;  // 这样就跳到下一个桶了// 如果下一个桶是空的,就继续跳while (hashi < (HT->_table.size())) {if (HT->_table[hashi]) break;hashi++;  // 这样就跳到下一个桶了}if (hashi >= (HT->_table.size())) {_pNode = nullptr;}else {_pNode = HT->_table[hashi];}}return *this;}HashIterator(Node* pNode, HashTable<K, T, KeyOfT, Hash>* pHT):_pNode(pNode), HT(pHT){}
};

3. 在哈希表中封装与应用迭代器

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{// 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有:迭代器类的 operator++ 函数实现中,需要访问哈希表类的 私有成员_tabletemplate <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend class HashIterator;typedef HashNode<T> Node;
public:// 定义迭代器类型 Iterator 和  Const_Iteratortypedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;// 迭代器Iterator begin() {// 先找到有效数据节点:不是每个哈希桶都有数据的for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Iterator(_table[i], this);}}Iterator end() {return Iterator(nullptr, this);} Const_Iterator begin() const {// 先找到for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Const_Iterator(_table[i], this);}}Const_Iterator end() const {return Const_Iterator(nullptr, this);}// ..... 其他函数private://也可以使用 list:双向带头循环链表//vector<list<pair<K, V>>> _table;  vector<Node*> _table;size_t _n = 0; // 负载因子Hash hash;KeyOfT kot;
};

参考STL库的写法,以及为了后续实现 operator[] ,将 find 函数的返回值改成 Iterator,将 insert 函数的返回值改成 pair<Iterator, bool> ,并且两个函数内部一些程序对应稍微修改,这里不赘述

哈希表类完整代码

实现函数:封装迭代器 begin / end 、const_begin / const_end 、插入 insert、删除 erase、查询 find 

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{// 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有:迭代器类的 operator++ 函数实现中,需要访问哈希表类的 私有成员_tabletemplate <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend class HashIterator;typedef HashNode<T> Node;
public:typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;HashTable() {// 一开始vector里面是随机值,不是 nullptr!! 要自己初始化_table.resize(10, nullptr);}// 本哈希桶需要显式写析构:因为 Node* 是内置类型,只会默认析构成 nullptr,链表节点不会被处理~HashTable() {Node* cur = nullptr;Node* next = nullptr;for (size_t i = 0; i < _table.size(); ++i) {cur = _table[i];while (cur) {next = cur->_next;delete cur;cur = next;}}}// 迭代器Iterator begin() {// 先找到for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Iterator(_table[i], this);}}Iterator end() {return Iterator(nullptr, this);}Const_Iterator begin() const {// 先找到for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Const_Iterator(_table[i], this);}}Const_Iterator end() const {return Const_Iterator(nullptr, this);}Iterator find(const K& key) {size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur) {if (kot(cur->_data) == key) return Iterator(cur, this);cur = cur->_next;}return end();}pair<Iterator, bool> insert(const T& data) {// 考虑冗余Iterator ret = find(kot(data));if (ret != end()) {cout << "该数据已存在" << '\n';return make_pair(ret, false);}// 考虑扩容:当负载比率为 1 时,扩容(即 n == size)// 甚至可以大于 1,即理想情况下,就是平均每个桶有 1个以上 if (_n == _table.size()) {HashTable<K, T, KeyOfT, Hash> newTable;newTable._table.resize(2 * _table.size());// 若直接遍历每个节点,取数值 data insert 插入新表,会导致频繁的 new 节点,造成一定消耗// 我们可以直接将旧表的节点 直接转接到 新表,省去new节点的消耗for (size_t i = 0; i < _table.size(); ++i) {Node* cur = _table[i];/*while (cur) {newTable.insert(cur->_data);cur = cur->_next;}*/while (cur) {Node* Next = cur->_next;size_t hashi = hash(kot(cur->_data)) % newTable._table.size();cur->_next = newTable._table[hashi];newTable._table[hashi] = cur;cur = Next;}_table[i] = nullptr;}_table.swap(newTable._table);}size_t hashi = hash(kot(data)) % _table.size();// 头插和尾插都行:头插最方便Node* newNode = new Node(data);newNode->_next = _table[hashi];_table[hashi] = newNode;_n++;return make_pair(Iterator(newNode, this), true);}bool erase(const K& key) {Iterator ret = find(kot(data));if (ret._pNode == nullptr) {cout << "该节点不存在" << '\n';return false;}// 删除函数需要自己找目标节点,因为底层是单链表,没有prev,删除不好搞size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur) {if (kot(cur->_data) == key) {if (prev == nullptr) _table[hashi] = cur->_next;else prev->_next = cur->_next;delete cur;cur = nullptr;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://也可以使用 list:双向带头循环链表//vector<list<pair<K, V>>> _table;  vector<Node*> _table;size_t _n = 0; // 负载因子Hash hash;KeyOfT kot;
};

4. 完善 unordered_map / unordered_set 结构

前面实现了 哈希表迭代器 与 哈希表

因此,在 unordered_map / unordered_set 类中添加这些相关操作以完善。  【迭代器 begin/end、const_begin/const_end、插入insert、删除erase、查询find  】

4.1 u_map 的 operator[] 功能

前面实现了 insert 函数的返回值为 pair<iterator, bool>

(1)若插入成功,则返回的 pair<iterator, bool> 中的 iterator 迭代器指向 新元素,bool == true

(2)若插入失败,则返回的 pair<iterator, bool> 中的 iterator 迭代器指向 已存在且数值相同的那个元素,bool == false

operator[] 返回迭代器 iterator 指向节点中数据 data.second == value

// operator[]
V& operator[](const K& key) {pair<iterator, bool> ret = _ht.insert({ key, V() });return ret.first->second;
}

4.2 关于处理 u_map / u_set 的 key 值不能修改

因为 unordered_map / unordered_set 底层使用 拉链法的哈希结构,需要通过 除留余数法 计算 key 值映射的下标位置,确定该数据的存放位置

如果修改了 u_map / u_set 的 key,则 删除或查询操作时,通过 除留余数法 计算 key 值映射的下标位置 就会出现严重错误!!比如本来 key == 19,映射的位置 hashi == 19/10 == 9,将key修改,key == 23,映射的位置就变化 hashi == 23/10 == 3,位置就变了!!

设计程序:将参数 key 使用 const 修饰,从源头上限制该类型的参数不能被修改

对于 unordered_set,需要将 哈希表 模板类型参数修改成 const K

HashTable<K, const K, set_KeyOfT, Hash> _ht;

对于 unordered_map,需要将 哈希表 模板类型参数修改成 pair<const K, V>  

HashTable<K, pair<const K, V>, map_KeyOfT, Hash> _ht;

其他某些部分也要对应修改,自行调整(通常会报错来提示你,如果不改 doge)

4.3 unordered_map 的完整代码

#pragma once
#include"HashTable.h"namespace my
{template<class K, class V, class Hash = hash_bucket::HashFunc<K>>class unordered_map{struct map_KeyOfT{const K& operator()(const pair<K, V>& kv) {return kv.first;}};public:// 重命名哈希表的迭代器typedef typename hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash>::Const_Iterator const_iterator;// 迭代器iterator begin() {return _ht.begin();}iterator end() {return _ht.end();}const_iterator begin() const {return _ht.begin();}const_iterator end() const {return _ht.end();}// 其他操作:插入删除+查找pair<iterator, bool> insert(const pair<K, V>& kv) {return _ht.insert(kv);}bool erase() {return _ht.erase();}iterator find(const K& key) {return _ht.find();}// operator[]V& operator[](const K& key) {pair<iterator, bool> ret = _ht.insert({ key, V() });return ret.first->second;}private:// 注意要加上类域指定,否则编译器报奇怪的错误hash_bucket::HashTable<K, pair<const K, V>, map_KeyOfT, Hash> _ht;};
}

4.4 unordered_set 的完整代码

#pragma once
#include"HashTable.h"namespace my
{template<class K, class Hash = hash_bucket::HashFunc<K>>class unordered_set{struct set_KeyOfT {const K& operator()(const K& key) {return key;}};public:typedef typename hash_bucket::HashTable<K, const K, set_KeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, set_KeyOfT, Hash>::Const_Iterator const_iterator;// 迭代器iterator begin() {return _ht.begin();}iterator end() {return _ht.end();}const_iterator begin() const {return _ht.begin();}const_iterator end() const {return _ht.end();}// 其他操作:插入删除+查找pair<iterator, bool> insert(const K& key) {return _ht.insert(key);}bool erase() {return _ht.erase();}iterator find(const K& key) {return _ht.find();}private:hash_bucket::HashTable<K, const K, set_KeyOfT, Hash> _ht;};
}

5. 哈希表结构 完整代码:节点+迭代器+哈希函数+哈希表

namespace hash_bucket
{template<class T>struct HashNode {typedef HashNode<T> Node;T _data;Node* _next;HashNode(const T& data):_data(data),_next(nullptr){}};template<class K>class HashFunc{		public:size_t operator()(const K& key) {return (size_t)key;}};template<>class HashFunc<string>{public:size_t operator()(const string& s) {size_t n = 0;for (auto& ch : s) {n += ch;  // 将字符串的每个字符的ASCLII码值相加起来,但这样还是不可完全避免冲突,如 abc 和 cba 的 ASCII码值总和是相等的,则 n 相等,取模之后也就冲突// 缓解冲突的一个方法:每一个字符都相乘一个 31n *= 31;}return n;}};// 前置声明,迭代器类中使用哈希表,会向上查找是否有哈希表,否则不匹配,需要提前声明template<class K, class T, class KeyOfT, class Hash>class HashTable;// 迭代器template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>class HashIterator{typedef HashNode<T> Node;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;public:Node* _pNode;HashTable<K, T, KeyOfT, Hash>* HT; // 获取哈希表指针Ref operator*() {return _pNode->_data;}Ptr operator->() {return &(_pNode->_data);}bool operator!=(const Self& it) {return _pNode != it._pNode;}bool operator==(const Self& it) {return _pNode == it._pNode;}Self& operator++() {assert(_pNode);// 逻辑是 ++ 到下一个有效元素位置:因此需要判断下一个位置是否是空节点// 因为一个链表遍历完,需要重新到另一个哈希桶,因此还需要重新更新迭代器指向,需要获取哈希表信息,则传一个哈希表指针过来if (_pNode->_next != nullptr) {_pNode = _pNode->_next;}else {KeyOfT kot;Hash hash;size_t hashi = hash(kot(_pNode->_data)) % (HT->_table.size());hashi++;  // 这样就跳到下一个桶了// 如果下一个桶是空的,就继续跳while (hashi < (HT->_table.size())) {if (HT->_table[hashi]) break;hashi++;  // 这样就跳到下一个桶了}if (hashi >= (HT->_table.size())) {_pNode = nullptr;}else {_pNode = HT->_table[hashi];}}return *this;}HashIterator(Node* pNode, HashTable<K, T, KeyOfT, Hash>* pHT):_pNode(pNode), HT(pHT){}};template<class K, class T, class KeyOfT, class Hash>class HashTable{// 将迭代器类设置成 友元,以便迭代器类可以访问该类的私有template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend class HashIterator;typedef HashNode<T> Node;public:typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;HashTable() {// 一开始vector里面是随机值,不是 nullptr!! 要自己初始化_table.resize(10, nullptr);}// 本哈希桶需要显式写析构:因为 Node* 是内置类型,只会默认析构成 nullptr,链表节点不会被处理~HashTable() {Node* cur = nullptr;Node* next = nullptr;for (size_t i = 0; i < _table.size(); ++i) {cur = _table[i];while (cur) {next = cur->_next;delete cur;cur = next;}}}// 迭代器Iterator begin() {// 先找到for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Iterator(_table[i], this);}}Iterator end() {return Iterator(nullptr, this);}Const_Iterator begin() const {// 先找到for (size_t i = 0; i < _table.size(); ++i) {if (_table[i]) return Const_Iterator(_table[i], this);}}Const_Iterator end() const {return Const_Iterator(nullptr, this);}Iterator find(const K& key) {size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi]; while (cur) {if (kot(cur->_data) == key) return Iterator(cur, this);cur = cur->_next;}return end();}pair<Iterator, bool> insert(const T& data) {// 考虑冗余Iterator ret = find(kot(data));if (ret != end()) {cout << "该数据已存在" << '\n';return make_pair(ret, false);}// 考虑扩容:当负载比率为 1 时,扩容(即 n == size)// 甚至可以大于 1,即理想情况下,就是平均每个桶有 1个以上 if (_n == _table.size()) {HashTable<K, T, KeyOfT, Hash> newTable;newTable._table.resize(2 * _table.size());// 若直接遍历每个节点,取数值 data insert 插入新表,会导致频繁的 new 节点,造成一定消耗// 我们可以直接将旧表的节点 直接转接到 新表,省去new节点的消耗for (size_t i = 0; i < _table.size(); ++i) {Node* cur = _table[i];/*while (cur) {newTable.insert(cur->_data);cur = cur->_next;}*/while (cur) {Node* Next = cur->_next;size_t hashi = hash(kot(cur->_data)) % newTable._table.size();cur->_next = newTable._table[hashi];newTable._table[hashi] = cur;cur = Next;}_table[i] = nullptr;}_table.swap(newTable._table);}size_t hashi = hash(kot(data)) % _table.size();// 头插和尾插都行:头插最方便Node* newNode = new Node(data);newNode->_next = _table[hashi];_table[hashi] = newNode;_n++;return make_pair(Iterator(newNode, this), true);}bool erase(const K& key) {Iterator ret = find(kot(data));if (ret._pNode == nullptr) {cout << "该节点不存在" << '\n';return false;}// 删除函数需要自己找目标节点,因为底层是单链表,没有prev,删除不好搞size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur) {if (kot(cur->_data) == key) {if (prev == nullptr) _table[hashi] = cur->_next;else prev->_next = cur->_next;delete cur;cur = nullptr;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://也可以使用 list:双向带头循环链表//vector<list<pair<K, V>>> _table;  vector<Node*> _table;size_t _n = 0; // 负载因子Hash hash;KeyOfT kot;};}

 unordered_map / unordered_set 的 测试代码

void test_map() {my::unordered_map<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "left", "左边" });dict.insert({ "right", "右边" });//dict["left"] = "左边,剩余";dict["insert"] = "插入";dict["string"];my::unordered_map<string, string>::iterator it = dict.begin();while (it != dict.end()) {// 不能修改first,可以修改second//it->first += 'x';it->second += 'x';cout << it->first << ":" << it->second << endl;++it;}cout << endl;
}void test_set() {my::unordered_set<string> st;st.insert({ "sort" });st.insert({ "left" });st.insert({ "right" });/*dict["left"] = "左边,剩余";dict["insert"] = "插入";dict["string"];*/my::unordered_set<string>::iterator it = st.begin();while (it != st.end()){// 不能修改*it//*it += 'x';cout << *it <<  endl;++it;}cout << endl;
}

这篇关于【C++ 第十七章】封装 unordered_map / unordered_set的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112812

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

poj 3050 dfs + set的妙用

题意: 给一个5x5的矩阵,求由多少个由连续6个元素组成的不一样的字符的个数。 解析: dfs + set去重搞定。 代码: #include <iostream>#include <cstdio>#include <set>#include <cstdlib>#include <algorithm>#include <cstring>#include <cm