4.关联式容器

关联式container

STL中一些常见的容器:

  1. 序列式容器(Sequence Containers)

    • vector(动态数组): 动态数组,支持随机访问和在尾部快速插入/删除。
    • list(链表): 双向链表,支持在任意位置快速插入/删除。
    • deque(双端队列): 双端队列,支持在两端快速插入/删除。

    容器适配器(Container Adapters):

    • stack(栈): 后进先出(LIFO)的数据结构。
    • queue(队列): 先进先出(FIFO)的数据结构。
    • priority_queue(优先队列): 具有优先级的队列。

  2. 关联式容器(Associative Containers)

    • set(集合): 有序集合,不允许重复元素。
    • multiset(多重集合): 有序集合,允许重复元素。
    • map(映射): 键-值对的集合,按键有序存储,不允许重复键。
    • multimap(多重映射): 键-值对的集合,按键有序存储,允许重复键。

    无序关联容器(Unordered Associative Containers):

    • unordered_set: 无序集合,不允许重复元素。
    • unordered_multiset: 无序集合,允许重复元素。
    • unordered_map: 无序映射,按键无序存储,不允许重复键。
    • unordered_multimap: 无序映射,按键无序存储,允许重复键。

关联式容器

关联式容器是指数据是键值对的格式。容器内部结构(可能是RB-tree或者hash-table)便依照其键值大小,以某种特定规则将这个元素放置于合适的位置,关联式容器没有所谓头尾(只有最大元素和最小元素);所以不会有所谓push_back()push_front()等行为的操作。

1.set

set中所有元素都会根据元素的键值自动被排序。std::set 使用红黑树(一种自平衡的二叉搜索树)作为底层数据结构。

set的元素不像map那样可以同时拥有key和value,set的键值就是实值。并且set不允许两个元素拥有相同的值。

迭代器

std::set 的迭代器性质:

  1. 递增顺序: std::set 中的元素是有序排列的,迭代器按照递增(升序)的顺序遍历元素。这是由底层平衡二叉搜索树的特性决定的。
  2. 双向迭代器: std::set 提供双向迭代器,支持向前(++)和向后(-)遍历。
  3. 随机访问特性的缺失: 与支持随机访问的容器(如 std::vector)不同,std::set 的迭代器不支持 +nn 这样的随机跳跃,因为底层结构并不是一个连续的存储区域。
  4. 不能通过迭代器修改set键值,因为set的元素值关系到set元素的排列,随意改变值,会导致排列顺序崩溃,所以不不能通过迭代器修改set值。

因此插入和删除操作并不会使set迭代器失效。当然,被删除的哪个迭代器除外。


set的核心在于使用的红黑树的insert_unique插入数据。

在这里插入图片描述

2.map

map所有元素都会根据元素的键值自动被排序。std::map 也是使用红黑树(一种自平衡的二叉搜索树)作为底层数据结构。

map的所有元素都是pair,同时拥有实值(value)和键值(key)。pair的第一元素被视为键值,第二元素被视为实值。map不允许两个元素拥有相同的键值。

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

map和set一样的核心在于使用的红黑树的insert_unique插入数据。

在这里插入图片描述

迭代器

map和set的迭代器性质一样。

  • 递增顺序
  • 双向迭代器
  • 随机访问特性缺少
  • 不能通过迭代器修改map键值。但是可以通过迭代器修改键值对应的实值。
int main() {
	std::map<int, std::string> myMap;

	// 插入元素
	myMap[1] = "One";
	myMap[2] = "Two";
	myMap[3] = "Three";

	// 使用迭代器遍历并修改值
	for (auto it = myMap.begin(); it != myMap.end(); ++it) {
		std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;

		//it->first = 4; //报错
		// 修改值
		it->second = "Modified";
	}
	for (auto it = myMap.begin(); it != myMap.end(); ++it) {
		std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
	}
	return 0;
}

关联式容器的find方法

在这里插入图片描述

3.multiset

multiset的特性以及用法和set完全相同,唯一的差别在于它允许键值重复。

它的底层机制是使用RB-tree的insert_equal()而非insert_unique();

在这里插入图片描述

4.multimap

与map的用法完全相同,唯一的差别在于,它允许键值重复。

它的底层机制是使用RB-tree的insert_equal()而非insert_unique();

在这里插入图片描述

5.hashtable

上述提到的以红黑树做为底层结构的set、map,其查找的时间复杂度为对数,但是依赖于元素值是要有足够的随机性。例如输入数据完全有序或完全逆序,红黑树可能会退化成链表,导致性能下降至O(n)。

HashTable 是一种通过哈希函数将关键字映射到存储桶的数据结构。STL中的HashTable通常是一个数组,每个数组元素是一个链表或其他形式的容器。当有多个关键字哈希到同一个存储桶时,它们被放入同一个链表中。

使用哈希函数会遇到一个问题,可能有不同的元素被映射到相同的位置,这就是所谓的hash碰撞问题。解决hash碰撞问题有很多,包括线性探测、二次探测、开链法等。

线性探测

线性探测的基本操作:

  1. 插入操作

    • 根据散列函数计算键的散列值。
    • 如果该位置为空,则将键值对插入该位置。
    • 如果该位置已被占用,就向后线性探测,查找下一个空闲位置,直到找到一个空闲位置为止,然后插入键值对。
  2. 查找操作

    • 根据散列函数计算键的散列值。
    • 如果该位置为空,表示键不存在。
    • 如果该位置不为空,检查键是否匹配,如果匹配则找到了键,返回对应的值;否则,向后线性探测,查找下一个位置,直到找到匹配的键或者遇到空位置为止。
  3. 删除操作

    • 查找要删除的键。
    • 如果找到,将该位置标记为空,表示删除该键。注意:在某些实现中,可以使用特殊的标记表示已删除的位置,而不是直接将位置设置为空。被叫做惰性删除,实际的删除操作等到整理表的时候再删。

    具体的探测序列可以表示为:
    在这里插入图片描述

二次探测

二次探测是解决散列表冲突的一种方法,类似于线性探测,但是在寻找下一个可用位置时,不是简单地线性向后移动,而是使用一个二次方程来计算下一个位置。这样可以更灵活地处理冲突,减少聚集的可能性。
在这里插入图片描述
二次探测的优点是相对于线性探测,能够更好地分散冲突,减少聚集问题。然而,如果HashTable的大小选择不当,仍然可能发生聚集问题。

开链法

开链法的做法是为每一个表格元素维护一个list。通过哈希函数定位到某一个list,然后在那一个list上执行插入、搜寻、删除操作。

使用开链法,哈希表的负载系数将大于1。上述的线性探测和二次探测的负载系数都是小于1的。

开链法由一个bukets vector和一个bucket list组成。
在这里插入图片描述
在这里插入图片描述

迭代器

在这里插入图片描述

hash_set、hash_map、hash_multiset、hash_multimap

hash_set以hashtable为底层机制。由于hash_set所供应的操作接口。 注意:hash_table中没有自动排序功能。

hash_map、hash_multiset、hash_multimap基本都与对应的类型相同,只是底层机制由hash_table来进行实现。

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aries_Ro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值