C++学习笔记之关联式容器:set,multiset,map,multimap

一、关联式容器

1. 定义

set, multiset, map, multimap 是一种非线性的树结构,具体的说采用的是一种比较高效的特殊的平衡检索二叉树—— 红黑树结构。

  • set ,又称集合,实际上就是一组元素的集合,但其中所包含的元素的值是唯一的,且是按一定顺序排列的,集合中的每个元素被称作集合中的实例。因为其内部是通过链表的方式来组织,所以在插入的时候比vector 快,但在查找和末尾添加上被vector 慢
  • multiset ,是多重集合,其实现方式和set 是相似的,只是它不要求集合中的元素是唯一的,也就是说集合中的同一个元素可以出现多次
  • map ,提供一种“键- 值”关系的一对一的数据存储能力。其“键”在容器中不可重复,且按一定顺序排列(其实我们可以将set 也看成是一种键- 值关系的存储,只是它只有键没有值。它是map 的一种特殊形式)。由于其是按链表的方式存储,它也继承了链表的优缺点。
  • multimap , 和map 的原理基本相似,它允许“键”在容器中可以不唯一。

2. 特点

  • 其内部实现是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的

  • set 和map 保证了元素的唯一性,mulset 和mulmap 扩展了这一属性,可以允许元素不唯一

  • 元素是有序的集合,默认在插入的时候按升序排列

3. 对比顺序性容器

  • 关联容器对元素的插入和删除操作比vector 要快,因为vector 是顺序存储,而关联容器是链式存储;比list 要慢,是因为即使它们同是链式结构,但list 是线性的,而关联容器是二叉树结构,其改变一个元素涉及到其它元素的变动比list 要多,并且它是排序的,每次插入和删除都需要对元素重新排序;

  • 关联容器对元素的检索操作比vector 慢,但是比list 要快很多。vector 是顺序的连续存储,当然是比不上的,但相对链式的list 要快很多是因为list 是逐个搜索,它搜索的时间是跟容器的大小成正比,而关联容器 查找的复杂度基本是Log(N) ,比如如果有1000 个记录,最多查找10 次,1,000,000 个记录,最多查找20 次。容器越大,关联容器相对list 的优越性就越能体现;

  • 在使用上set 区别于vector,deque,list 的最大特点就是set 是内部排序的,这在查询上虽然逊色于vector ,但是却大大的强于list 。

  • 在使用上map 的功能是不可取代的,它保存了“键- 值”关系的数据,而这种键值关系采用了类数组的方式。数组是用数字类型的下标来索引元素的位置,而map 是用字符型关键字来索引元素的位置。在使用上map 也提供了一种类数组操作的方式,即它可以通过下标来检索数据,这是其他容器做不到的,当然也包括set 。(STL 中只有vector 和map 可以通过类数组的方式操作元素,即如同ele[1] 方式)

二、set和multiset容器

1. 基本概念

简介:

  • 所有元素都会在插入时自动被排序

本质:

  • set/multiset属于关联式容器, 底层结构是用二叉树实现

set和multiset区别

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

2. set构造和赋值

构造:

  • set<T> st; // 默认构造
  • set(const set& st); // 拷贝构造

赋值:

  • set& operator=(const set& st);
#include<iostream>
#include<set>
using namespace std;

void printset(set<int>& s)
{
    for(set<int>::iterator it = s.begin(); it != s.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

void test()
{
    set<int> s;

    // 插入数据 只有insert方式
    s.insert(10);
    s.insert(30);
    s.insert(20);
    s.insert(50);
    s.insert(10);

    // 遍历容器
    // set容器特点:所有元素插入时自动被排序
    // set容器不允许插入重复值,如果有重复的数,会被忽略
    printset(s);  // 10 20 30 50 
}

3. set大小和交换

函数原型;

  • size(); // 返回容器中元素的数目
  • empty(); // 判断容器是否为空
  • swap(st); // 交换两个集合容器

4. set插入和删除

函数原型:

  • insert(elem); // 在容器中插入元素
  • clear(); // 清除所有元素
  • erase(pos); // 删除pos迭代器所指的元素,返回下一个元素的迭代器
  • erase(beg, end); // 删除区间【beg, end)的所有元素,返回下一个元素的迭代器
  • erase(elem); // 删除容器中值为elem的元素

5. set查找和统计

函数原型:

  • find(key); // 查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); // 统计key的元素个数
    set<int> s;

    // 插入数据 只有insert方式
    s.insert(10);
    s.insert(30);
    s.insert(20);
    s.insert(50);
    s.insert(10);

    set<int>::iterator pos = s.find(30);
    if(pos != s.end())
    {
        cout<< "找到元素: " << *pos << endl;
    }
    else{
        cout << "没找到" << endl;
    }
    int num = s.count(10);
    // 对于set而已,统计结果要么是0,要么是1,因为没有重复的数
    cout << " num = " << num << endl;

6. set和multiset区别

区别:

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入成功
  • multiset不会检测数据,因此可以插入重复数据
    set<int> s;

    // 接受插入后返回的结果
    pair<set<int>::iterator, bool> ret = s.insert(10);
    if(ret.second) // 表示第二个boll
    {
        cout << "第一次插入成功" << endl;
    }else{
        cout << "第一次插入失败" << endl;
    }

    ret = s.insert(10);
    if(ret.second)
    {
        cout << "第二次插入成功" << endl;
    }else{
        cout << "第二次插入失败" << endl;
    }

    // multiset
    multiset<int> ms;
    ms.insert(10);
    ms.insert(10);

总结:

  • 如果不允许插入重复数据可以利用set
  • 如果需要插入重复数据利用multiset

7. pair对组创建

功能描述:

  • 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

  • pair<type, type> p (value1, value2);
  • pair<type, type> p = make_pair(value1, value2);
  • p.firstp.second访问
   // 创建对组
    pair<string, int> p("tom", 20);

    cout << "姓名: " << p.first
         << " 年龄: " << p.second
         << endl;

    // 另一种方式
    pair<string, int> p2 = make_pair("jery", 30);
     cout << "姓名: " << p2.first
          << " 年龄: " << p2.second
          << endl;

8. set容器排序

学习目标:

  • set容器默认排序规则为从小到大,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则
  • 仿函数:在一个类中重载()

示例一:set存放内置数据结构

// 仿函数
class MyCompare
{
public:
    bool operator()(int v1, int v2)
    {
        return v1>v2;
    }
};
void test()
{
    set<int> s1;
    s1.insert(10);
    s1.insert(40);
    s1.insert(30);
    s1.insert(15);

    for(set<int>::iterator it = s1.begin(); it != s1.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;		// 10 15 30 40

    // 指定排序规则为从大到小, 利用仿函数
    set<int, MyCompare> s2;
    s2.insert(10);
    s2.insert(40);
    s2.insert(30);
    s2.insert(15);

    for(set<int, MyCompare>::iterator it1 = s2.begin(); it1 != s2.end(); it1++)
    {
        cout << *it1 << " ";
    }
    cout << endl;   //40 30 15 10
}

示例二: set存放自定义数据类型
对于自定义数据类型,set必须指定排序规则才可以插入数据

class Person
{
public:
    Person(string name, int age)
    {
        this->m_age = age;
        this->m_name = name;
    }
    string m_name;
    int m_age;
};

// 仿函数
class MyCompare
{
public:
    bool operator()(const Person& p1, const Person& p2)
    {
        // 按照年龄来排序
        return p1.m_age > p2.m_age;
    }
};
void test()
{
    // 创建对象
    Person p1("刘备", 27);
    Person p2("张飞", 22);
    Person p3("关羽", 25);
    Person p4("赵云", 23);

    // 创建容器
    set<Person, MyCompare> s;
    
    s.insert(p1);
    s.insert(p2);
    s.insert(p3);
    s.insert(p4);

    for(set<Person, MyCompare>::iterator it = s.begin(); it != s.end(); it++)
    {
        cout << "姓名: " << it->m_name 
            << " 年龄: " << it->m_age
            << endl;
    }
}

三、map/multimap容器

1. map基本概念

简介:

  • map中所有元素都是pair
  • pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
  • 所有元素都会根据元素的key(键值)自动排序

本质:

  • map/multimap属于关联式容器,底层结构是用二叉树实现

优点:

  • 可以根据key值快速找到value值

map和multimap区别

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素

2. map构造和赋值

构造:

  • map<T1, T2> mp; // 默认构造函数
  • map(const map& mp); // 拷贝构造函数

赋值:

  • map& operator=(const map& mp);
void PrintMap(map<int,int>& m)
{
    for(map<int,int>::iterator it = m.begin(); it != m.end(); it++)
    {
        cout << "key: " << it->first << " value: " << it->second << endl;
    }
}
void test()
{
    map<int, int> m;   // 默认构造

    m.insert(pair<int,int>(1,10));
    m.insert(pair<int,int>(3,20));
    m.insert(pair<int,int>(2,40));
    m.insert(pair<int,int>(4,50));
    // m.insert(make_pair(5,40));

    PrintMap(m);

    map<int,int> m2(m);   // 拷贝构造
    PrintMap(m2);

    map<int,int> m3;
    m3 = m2;  // 赋值
    PrintMap(m3);
}

3. map大小和交换

函数原型:

  • size(); // 返回容器中元素的数目
  • empty(); // 判断容器是否为空
  • swap(st); // 交换两个集合容器

4. map插入和删除

函数原型:

  • insert(elem); // 插入元素
  • clear(); // 清除所有元素
  • erase(pos); // 删除pos迭代器所指的元素,返回下一个元素的迭代器
  • erase(beg, end); // 删除区间【beg, end)的所有元素,返回下一个元素的迭代器
  • erase(key); // 删除容器中值为key的元素
  • 可以通过【key】访问value

5. map查找和统计

函数原型:

  • find(key); // 查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
  • count(key); // 统计key的元素个数
    map<int, int> m;   // 默认构造

    m.insert(pair<int,int>(1,10));
    m.insert(pair<int,int>(3,20));
    m.insert(pair<int,int>(2,40));
    m.insert(pair<int,int>(4,50));

   map<int, int>::iterator pos = m.find(3);

   if(pos != m.end())
   {
       cout << "查到了元素 key = " << pos->first << " value = " << pos->second << endl;
   }else{
       cout << "没查到" << endl;
   }

    // map不允许插入重复key元素,count统计而言 结果要么是0 要么是1
   int num = m.count(3);
   cout << "num = " << num << endl;

6. map容器排序

学习目标:

  • map容器默认排序规则为 按照key值进行 从小到大排序, 掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则
// 仿函数
class MyCompare
{
public:
    bool operator()(int v1, int v2)
    {
        return v1>v2;
    }
};
void PrintMap(map<int,int, MyCompare>& m)
{
    for(map<int,int, MyCompare>::iterator it = m.begin(); it != m.end(); it++)
    {
        cout << "key: " << it->first << " value: " << it->second << endl;
    }
}
void test()
{
    map<int, int, MyCompare> m;   // 默认构造

    m.insert(pair<int,int>(1,10));
    m.insert(pair<int,int>(3,20));
    m.insert(pair<int,int>(2,40));
    m.insert(pair<int,int>(4,50));

   PrintMap(m);
}

7. 案例-员工分组

1. 案例描述

  • 公司今天招聘了10个员工(ABCDEFGHIJ),10名员工进入公司之后,需要指派员工在哪个部门工作
  • 员工信息有:姓名 工资组成; 部门分为:策划、美术、研发
  • 随机给10个员工分配部门和工资
  • 通过multimap进行信息的插入 key(部门编号) value(员工)
  • 分部门显示员工信息

2. 实现步骤

  • 创建10名员工,放到vector中
  • 遍历vector容器,取出每个员工,进行随机分组
  • 分组后,将员工部门编号作为key,具体员工作为value,放入到multimap容器中
  • 分部门显示员工信息

3. 案例代码

#include<iostream>
#include<vector>
#include<map>
#include<string>
using namespace std;

#define CEHUA 0
#define MEISHU 1
#define YANFA 2

class Worker
{
public:
    string m_name;
    int m_salary;
};

// 创建员工
void createWorker(vector<Worker>& v)
{
    string nameSeed = "ABCDEFGHIJ";
    for(int i=0; i<10; i++)
    {
        Worker worker;
        worker.m_name = "员工";
        worker.m_name += nameSeed[i];
        
        worker.m_salary = rand()%10000 + 10000;  // 10000~19999
        v.push_back(worker);
    }
}

// 员工分组
void setGroup(vector<Worker>& v, multimap<int, Worker>& m)
{
    for(vector<Worker>::iterator it = v.begin(); it != v.end(); it++)
    {
        // 产生随机部门编号
        int deptID = rand() % 3; // 0 1 2

        // 将员工插入到分组中
        // key部门编号,value具体员工
        m.insert(pair<int, Worker>(deptID, *it));
    }
}
void showWorkerByGrourp(multimap<int, Worker>& m)
{
    cout << "策划部门:" << endl;
    multimap<int, Worker>::iterator pos = m.find(CEHUA);
    int count = m.count(CEHUA);
    int index = 0;
    for(; pos != m.end() && index < count; pos++, index++)
    {
        cout << "姓名:" << pos->second.m_name << "工资: " << pos->second.m_salary << endl;
    }
}
int main()
{
    // 1.创建员工
    vector<Worker> vWorker;
    createWorker(vWorker);
    // 测试
    for(vector<Worker>::iterator it = vWorker.begin(); it != vWorker.end(); it++)
    {
        cout << "姓名:" << it->m_name
             << " 工资: " << it->m_salary
             << endl;
    }

    // 2. 员工分组
    multimap<int, Worker> mWorker;
    setGroup(vWorker, mWorker);

    // 3. 分组显示员工
    showWorkerByGrourp(mWorker);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值