C++ STL-map和set

1. 序列式容器与关联式容器对比

在C++标准模板库(STL)中,容器主要分为两大类:

  • 序列式容器:如stringvectorlistdeque等,这些容器维护元素的线性序列,元素之间的关系仅由它们在容器中的位置决定。特点是:

    • 元素按存储位置顺序保存和访问
    • 交换元素位置不会破坏容器结构
    • 适合需要保持插入顺序的场景
  • 关联式容器:如map/set系列和unordered_map/unordered_set系列,这些容器基于元素的关键字而非位置来组织数据。特点是:

    • 元素按关键字保存和访问
    • 通常采用非线性结构(如树或哈希表)
    • 交换元素可能破坏内部结构
    • 提供高效的查找操作(O(logN)或O(1))

本章重点讲解基于红黑树实现的mapset容器,它们提供了有序的键值存储和高效的查找能力。

2. set容器详解

2.1 set基本特性

set是存储唯一键的关联容器,底层用红黑树实现,提供O(logN)的查找、插入和删除操作。主要特点包括:

  • 元素自动排序(默认升序)
  • 键值唯一(不允许重复)
  • 不可直接修改元素值(会破坏排序)
template <class T, 
          class Compare = less<T>,
          class Alloc = allocator<T>> 
class set;

注:set默认情况下遍历出来是升序,但是如果定义对象时显示传参greater,就是降序!

2.2 set常用操作

构造方式

set<int> s1; // 默认构造
set<int> s2 = {1, 2, 3}; // 初始化列表构造
set<int> s3(s2.begin(), s2.end()); // 迭代器范围构造

插入元素

s1.insert(4); // 插入单个元素
s1.insert({5, 6, 7}); // 插入初始化列表

在这里插入图片描述
插入的返回值也是一个pair结构pair中存储了布尔类型和迭代器,分别代表此次插入是否成功,若成功则返回被插入元素迭代器的位置
查找与删除

//std::set<int>::iterator it;
auto it = s1.find(4); // 查找,返回迭代器
if(it != s1.end()) {
    s1.erase(it); // 通过迭代器删除
}

size_t n = s1.erase(5); // 直接通过值删除,返回删除数量

2.3 set应用示例

去重排序

vector<int> nums = {3,1,4,1,5,9,2,6,5};
set<int> unique_nums(nums.begin(), nums.end());
// unique_nums: {1,2,3,4,5,6,9}

集合运算

// 求两个数组的交集
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    set<int> s1(nums1.begin(), nums1.end());
    set<int> s2(nums2.begin(), nums2.end());
    vector<int> res;
    
    auto it1 = s1.begin(), it2 = s2.begin();
    while(it1 != s1.end() && it2 != s2.end()) {
        if(*it1 < *it2) ++it1;
        else if(*it1 > *it2) ++it2;
        else {
            res.push_back(*it1);
            ++it1; ++it2;
        }
    }
    return res;
}

3. map容器详解

3.1 map基本特性

map是存储键值对的关联容器,同样基于红黑树实现,提供O(logN)的操作效率。主要特点包括:

  • 按键自动排序(默认升序,和set一样)
  • 键唯一,值可重复
  • 可修改值但不可直接修改键
template <class Key, 
          class T,
          class Compare = less<Key>,
          class Alloc = allocator<pair<const Key,T>>>
class map;

3.2 pair类型

map中存储的元素类型为pair<const Key, T>,STL提供了方便的pair操作:

pair<string, int> p1("apple", 3); // 直接构造
auto p2 = make_pair("banana", 2); // 使用make_pair函数
cout << p1.first << ": " << p1.second; // 访问成员

3.3 map常用操作

插入元素

map<string, int> m;
m.insert({"apple", 3}); // 方式1
m.insert(make_pair("banana", 2)); // 方式2
m["orange"] = 5; // 使用[]操作符

在这里插入图片描述

map的方括号设计的十分巧妙,它的参数的pair中的first,返回值是pair中的second,并且它返回的是引用,可以根据first修改second它可以用来计数,请看如下代码:

string arr[]={"苹果","西瓜","香蕉","苹果","西瓜","西瓜","西瓜","苹果"};
map<string,int> countmap;
for(auto str : arr)
{
	countmap[str]++;
}

访问元素

auto it = m.find("apple"); // 查找
if(it != m.end()) {
    cout << it->first << ": " << it->second;
}

int count = m["banana"]; // 使用[]访问,若不存在会插入

删除元素

m.erase("apple"); // 按键删除
m.erase(m.begin()); // 通过迭代器删除
m.erase(m.begin(), m.end()); // 删除范围

3.4 map应用示例

词频统计

vector<string> words = {"apple", "banana", "apple", "orange"};
map<string, int> word_count;

// 方法1:使用find和insert
for(const auto& word : words) {
    auto it = word_count.find(word);
    if(it != word_count.end()) {
        it->second++;
    } else {
        word_count.insert({word, 1});
    }
}

// 方法2:使用[]操作符(更简洁)
for(const auto& word : words) {
    word_count[word]++;
}

复杂问题简化

// 复制带随机指针的链表
Node* copyRandomList(Node* head) {
    map<Node*, Node*> node_map;
    Node* curr = head;
    
    // 第一次遍历创建所有节点
    while(curr) {
        node_map[curr] = new Node(curr->val);
        curr = curr->next;
    }
    
    // 第二次遍历设置指针
    curr = head;
    while(curr) {
        node_map[curr]->next = node_map[curr->next];
        node_map[curr]->random = node_map[curr->random];
        curr = curr->next;
    }
    
    return node_map[head];
}

4. multiset和multimap

multisetmultimap是允许键重复的版本,与setmap的主要区别:

  • 允许存储多个相同键
  • multimap不支持[]操作符
  • find返回第一个匹配元素的迭代器
  • count返回匹配元素的数量
multiset<int> ms = {1,2,2,3,3,3};
cout << ms.count(2); // 输出2

multimap<string, int> mm;
mm.insert({"a", 1});
mm.insert({"a", 2}); // 允许插入相同键

5. 使用建议

  1. 有序性

    • 需要元素有序:选择map/set(基于红黑树)
    • 不需要有序:选择unordered_map/unordered_set(基于哈希表,O(1)查找)
  2. 重复键

    • 键唯一:map/set
    • 允许重复键:multimap/multiset
  3. 操作复杂度

    • map/set:插入、删除、查找都是O(logN)
    • unordered_map/unordered_set:平均O(1),最差O(N)

6. 实际应用案例

6.1 前K个高频单词

vector<string> topKFrequent(vector<string>& words, int k) {
    map<string, int> count_map;
    for(const auto& word : words) {
        count_map[word]++;
    }
    
    // 自定义比较器:频率降序,同频按字典序升序
    auto cmp = [](const pair<string,int>& a, const pair<string,int>& b) {
        return a.second > b.second || (a.second == b.second && a.first < b.first);
    };
    
    vector<pair<string,int>> vec(count_map.begin(), count_map.end());
    sort(vec.begin(), vec.end(), cmp);
    
    vector<string> result;
    for(int i = 0; i < k; ++i) {
        result.push_back(vec[i].first);
    }
    
    return result;
}

6.2 环形链表检测

ListNode *detectCycle(ListNode *head) {
    set<ListNode*> visited;
    while(head) {
        if(visited.count(head)) {
            return head;
        }
        visited.insert(head);
        head = head->next;
    }
    return nullptr;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值