【C++】快速上手STL:map和set

目录

一、关联式容器和键值对

二、set的介绍

三、set的使用

四、map的介绍

五。map的使用

总结:


一、关联式容器和键值对

在学习如何使用set和map之前,我们先来了解什么是关联式容器

  • vector、list、deque、 forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身
  • 关联式容器也是用来存储数据,但他存储的不是数据本身,里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

而键值对是一个用来表示一一对应关系的数据结构,它一般包含两个成员变量key和value,key代表键值,value表示与key对应的信息。我们通常用pair来表示一个键值对的数据结构。

template <class T1, class T2>
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;

    T1 first;
    T2 second;

    pair(): first(T1()), second(T2())
    {}

    pair(const T1& a, const T2& b): first(a), second(b)
    {}
};

二、set的介绍

  • set它是一个底层由二叉搜素树来实现的容器,它具有排序+去重的功能。我们在使用迭代器去遍历它的时候,可以得到一个有序的数组。它里面的元素是value,底层是一个<value,value>的键值对,我们平常在使用的时候,只需要传一个value即可。
  • 它的value是都是用const修饰的,所以它的元素不能被修改,但可以进行插入和删除。我们在插入的时候插入多个相同的value,它会帮我们自动去重。
  •  set搜素的数据的时间复杂度为log_2(N);默认按照小于来比较。

三、set的使用

通过查文献,我们看到set的模板参数里面有一个T变量,一个compare,默认是用less表示我们的变量按从小到大的顺序排列,而Alloc是一个适配器,默认是使用allocator,用于分配和释放内存。

这个通常已经满足了我们很大部分的需求,所以我们一般使用的时候可以不用关心


  1. set的初始化
#include<iostream>
#include<set>

using namespace std;

int main ()
{
    //默认构造
    std::set<int> first;                          
    
    //范围构造
    int myints[]= {10,20,30,40,50};
    std::set<int> second (myints,myints+5);        
    
    //拷贝构造
    std::set<int> third (second);                  
    
    //迭代器区间初始化
    std::set<int> fourth (second.begin(), second.end());  
    return 0;
}

2.set的插入

#include<iostream>
#include<set>

using namespace std;

void test_set()
{
    set<int> s;
    s.insert(1);
    s.insert(2);
    s.insert(9);
    s.insert(7);
    s.insert(4);
    s.insert(6);
    s.insert(1);
    set<int>::iterator it = s.begin();
    while(it != s.end())
    {
        cout<<*it<<" ";
        ++it;
    }
    cout<<endl;

}

int main()
{
    test_set();
    return 0;
}

运行结果可以看到,我们原本插入两个1,最后输出了一个1,无序的插入,通过迭代器遍历可以得到一个有序的数列。

 我们也可以通过范围for来遍历,因为范围for的底层就是用迭代器实现的。

#include<iostream>
#include<set>

using namespace std;

void test_set()
{
    set<int> s;
    s.insert(1);
    s.insert(2);
    s.insert(9);
    s.insert(7);
    s.insert(4);
    s.insert(6);
    for(const auto& a : s)
    {
        cout << a << " ";
    }
    cout << endl;


}

int main()
{
    test_set();
    return 0;
}

运行的结果也是一样的。


3.set的一些其他功能

查找功能

find如果找到了,就返回那个节点的迭代器,如果没有找到,那就返回一个end(); 

 所以我们可以这样子使用。

#include<iostream>
#include<set>

using namespace std;

void test_set()
{
    set<int> s;
    s.insert(1);
    s.insert(2);
    s.insert(9);
    s.insert(7);
    s.insert(4);
    s.insert(6);
    set<int>::iterator ret = s.find(6);
    if(ret != s.end())
        cout << *it << endl;
    
    //或者
    if(s.find(5) != s.end())
    {
        cout << "找到了"<<endl;
    }

}

int main()
{
    test_set();
    return 0;
}

统计次数(count)

 这个主要是multiset更实用,multiset就是一个不去重+排序。因为set有去重功能了,所以每个元素出现的次数不是0,就是1。


找边界(lower_bound,    upper_bound,    equal_range)

lower_bound通常和upper_bound配合使用,lower_bound是左边界,upper_bound是有边界。它是一个左闭右开的区间[lower_bound,upper_bound)。

#include<iostream>
#include<set>

using namespace std;

void test_set()
{
    set<int> s;
    s.insert(1);
    s.insert(2);
    s.insert(9);
    s.insert(7);
    s.insert(4);
    s.insert(6);
    for (auto a : s)
    {
        cout << a << " ";
    }
    cout << endl;
    
    set<int>::iterator itlow, itup;
    itlow = s.lower_bound(4);
    itup = s.upper_bound(7);

    while (itlow != itup)
    {
        cout << *itlow << " ";
        itlow++;
    }
    

}

int main()
{
    test_set();
    return 0;
}

lower_bound(4)是找比4大于或等于的位置,upper_bound(7)是找比7大的位置,因为它是一个左闭右开的区间,要让找到的数字位于区间内,所以upper_bound就会往后找。

假如我们来一个lower_bound(3),那么会找到4,因为我们元素里面没有3,所以会找比3大于等于的数字,这时候找到的是4.lower_bound(7),要找比7大的位置,所以找到9位置。

#include<iostream>
#include<set>

using namespace std;

void test_set()
{
    set<int> s;
    s.insert(1);
    s.insert(2);
    s.insert(9);
    s.insert(7);
    s.insert(4);
    s.insert(6);

    for (auto a : s)
    {
        cout << a << " ";
    }
    cout << endl;

    set<int>::iterator itlow, itup;

    itlow = s.lower_bound(3);
    cout << *itlow << endl;

    itup = s.upper_bound(7);
    cout << *itup << endl;


}

int main()
{
    test_set();
    return 0;
}


四、map的介绍

1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元

素。

2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的

内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型

value_type绑定在一起,为其取别名称为pair:

typedef pair<const key, T> value_type;从这里我们可以看到,key是用一个const修饰的,所以map的key值不能被修改,但是value可以修改。

3. 在内部,map中的元素总是按照键值key进行比较排序的。

4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序

对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

 我认为map主要应用于处理一些具有映射关系的问题。


五。map的使用

  1. map的初始化
  • 直接初始化
    map<int,int> mymap; 
  • 拷贝构造
    map<int,int> mymap1;
    map<int,int> mymap2(mymap1);
  • 迭代器区间初始化
    map<int,int> mymap1;
    map<int,int> mymap2(mymap1.begin(),mymap1.end());


        2.map的插入

我们知道map里实际放的是一个pair的数据结构,所以我们在插入时,插入的应当是一个pair,这里有四种插入的方式。

map<string,int> mymap1;

mymap1.insert(pair<string,int>("hello",1));

mymap1.insert(make_pair("A",1));

mymap1.insert({"world",1});

map<string,int> mymap2;
mymap2.insert(mymap1.begin(),mymap1.end());

这里的第三种插入方式实际上是一种隐式类型转化,它是c++11支持的,这是一种多参数的隐式类型转化。

这里最推荐使用第二种,因为第二种是最通用的。


3.map重载了[]

这个[]我认为是map的最精华所在,它和我们平常的[]不太一样,map的[]里面放的是key值,找到的是key对应的value值。

map<string,int> mymap1;
mymap1["hello"] = 1;
mymap1["world"] = 1;
mymap1["hello"]++;

我们可以用[]来插入数据,或者对key对应的value值做修改。就比如上面的代码,

mymap1["hello"],hello不存在,那就会调用insert,将hello插入,然后再根据创建的hello节点把value的位置找到,然后赋值修改成1;如果【】里的key存在,则会直接找到value然后做修改。


它是通过这样的一个返回值实现的。

(*((this->insert(make_pair(k,mapped_type()))).first)).second

我们观察发现它这里和insert有关,它使用了insert的返回值,所以我们探索一下insert的返回值。

通过查文献,大致意思是

1.key在map里面,返回pair<树里面的key所在的节点的iterator,false>

2.key不在map里面,返回pair<新插入的key所在节点的iterator,true>

所以我们在使用insert的时候,返回key的iterator,

(*(pair<key的iterator,true或者false>)).first)).second

然后再调用它的first

(*(iteraotor)).second

 然后是解引用iterator,找到pair<string,int>

pair<string,int> .second

这样就找到了value值。

所以我们的map重载的[]可以完成插入和对key对应的value进行修改。


总结:

💦💦如果有写的有什么不好的地方,希望大家指证出来,我会不断的改正自己的错误。💯💯如果感觉写的还可以,可以点赞三连一波哦~🍸🍸后续会持续为大家更新

🔥🔥你们的支持是我最大的动力,希望在往后的日子里,我们大家一起进步!!!🔥🔥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值