【C++】map 和 set

发布于:2024-10-10 ⋅ 阅读:(95) ⋅ 点赞:(0)

目录

一 基础概念

1 关联式容器

2 键值对

3 树形结构的关联式容器

二 map

1 概念

2 基础操作

3 使用实列

1 实例一

2 实例二

3 实例三 

4 实例四 

4 multimap 

1 实例一

三 set

1 概念

2 基础操作

3 使用实例

1 实例一

2 实例二

3 实例三

4 multiset

1 实例一 


一 基础概念

1 关联式容器

STL中的部分容器,比如:vector、list、deque等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的 键值对,在数据检索时比序列式容器效率更高。

2 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代 表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应 该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义

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)
       {}
};

3 树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结 构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列

二 map

1 概念

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;

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

4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

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

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

map的查询效率是O(log_2N)是正确的,但map的底层结构不是二叉搜索树,而是红黑树

2 基础操作

 

注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过 key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认 value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。

3 使用实列

1 实例一

void test_map1()
{
       map<string, string> dict;
       dict.insert(pair<string, string>("sort", "排序"));

       //pair<string, string> kv("string", "字符串");
       pair<string, string> kv = { "string", "字符串" };
       dict.insert(kv);

       // C++11 多参数隐式类型转换(构造函数)
       dict.insert({ "apple", "苹果" });

       // C++98
       dict.insert(make_pair("sort", "排序"));

       //map<string, string>::iterator it = dict.begin();
       auto it = dict.begin();
       while (it != dict.end())
       {
              //cout << (*it).first << (*it).second << endl;
              cout << it->first << it->second << endl;
              ++it;
       }
       cout << endl;

       for (auto& kv : dict)
       {
              cout << kv.first << ":" << kv.second << endl;
       }
       cout << endl;
}


int main()
{
       /*test_set1();*/
       /*test_set2();*/
       /*test_set3();*/
       test_map1();
       return 0;
}

2 实例二

void test_map2()
{
       // key相同,value不同,不会插入也不会更新
       map<string, string> dict;
       dict.insert(make_pair("sort", "排序"));
       dict.insert(make_pair("string", "字符串"));

       dict["left"]; // 插入
       cout << dict["sort"] << endl; // 查找

       dict["sort"] = "xxx"; // 修改
       dict["right"] = "右边"; // 插入+修改

       for (auto& kv : dict)
       {
              cout << kv.first << ":" << kv.second << endl;
       }
       cout << endl;
}

int main()
{
       /*test_set1();*/
       /*test_set2();*/
       /*test_set3();*/
       /*test_map1();*/
       test_map2();
       return 0;
}

3 实例三 

void test_map3()
{
       string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
       map<string, int> countMap;
       //统计个数

       //方法1
       //for (auto& e : arr)
       //{
       //     map<string, int>::iterator it = countMap.find(e);
       //     if (it != countMap.end())
       //     {
       //            it->second++;
       //     }
       //     else
       //     {
       //            countMap.insert(make_pair(e, 1));
       //     }
       //}


       //方法2
       //for (auto& e : arr)
       //{
       //     pair<map<string, int>::iterator, bool> ret;
       //     ret = countMap.insert(make_pair(e, 1));

       //     // 已经存在了
       //     if (ret.second == false)
       //     {
       //            ret.first->second++;
       //     }
       //}

       //方法3
       for (auto& e : arr)
       {
              countMap[e]++;
       }

       for (auto& kv : countMap)
       {
              cout << kv.first << ":" << kv.second << endl;
       }
       cout << endl;
}
int main()
{
       /*test_set1();*/
       /*test_set2();*/
       /*test_set3();*/
       /*test_map1();*/
       test_map3();
       return 0;
}

而[ ] 底层大概这样

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

4 实例四 

降序

int main()
{
    map<int, int, greater<int>> m;
    m.insert(make_pair(1, 2));
    m.insert(make_pair(2, 2));
    m.insert(make_pair(3, 2));
    m.insert(make_pair(4, 2));
    m.insert(make_pair(5, 2));
    for (auto& e : m)
    {
        cout << e.first << ' ' ;
    }
    return 0;
}

4 multimap 

Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的key是可以重复的。

注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。

multimap中的接口可以参考map,功能都是类似的。

注意:

1. multimap中的key是可以重复的。

2. multimap中的元素默认将key按照小于来比较

3. multimap中没有重载operator[]操作。

4. 使用时与map包含的头文件相同

1 实例一

void test_map4()
{
       multimap<string, string> dict;
       dict.insert(make_pair("sort", "排序"));
       dict.insert(make_pair("string", "字符串"));
       dict.insert(make_pair("sort", "xxx"));
       dict.insert(make_pair("sort", "排序"));

       for (auto& kv : dict)
       {
              cout << kv.first << ":" << kv.second << endl;
       }
       cout << endl;
}
int main()
{
       /*test_set1();*/
       /*test_set2();*/
       /*test_set3();*/
       /*test_map1();*/
       /*test_map3();*/
       test_map4();
       return 0;
}

三 set

1 概念

1. set是按照一定次序存储元素的容器

2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。

3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。

4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。

5. set在底层是用二叉搜索树(红黑树)实现的

注意:

1. 与map / multimap不同,map / multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。

2. set中插入元素时,只需要插入value即可,不需要构造键值对。

3. set中的元素不可以重复(因此可以使用set进行去重)。

4. 使用set的迭代器遍历set中的元素,可以得到有序序列

5. set中的元素默认按照小于来比较

6. set中查找某个元素,时间复杂度为:$log_2 n$

7. set中的元素不允许修改

元素的哈希性要求: 在集合中,元素的存储和查找依赖于哈希值。为了保持高效的查找和插入操作,集合要求所有元素必须是可哈希的(即哈希值在其生命周期中保持不变)。如果允许修改集合中的元素,元素的哈希值可能会发生变化,这将破坏集合的结构,导致查找和操作失效。因此,集合要求其元素是不可变的对象

8. set中的底层使用二叉搜索树(红黑树)来实现。

2 基础操作

 

3 使用实例

1 实例一

void test_set1()
{
       // 排序+去重
       set<int> s;
       s.insert(5);
       s.insert(1);
       s.insert(6);
       s.insert(3);
       s.insert(4);
       s.insert(5);
       s.insert(1);

       set<int>::iterator it = s.begin();
       //找不到返回s.end()
       while (it != s.end())
       {
              cout << *it << " ";
              ++it;
       }
       cout << endl;

       set<int>::iterator pos = s.find(5);
       //找不到返回s.end()
       if (pos != s.end())
       {
              cout << "找到了" << endl;
              s.erase(pos);
       }

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

       // 在就删除,不在就不做任何处理
       s.erase(3);
       s.erase(30);
       for (auto e : s)
       {
              cout << e << " ";
       }
       cout << endl;

       // 这个值在,找到有效位置,再进行删除
       //pos = s.find(5);
       //s.erase(pos);

       //count--> 返回该元素个数 有返回1 没有返回0
       if (s.count(5))
       {
              cout << "在" << endl;
       }
       else
       {
              cout << "不在" << endl;
       }
}


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

2 实例二

void test_set2()
{
       // 排序+去重
       set<int> s;
       s.insert(5);
       s.insert(1);
       s.insert(6);
       s.insert(3);
       s.insert(4);
       for (auto e : s)
       {
              cout << e << " ";
       }
       cout << endl;


       auto start = s.lower_bound(3);  // 返回 >=val 的值
       cout << *start << endl;

       auto finish = s.upper_bound(5);  // 返回 >val 的值
       cout << *finish << endl;

       //找 [3, 5]区间的数
       while (start != finish)
       {
              cout << *start << " ";
              ++start;
       }
       cout << endl;

       start = s.lower_bound(3);
       //删除[3, 5]区间的数字
       s.erase(start, finish);
       for (auto e : s)
       {
              cout << e << " ";
       }
       cout << endl;
}

int main()
{
       /*test_set1();*/
       test_set2();
       return 0;
}

3 实例三

#include<set>
int main()
{
    set<int, greater<int>> s = { 1, 2, 3, 4, 5 };
    for (auto e : s)
    {
        cout << e << ' ';
    }
    return 0;
}

4 multiset

multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。

multiset底层结构为二叉搜索树(红黑树)。

操作可以参考set

1 实例一 

void test_set3()
{
       // 排序
       multiset<int> s;
       s.insert(5);
       s.insert(1);
       s.insert(6);
       s.insert(3);
       s.insert(4);
       s.insert(5);
       s.insert(1);
       s.insert(1);
       s.insert(5);
       s.insert(1);
       s.insert(1);
       s.insert(2);
       s.insert(7);
       s.insert(10);


       multiset<int>::iterator it = s.begin();
       while (it != s.end())
       {
              cout << *it << " ";
              ++it;
       }
       cout << endl;

       //count 返回该value的个数
       cout << s.count(5) << endl;
       cout << s.count(1) << endl;

       it = s.find(5);
       while (it != s.end() && *it == 5)
       {
              cout << *it << " ";
              ++it;
       }
       cout << endl;
}

int main()
{
       /*test_set1();*/
       /*test_set2();*/
       test_set3();
       return 0;
}

本节是对后面的hash 的过度, 内容还是很简单的, 后续还会模拟实现它们, 继续加油! 


网站公告

今日签到

点亮在社区的每一天
去签到