关联容器
map和set在存储数据时,元素的键(key)需要保证不能重复,会对key进行升序排序(从小到大);
multimap和multiset在存储元素时无上述要求。
set中键和值相同(key=value)
“键值对”并不是普通类型数据,C++ STL 标准库提供了 pair 类模板,其专门用来将 2 个普通元素 first 和 second(可以是 C++ 基本数据类型、结构体、类自定的类型)创建成一个新元素<first, second>。pair 类模板定义在头文件中,所以在使用该类模板之前,需引入此头文件。
创建C++ map容器的几种方法:
1) 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器,比如:
std::map<std::string, int>myMap;
如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。
2)当然在创建 map 容器的同时,也可以进行初始化,比如:
std::map<std::string, int>myMap{ {"C语言教程",10},{"STL教程",20} };
由此,myMap 容器在初始状态下,就包含有 2 个键值对。
再次强调,map 容器中存储的键值对,其本质都是 pair 类模板创建的 pair 对象。因此,下面程序也可以创建出一模一样的 myMap 容器:
std::map<std::string, int>myMap{std::make_pair("C语言教程",10),std::make_pair("STL教程",20)};
除此之外,在某些场景中,可以利用先前已创建好的 map 容器,再创建一个新的 map 容器。例如:
std::map<std::string, int>newMap(myMap);
由此,通过调用 map 容器的拷贝(复制)构造函数,即可成功创建一个和 myMap 完全一样的 newMap 容器。
当然,在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下,map 容器调用 std::less 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。
http://c.biancheng.net/view/7173.html
#include <iostream>
#include <map> // map
#include <string> // string
using namespace std;
int main() {
//创建空 map 容器,默认根据个键值对中键的值,对键值对做降序排序
std::map<std::string, std::string, std::greater<std::string>>myMap;
//调用 emplace() 方法,直接向 myMap 容器中指定位置构造新键值对
myMap.emplace("C语言教程","http://c.biancheng.net/c/");
myMap.emplace("Python教程", "http://c.biancheng.net/python/");
myMap.emplace("STL教程", "http://c.biancheng.net/stl/");
//输出当前 myMap 容器存储键值对的个数
cout << "myMap size==" << myMap.size() << endl;
//判断当前 myMap 容器是否为空
if (!myMap.empty()) {
//借助 myMap 容器迭代器,将该容器的键值对逐个输出
for (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout << i->first << " " << i->second << endl;
}
}
return 0;
}
C++ STL map获取键对应值的几种方法:
map 容器中存储的都是 pair 类型的键值对,但几乎在所有使用 map 容器的场景中,经常要做的不是找到指定的 pair 对象(键值对),而是从该容器中找到某个键对应的值。
- map 类模板中对[ ]运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。
#include <iostream>
#include <map> // map
#include <string> // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
{"C语言教程","http://c.biancheng.net/c/"},
{"Java教程","http://c.biancheng.net/java/"} };
string cValue = myMap["C语言教程"];
cout << cValue << endl;
return 0;
}
http://c.biancheng.net/c/
注意,只有当 map 容器中确实存有包含该指定键的键值对,借助重载的 [ ] 运算符才能成功获取该键对应的值;反之,若当前 map 容器中没有包含该指定键的键值对,则此时使用 [ ] 运算符将不再是访问容器中的元素,而变成了向该 map 容器中增添一个键值对。其中,该键值对的键用 [ ] 运算符中指定的键,其对应的值取决于 map 容器规定键值对中值的数据类型,如果是基本数据类型,则值为 0;如果是 string 类型,其值为 “”,即空字符串(即使用该类型的默认值作为键值对的值)。
#include <iostream>
#include <map> // map
#include <string> // string
using namespace std;
int main() {
//创建空 map 容器
std::map<std::string, int>myMap;
int cValue = myMap["C语言教程"];
for (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout << i->first << " "<< i->second << endl;
}
return 0;
}
C语言教程 0
- 除了借助 [ ] 运算符获取 map 容器中指定键对应的值,还可以使用 at() 成员方法。和前一种方法相比,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出 out_of_range 异常。
#include <iostream>
#include <map> // map
#include <string> // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
{"C语言教程","http://c.biancheng.net/c/"},
{"Java教程","http://c.biancheng.net/java/"} };
cout << myMap.at("C语言教程") << endl;
//下面一行代码会引发 out_of_range 异常
//cout << myMap.at("Python教程") << endl;
return 0;
}
3)除了可以直接获取指定键对应的值之外,还可以借助 find() 成员方法间接实现此目的。和以上 2 种方式不同的是,该方法返回的是一个迭代器,即如果查找成功,该迭代器指向查找到的键值对;反之,则指向 map 容器最后一个键值对之后的位置(和 end() 成功方法返回的迭代器一样)。
#include <iostream>
#include <map> // map
#include <string> // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
{"C语言教程","http://c.biancheng.net/c/"},
{"Java教程","http://c.biancheng.net/java/"} };
map< std::string, std::string >::iterator myIter = myMap.find("C语言教程");
cout << myIter->first << " " << myIter->second << endl;
return 0;
}
C语言教程 http://c.biancheng.net/c/
注意,此程序中如果 find() 查找失败,会导致第 13 行代码运行出错。因为当 find() 方法查找失败时,其返回的迭代器指向的是容器中最后一个键值对之后的位置,即不指向任何有意义的键值对,也就没有所谓的 first 和 second 成员了。
。。。
C++ STL map insert()插入数据的4种方式(略)
multimap容器
multimap 容器也用于存储 pair<const K, T> 类型的键值对(其中 K 表示键的类型,T 表示值的类型),其中各个键值对的键的值不能做修改;并且,该容器也会自行根据键的大小对存储的所有键值对做排序操作。和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。
C++ STL无序关联式容器
无序关联式容器,又称哈希容器。和关联式容器一样,此类容器存储的也是键值对元素;不同之处在于,关联式容器默认情况下会对存储的元素做升序排序,而无序关联式容器不会。
关联式容器的底层实现采用的树存储结构,更确切的说是红黑树结构;
无序容器的底层实现采用的是哈希表的存储结构。
unordered_map和unodered_set在存储数据时,元素的键(key)需要保证不能重复,key无序存储;
实际场景中如果涉及大量遍历容器的操作,建议首选关联式容器;反之,如果更多的操作是通过键获取对应的值,则应首选无序容器。
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
//创建并初始化一个 unordered_map 容器,其存储的 <string,string> 类型的键值对
std::unordered_map<std::string, std::string> my_uMap{
{"C语言教程","http://c.biancheng.net/c/"},
{"Python教程","http://c.biancheng.net/python/"},
{"Java教程","http://c.biancheng.net/java/"} };
//查找指定键对应的值,效率比关联式容器高
string str = my_uMap.at("C语言教程");
cout << "str = " << str << endl;
//使用迭代器遍历哈希容器,效率不如关联式容器
for (auto iter = my_uMap.begin(); iter != my_uMap.end(); ++iter)
{
//pair 类型键值对分为 2 部分
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
程序执行结果为:
str = http://c.biancheng.net/c/
C语言教程 http://c.biancheng.net/c/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map<string, string> umap;
//向 umap 容器添加新键值对
umap.emplace("Python教程", "http://c.biancheng.net/python/");
umap.emplace("Java教程", "http://c.biancheng.net/java/");
umap.emplace("Linux教程", "http://c.biancheng.net/linux/");
//输出 umap 存储键值对的数量
cout << "umap size = " << umap.size() << endl;
//使用迭代器输出 umap 容器存储的所有键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
程序执行结果为:
umap size = 3
Python教程 http://c.biancheng.net/python/
Linux教程 http://c.biancheng.net/linux/
Java教程 http://c.biancheng.net/java/
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<int, int> umap;
//向 umap 容器添加 50 个键值对
for (int i = 1; i <= 50; i++) {
umap.emplace(i, i);
}
//获取键为 49 的键值对所在的范围
auto pair = umap.equal_range(49);
//输出 pair 范围内的每个键值对的键的值
for (auto iter = pair.first; iter != pair.second; ++iter) {
cout << iter->first <<" ";
}
cout << endl;
//手动调整最大负载因子数
umap.max_load_factor(3.0);
//手动调用 rehash() 函数重哈希
umap.rehash(10);
//重哈希之后,pair 的范围可能会发生变化
for (auto iter = pair.first; iter != pair.second; ++iter) {
cout << iter->first << " ";
}
return 0;
}
程序执行结果为:
49
49 17
其它不再过多赘述,多看程序,掌握常用用法
哈希表练习
哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重。
哈希表采用的是一种转换思想,其中一个中要的概念是如何将「键」或者「关键字」转换成数组下标?在哈希表中,这个过程有哈希函数来完成,但是并不是每个「键」或者「关键字」都需要通过哈希函数来将其转换成数组下标,有些「键」或者「关键字」可以直接作为数组的下标。
1、给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> a;//建立hash表存放数组元素
vector<int> b(2,-1);//存放结果
for(int i=0;i<nums.size();i++)
a.insert(map<int,int>::value_type(nums[i],i));//建立哈希表
for(int i=0;i<nums.size();i++)
{
if(a.count(target-nums[i])>0&&(a[target-nums[i]]!=i))//在容器中查找以 key 键的键值对的个数。判断是否找到目标元素且目标元素不能是本身
{
b[0]=i;
b[1]=a[target-nums[i]];
break;
}
}
return b;
};
};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int>hashtable;//创建空哈希表
for(int i=0;i<nums.size();i++){//固定第一个值
auto it=hashtable.find(target-nums[i]);//在哈希表中查找第二个值,nums[i]数本身为键,i为键值<nums[i],i>;
//find(key)查找以 key 为键的 键值对,如果找到,则返回一个指向该键值对的正向迭代器;
//反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
if(it!=hashtable.end()){
return {it->second,i};//哈希表是在访问每一个i之后增加建立的,因此i的值靠后
}
hashtable[nums[i]]=i;//将每一个i以及nums[i]添加到哈希表中
}
return {};
}
};
217、给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
sort(nums.begin(), nums.end());//排序
int n = nums.size();
for (int i = 0; i < n - 1; i++) {
if (nums[i] == nums[i + 1]) {
return true;
}
}
return false;
}
};
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int>hashtable;
for(int i=0;i<nums.size();i++){
auto it=hashtable.find(nums[i]);
if(it!=hashtable.end()){
return true;
}
hashtable[nums[i]]=i;
}
return false;
}
};
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int>hashtable;
for(int i=0;i<nums.size();i++){
auto it=hashtable.find(nums[i]);
if(it!=hashtable.end()){
return true;
}
hashtable.insert(nums[i]);
}
return false;
}
};
36、请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
sizeof(array[0][0]):一个元素占用的空间,
sizeof(array[0]):一行元素占用的空间,
sizeof(array):整个数组占用的空间,
行数 = sizeof(array)/sizeof(array[0]);
列数 = sizeof(array[0])/sizeof(array[0][0]);
public:
bool isValidSudoku(vector<vector<char>>& board) {
vector<unordered_set<char>> row(9);
vector<unordered_set<char>> col(9);
vector<unordered_set<char>> bor(9);
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]=='.')continue;
if(row[i].count(board[i][j])<=0) row[i].insert(board[i][j]);
else return false;
if(col[j].count(board[i][j])<=0) col[j].insert(board[i][j]);
else return false;
if(bor[(i/3)*3+(j/3)].count(board[i][j])<=0) bor[(i/3)*3+(j/3)].insert(board[i][j]);
else return false;
}
}
return true;
}
};
128、给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
sort(nums.begin(),nums.end());
int n=nums.size();
int ans=1,t=1;
if(n==0){
return 0;
}
for(int i=1;i<n;i++){
if(nums[i-1]==nums[i]-1){
t++;
ans<t?ans=t:ans;
}
else if(nums[i-1]==nums[i]){
continue;
}
else{
t=1;
}
}
return ans;
}
};
//考虑现将数组排序,然后遍历数组,如果中断则记录对应的长度,每次的长度都要与上一次对比
//注意特殊情况,数组中有相等的元素,数组为空
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> num_set;
for (const int& num : nums) {//遍历将数组中的元素插入哈希表中
num_set.insert(num);
}
int longestStreak = 0;
for (const int& num : num_set) {//遍历哈希表
if (!num_set.count(num - 1)) {//当前元素的前一个元素(num-1)不存在时
int currentNum = num;
int currentStreak = 1;
while (num_set.count(currentNum + 1)) {//当前元素的后一个元素存在
currentNum += 1;
currentStreak += 1;
}
longestStreak = max(longestStreak, currentStreak);
}
}
return longestStreak;
}
};
73、给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m=matrix.size();//行数
int n=matrix[0].size();//列数
int flag_row0=false,flag_col0=false;
for(int i=0;i<m;i++){//验证第一列是否含零,如果含零则标记为置1
if(!matrix[i][0]){
flag_col0=true;
}
}
for(int j=0;j<n;j++){//验证第一行是否含零,如果含零则标记为置1
if(!matrix[0][j]){
flag_row0=true;
}
}
for(int i=1;i<m;i++){//从第二行、第二列开始遍历矩阵
for(int j=1;j<n;j++){
if(!matrix[i][j]){
matrix[i][0]=matrix[0][j]=false;//矩阵中存在零,则将对应的第一行和第一列中的元素置零
}
}
}
for(int i=1;i<m;i++){//从第二行、第二列开始遍历矩阵
for(int j=1;j<n;j++){
if(!matrix[i][0]||!matrix[0][j]){//检验矩阵中的第一行和第一列中标记的零位,对应行列全部置零
matrix[i][j]=0;
}
}
}
if(flag_col0){//第一列中如果有零元素,则第一列置零
for(int i=0;i<m;i++){
matrix[i][0]=0;
}
}
if(flag_row0){//第一行中如果有零元素,则第一行置零
for(int j=0;j<n;j++){
matrix[0][j]=0;
}
}
}
};
//时间复杂度O(mn),空间负载度O(1);
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();//矩阵的行数
int n = matrix[0].size();//矩阵的列数
vector<int> row(m,0);//定义含有m个元素的全零数组
vector<int> col(n,0);//定义含有n个元素的全零数组
for (int i = 0; i < m; i++) {//遍历矩阵
for (int j = 0; j < n; j++) {
if (!matrix[i][j]) {//如果下标为[i][j]的元素为零
row[i] = col[j] = true;//则将定义的行和列数组对应位置1,对其进行标记
}
}
}
for (int i = 0; i < m; i++) {//遍历矩阵
for (int j = 0; j < n; j++) {
if (row[i] || col[j]) {//如果是标记过的行列
matrix[i][j] = 0;//则将对应元素置0
}
}
}
}
};
//时间复杂度为O(mn);空间复杂度为O(m+n);
本文仅为本人刷题的部分笔记,如有错误请指出。
本文引用:
https://leetcode-cn.com/
http://c.biancheng.net/view/7166.html