C++ : STL关联式容器(map、set、multimap、multiset)

深入解析STL中的关联式容器,包括map、set、multimap和multiset的使用方法,特性及底层实现原理,如红黑树结构,以及如何进行元素的插入、查找和删除操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STL关联式容器(map、set、multimap、multiset)

STL中的序列式容器,比如:vector、list、deque、forward_list(C++11)等,其底层为线性序列的数据结构,里面存储的是元素本身
STL中的关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高
键值对:用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量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)
    {}
};

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

set

  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中查找某个元素,时间复杂度为:log2(N)
  7. set中的元素不允许修改
  8. set中的底层使用二叉搜索树(红黑树)来实现
应用

1>构造一个set类型的变量

std::set<int> s;
insert() : 插入一个元素

第一个insert返回的是一个pair类型的变量,其包含两个成员,第一个成员的类型是该map迭代器的类型,另一个成员的类型是bool
若map没有新插入的元素,那么插入成功,返回的是插入节点的迭代器以及true;
若该元素在map中已经存在,那么插入会失败,返回的是该元素已经存在节点的迭代器,以及false;

//构造一个int类型的set
    std::set<int> s;

//插入元素
    s.insert(4);
    s.insert(5);
    s.insert(1);
    s.insert(3);
    
//测试insert()的返回值
	pair<set<int>::iterator, bool> ret;
	ret = s.insert(2);//set中没有2这个元素,插入成功,返回插入节点的迭代器和true
	ret = s.insert(2);//set中已经存在了2,插入失败,返回的是元素4的迭代器和false

该insert插入的是一段迭代器区间

//定义一段vector,类型也是int
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);

//用insert向set s中插入该vector的全部
	s.insert(v.begin(), v.end());

利用迭代器访问元素

//定义一个对应类型的迭代器,指向s的起始
	std::set<int>::iterator it = s.begin();

//当it没有指向s的最后一个元素的后一个元素时,访问
	while (it != s.end())
	{
		cout << *it << " ";
		++it;  //迭代器自增,指向下一个元素
	}
	cout << endl;
erase() :
//1.用find查找1这个元素,并用迭代器it获取返回值,然后进行删除
	set<int>::iterator it = s.find(500);

	if (it != s.end())          //查询成功,则表示元素存在,删除该元素
		s.erase(it);
	else		                 //若查询失败,表示不存在,对it解引用会是NULL
		cout << "没有找到" << endl;

//2.直接删除一个key值
    s.erase(it);       //该语句在循环外时,有3就删除,没有3就报错
    s.erase(3);       //有3就删除,没有3就不删
//3.删除一段迭代器区间
	s.erase(s.begin(), s.end());
find() : 用来查找一个元素,若查找到,则返回该元素对应的迭代器,否则返回最后一个元素的下一个元素

在删除的时候用过(同上)

it = s.find(3); //O(logN) 推荐使用
    //it = std::find(s.begin(), s.end(), 3); //O(N) :适于所有迭代器
    if (it != s.end())
    {
        cout << "找到了" << endl;
        s.erase(it);
    }
    else		                 
		cout << "没有找到" << endl;
count()

统计一个元素的个数,在set中用于判断一个元素是否存在;因为无法插入相同的值,所以在set/map中与find的作用一样

emplace()

emplace和insert都是向set中插入一个元素,但效率上看,emplace的效率要高一点。insert是先构造好,然后又经过一次拷贝构造放入set中;而emplace是直接在set中进行构造

multiset

  • 查找关键字在不在

  • 排序,不查重;不支持operator[]

std::multiset<int> ms;   //multi多 排序,不查重;不支持operator[]
    ms.insert(3);
    ms.insert(3);
    ms.insert(1);
    ms.insert(5);
    ms.insert(4);
    ms.insert(3);
    ms.insert(2);
    ms.insert(3);

    auto mit = ms.find(3);  //找到一个继续找,可找到所有的3,返回的是中序的第一个3
    if (mit != ms.end())
    {
        cout << "找到了" << endl; 
        while (*mit == 3)
        {
            cout << *mit << endl;
            ++mit;
        }
    }

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

map

  1. map是关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
    typedef pair value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
【map->通过关键字查找映射信息value】:

map是一种key(键),value(值)的形式,用来保存键和值组成的集合,键必须是唯一的,但值可以不唯一。里面的元素可以根据键进行自动排序,由于map是key_value的形式,所以map里的所有元素都是pair类型。pair里面的first被称为key(键),second被称为value(值)

pair是一个有两个模板参数的结构体,分别是first(key)、second(value)

template<class K, class V>
struct pair
{
    K first;
    V second;
    //构造函数
};

创建pair类型的方法

pair<string,int>("桃子",5);
make_pair("香蕉",1);
pair<string,int> p = {"苹果",3};

make_pair

template<class K, class V>
inline std::pair<K, V> make_pair(const K& k, const V& v)
{
    return std::pair<K, V>(k, v);
}

pair与make_pair的比较
pair:是将两个数据合成一个数据,当有这样的需求时就要用到pair,就是map。函数要返回连个数据的时候要用到pair,pair是一个结构体,因为两个成员变量用的是struct而不是class。
make_pair:可使用pair的构造函数,也可使用make_pair来生成需要的pair。一般来说,在需要pair的时候我们用make_pair来生成pair的对象很方便,但由于pair可以进行隐示的类型转换局带来一些问题,如下:

std::pair<int, float>(2, 2.2);
std::make_pair(2, 2.2);

第一个是float,第二个就自动匹配成了double

应用:
插入:insert()

1>.插入一个pair

std::map<std::string, std::string> dict;
dict.insert(std::pair<std::string, std::string>("sort", "排序"));
dict.insert(std::make_pair("string", "字符串"));

2>.插入一个value_type

dict.insert(std::map<std::string, int>::value_type("change", 1));

3>. 利用数组下标进行插入,这里的K值需要是int

std::map<std::string, std::string> dict;
dict["pair"];
dict["key"] = "关键字"; 
dict["pair"] = "剩余";
删除:erase()

1>.迭代器删除

std::map<std::string, int>::iterator it = dict.find("字符串");
if (it != m.end())
    dict.erase(it);

2>.key值删除

dict.erase("关键字");
查找:find()
 iterator find ( const key_type& x );
 const_iterator find ( const key_type& x ) const;

用来查找一个元素,查找成功,返回这个元素的迭代器,否则返回最后一个元素的下一个元素(end)

count

统计个数,不允许出现重复的Key值,所以find()作用类似
size_typecount ( const key_type& x ) const;
和set中的count使用功能相同

operator[]

T&operator[] ( const key_type& x );
operator[]比较常用, 实际进行插入查找。如果map中有这个x,则它就把这个x所对应的value的引用返回。如果map中没有这个x的话,则它会调用insert(pair<K,V>(k,V())),将k和V的缺省值对应起来插入后并返回这个value的引用。

multimap

与 map 类似,所不同的是它允许重复键。这个属性使得 multimap 比预想的要更有用:比如在电话簿中相同的人可以有两个以上电话号码,文件系统中可以将多个符号链接映射到相同的物理文件,或DNS服务器可以将几个URLs映射到相同的IP地址。在这些场合,你可以象下面这样:

// 举例:伪码
            multimap <string, string> phonebook;
            phonebook.insert("Lily","88345678"); // 家里电话
            phonebook.insert("Lily","17712345678"); // 单位电话
            phonebook.insert("Lily"," 15887654321"); // 移动电话

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

 #include <map>
#include <string>

void TestMultimap1()
{
multimap<string, string> m;
m.insert(make_pair("李逵", "黑旋风"));
m.insert(make_pair("林冲", "豹子头"));
m.insert(make_pair("鲁达", "花和尚"));

// 尝试插入key相同的元素
 m.insert(make_pair("李逵", "铁牛"));
cout << m.size() << endl;
for (auto& e : m)
cout << "<" << e.first << "," << e.second << ">" << endl;

// key为李逵的元素有多少个
cout << m.count("李逵") << endl;
return 0;
}

void TestMultimap2()
{
    multimap<int, int> m;
    for (int i = 0; i < 10; ++i)
        m.insert(pair<int, int>(i, i));
    for (auto& e : m)
        cout << e.first << "--->" << e.second << endl;
        cout << endl;

// 返回m中大于等于5的第一个元素
auto it = m.lower_bound(5);
    cout << it->first << "--->" << it->second << endl;

// 返回m中大于5的元素
    it = m.upper_bound(5);
        cout << it->first << "--->" << it->second << endl;
}

注意:

  1. multimap中的key是可以重复的。
  2. multimap中的元素默认将key按照小于来比较
  3. multimap中没有重载operator[]操作
  4. 使用时与map包含的头文件相同
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值