C++:有序关联容器的插入与删除

遇到的问题,都有解决方案,希望我的博客能为您提供一点帮助。

一、插入操作

1. 基本插入方法
  • insert() 方法:set 和 mapinsert 方法返回一个 pair,其中 first 是指向插入元素的迭代器,second 是一个布尔值,若插入成功则为 true,若元素已存在则为 false

  • 单个元素插入
    • std::set s;
      auto result = s.insert(42);  // 返回pair
      if (result.second) {
          // 插入成功,result.first指向新元素
      }
    • 范围插入
      std::vector vec = {1, 2, 3};
      s.insert(vec.begin(), vec.end());
  • emplace() 方法(C++11):直接构造元素,避免拷贝/移动开销:

    std::map m;
    auto result = m.emplace("apple", 42);  // 返回pair
  • operator[](仅限map)​:若键存在则修改值,否则插入新键值对:

    m["apple"] = 42;  // 插入或覆盖
2. 不同容器的插入行为
容器 键是否唯一 插入重复键的行为
set/map 插入失败(result.secondfalse
multiset/multimap 插入成功,允许重复键
3. 插入优化:提示迭代器(hint)​
  • 若已知插入位置的大致范围,可提供提示迭代器提高效率:
    #include 
    #include 
    #include   // 设置控制台编码
    
    using namespace std;
    
    int main() {
        SetConsoleOutputCP(CP_UTF8);  // 支持中文输出
        
        set s = {10, 20, 30, 40};
        auto hint = s.end();
    
        // 批量插入优化
        cout << "插入前集合内容: ";
        for (auto& n : s) cout << n << " ";
        cout << endl;
    
        for(int value : {25, 35, 45}) {
            hint = s.insert(hint, value);
            cout << "插入 " << value << " 后集合内容: ";
            for (auto& n : s) cout << n << " ";
            cout << endl;
        }
    
        // 验证最终结果
        cout << "\n最终有序集合: ";
        for (auto& n : s) cout << n << " ";
        return 0;
    }

结果输出:

C++:有序关联容器的插入与删除_第1张图片

4. 插入操作的底层实现

STL实现(如GCC的libstdc++)通常:

  1. 从根节点开始搜索插入位置

  2. 比较键值,决定向左/向右子树移动

  3. 找到合适位置后创建新节点(通常为红色)

  4. 调整红黑树性质(旋转和重新着色)

关键点

  • 插入操作保持元素有序性

  • 平均和最坏时间复杂度均为O(log n)


二、删除操作

1. 基本删除方法

(1) erase 函数
// 通过迭代器删除
iterator erase(iterator position);

// 通过键值删除
size_type erase(const key_type& key);

// 范围删除
iterator erase(iterator first, iterator last);

 特点

  • 返回被删除元素的下一个元素的迭代器

  • 通过键值删除返回删除的元素数量(对于set/map为0或1)

示例

  • 通过迭代器删除

    auto it = s.find(42);
    if (it != s.end()) {
        s.erase(it);  // 删除指定元素,返回void(C++11前)或下一个迭代器(C++11起)
    }
  • 通过键删除

    int count = s.erase(42);  // 返回删除的元素数量
  • 通过范围删除

    auto start = s.lower_bound(20);
    auto end = s.upper_bound(40);
    s.erase(start, end);      // 删除区间[20, 40)内的元素

完整例子: 

#include 
#include 
#include   // 添加Windows头文件

using namespace std;

int main() {
    SetConsoleOutputCP(CP_UTF8);  // 设置控制台输出编码
    
    std::set s = {1, 2, 3, 4, 5};
    
    // 初始状态
    cout << "初始集合: ";
    for (auto& n : s) cout << n << " ";
    cout << endl;

    // 通过迭代器删除
    auto it = s.find(3);
    if (it != s.end()) {
        s.erase(it);
        cout << "删除3后集合: ";
        for (auto& n : s) cout << n << " ";
        cout << endl;
    }

    // 通过键值删除
    size_t count = s.erase(4);  // count = 1
    cout << "删除4后集合(删除" << count << "个元素): ";
    for (auto& n : s) cout << n << " ";
    cout << endl;

    // 范围删除(删除1和2)
    s.erase(s.begin(), s.find(3));  // 此时3已不存在,实际删除到end
    cout << "范围删除后剩余元素: ";
    for (auto& n : s) cout << n << " ";
    
    return 0;
}

2. 不同容器的删除行为

容器 删除重复键的行为
set/map erase(key)返回0或1(唯一键)
multiset/multimap erase(key)返回删除的重复键数量

3. 安全删除技巧(循环中删除)​

  • 现代方法​(C++11起,利用返回值):

    for (auto it = m.begin(); it != m.end();) {
        if (it->first == "apple") {
            it = m.erase(it);  // erase返回下一个有效迭代器
        } else {
            ++it;
        }
    }

4. 删除操作的底层实现

STL实现通常:

  1. 查找要删除的节点

  2. 根据子节点情况处理:

    • 无子节点:直接删除

    • 一个子节点:用子节点替代

    • 两个子节点:找到后继节点,交换内容后删除后继节点

  3. 调整红黑树性质(旋转和重新着色)

关键点

  • 保持树的平衡性

  • 平均和最坏时间复杂度均为O(log n)


三、示例代码

1. multimap的插入与范围删除
#include 
#include 
#include 
#include   
using namespace std;

int main() {
    SetConsoleOutputCP(CP_UTF8);  // 设置控制台输出编码
    
    multimap mm;
    
    // 插入元素并立即输出
    mm.emplace("apple", 1);
    mm.emplace("apple", 2);
    mm.emplace("banana", 3);
    
    cout << "插入后multimap内容:" << endl;
    for (const auto& [key, value] : mm) {  // 使用结构化绑定
        cout << key << ": " << value << endl;
    }

    // 删除操作
    if (auto [first, last] = mm.equal_range("apple"); first != mm.end()) {
        size_t count = distance(first, last);
        mm.erase(first, last);
        cout << "\n删除了" << count << "个apple条目" << endl;
    }

    // 验证删除结果
    cout << "\n删除后剩余条目:" << endl;
    for (const auto& [key, value] : mm) {
        cout << key << ": " << value << endl;
    }
    
    return 0;
}
2. set的提示插入与安全删除
#include 

int main() {
    std::set s = {10, 20, 30, 40};

    // 使用提示插入35
    auto hint = s.find(30);
    s.insert(hint, 35);  // 插入在30和40之间

    // 删除20到35之间的元素
    auto start = s.lower_bound(20);
    auto end = s.upper_bound(35);
    s.erase(start, end);  // 删除20、30、35
}

四、性能与异常安全性

1. 性能考虑

操作 时间复杂度 备注
insert O(log n) 最坏情况也是O(log n)
emplace O(log n) 可能比insert更高效
erase O(log n) 最坏情况也是O(log n)
find O(log n) 查找操作

2. 异常安全性

  • 插入操作:强异常安全保证(失败时容器状态不变)

  • 删除操作:不抛出异常(无失败情况)

​五、关键注意事项

  1. 迭代器失效
    • 删除元素后,指向该元素的迭代器失效,但其他迭代器仍有效。
    • 循环中删除时需使用返回值或后置递增更新迭代器。
  2. 性能
    • 插入/删除操作的时间复杂度为 ​O(log n)(红黑树平衡操作)。
    • 使用提示迭代器可将插入优化至 ​接近O(1)
  3. 键不可变性
    • 关联容器的键为const,不可直接修改。需先删除旧键,再插入新键值对。

你可能感兴趣的:(C++,c++,java,算法)