C/C++编程:STL之关联式容器set/multisets、maps/multimaps

1059 篇文章 285 订阅

目录


关联式容器

关联式容器依据特定的排序准则,自动为其元素排序。

  • 排序准则是以函数形式呈现,用来比较元素值(value)或者元素键(key),默认以operator<进行比较
  • 所有的关联式容器均有一个可供选择的template参数,致命排序准则。缺省采用operator<。 
  • 排序准则也可用来测试互等性:如果两元素都不小于对方,则两者相等
  • 通常关联式容器是由二叉树排序的。在二叉树中,每个元素都有一个父节点和两个子节点:左子树元素比自己小,右子树元素比自己大

关联式容器的差别在于元素的联系以及处理重复元素时的方法

理论

set/multisets

概述
 

 

 

内部结构:

 

 

 

 

自动排序:

 


构造&&析构

 

 

 

 

 

 

 

非更易型操作

 

 

特殊的查找函数

 赋值函数

 

 

迭代器相关

 

 插入和删除

 

 

实例程序

#include <iostream>
#include <set>
using namespace std;

void main()
{
	set<int>coll;
	coll.insert(1); //没有push_back,因为是自动排序的
	coll.insert(6);
	coll.insert(2);
	coll.insert(2);
	for (set<int>::const_iterator pos = coll.begin(); pos != coll.end(); ++pos) cout << *pos << ends;
	//输出1,2,6.不可以插入相同的元素,相同的元素被去掉了。自动排序
	system("pause");
}  

构造

#include <iostream>
#include <string>
#include <set>
#include <cmath>

struct Point { double x, y; };
struct PointCmp {
    bool operator()(const Point& lhs, const Point& rhs) const {
        return std::hypot(lhs.x, lhs.y) < std::hypot(rhs.x, rhs.y);
    }
};

int main()
{
    // (1) 默认初始化器
    std::set<std::string> a;
    a.insert("cat");
    a.insert("dog");
    a.insert("horse");
    for(auto& str: a) std::cout << str << ' ';
    std::cout << '\n';

    // (2) 迭代器初始化器
    std::set<std::string> b(a.find("dog"), a.end());
    for(auto& str: b) std::cout << str << ' ';
    std::cout << '\n';

    // (3) 复制构造函数
    std::set<std::string> c(a);
    c.insert("another horse");
    for(auto& str: c) std::cout << str << ' ';
    std::cout << '\n';

    // (4) 移动构造函数
    std::set<std::string> d(std::move(a));
    for(auto& str: d) std::cout << str << ' ';
    std::cout << '\n';
    std::cout << "moved-from set is ";
    for(auto& str: a) std::cout << str << ' ';
    std::cout << '\n';

    // (5) initializer_list 构造函数
    std::set<std::string> e {"one", "two", "three", "five", "eight"};
    for(auto& str: e) std::cout << str << ' ';
    std::cout << '\n';

    // 自定义比较
    std::set<Point, PointCmp> z = {{2, 5}, {3, 4}, {1, 1}};
    z.insert({1, -1}); // 这会失败,因为 1,-1 的长度等于 1,1
    for(auto& p: z) std::cout << '(' << p.x << ',' << p.y << ") ";
    std::cout << '\n';
}

=

#include <set>
#include <iostream>
 
void display_sizes(const std::set<int> &nums1,
                   const std::set<int> &nums2,
                   const std::set<int> &nums3)
{
    std::cout << "nums1: " << nums1.size() 
              << " nums2: " << nums2.size()
              << " nums3: " << nums3.size() << '\n';
}
 
int main()
{
    std::set<int> nums1 {3, 1, 4, 6, 5, 9};
    std::set<int> nums2; 
    std::set<int> nums3;
 
    std::cout << "Initially:\n";
    display_sizes(nums1, nums2, nums3);
 
    // 复制赋值从 nums1 复制数据到 nums2
    nums2 = nums1;
 
    std::cout << "After assigment:\n"; 
    display_sizes(nums1, nums2, nums3);
 
    // 移动赋值从 nums1 移动数据到 nums3,
    // 一同修改 nums1 和 nums3
    nums3 = std::move(nums1);
 
    std::cout << "After move assigment:\n"; 
    display_sizes(nums1, nums2, nums3);
}

begin、end、rbegin、rend

  • begin:返回指向 set 首元素的迭代器。若 set 为空,则返回的迭代器将等于 end() 
  • end:返回指向 set 末元素后一元素的迭代器。此元素表现为占位符;试图访问它导致未定义行为。
  • rbegin:返回指向逆向 set 首元素的逆向迭代器。它对应非逆向 set 的末元素。若 set 为空,则返回的迭代器等于 rend() 
  • rend:返回指向末尾的逆向迭代器

#include <algorithm>
#include <iostream>
#include <set>
 
int main() {
  std::set<int> set = { 3, 1, 4, 1, 5, 9, 2, 6, 5 };
  std::for_each(set.cbegin(), set.cend(), [](int x) {
    std::cout << x << ' ';
  });
  std::cout << '\n';
}

empty、size、max_size

  • empty:检查容器是否无元素,即是否 begin() == end() 
  • size:返回容器中的元素数
  • max_size:返回根据系统或库实现限制的容器可保有的元素最大数量,此值通常反映容器大小上的理论极限,至为 std::numeric_limits<difference_type>::max() 。运行时,可用 RAM 总量可能会限制容器大小到小于 max_size() 的值
#include <set>
#include <iostream>
 
int main()
{
    std::set<int> numbers;
    std::cout << "Initially, numbers.empty(): " << numbers.empty() << '\n';
 
    numbers.insert(42);
    numbers.insert(13317); 
    std::cout << "After adding elements, numbers.empty(): " << numbers.empty() << '\n';
}

#include <iostream>
#include <set>
 
int main()
{
    std::set<char> s;
    std::cout << "Maximum size of a 'set' is " << s.max_size() << "\n";
}

find、contains

  • find:寻找带有特定键的元素
  • contains:检查容器是否含有带特定键的元素
#include <iostream>
#include <set>
struct FatKey   { int x; int data[1000]; };
struct LightKey { int x; };
bool operator<(const FatKey& fk, const LightKey& lk) { return fk.x < lk.x; }
bool operator<(const LightKey& lk, const FatKey& fk) { return lk.x < fk.x; }
bool operator<(const FatKey& fk1, const FatKey& fk2) { return fk1.x < fk2.x; }
int main()
{  
// 简单比较演示
    std::set<int> example = {1, 2, 3, 4};
 
    auto search = example.find(2);
    if (search != example.end()) {
        std::cout << "Found " << (*search) << '\n';
    } else {
        std::cout << "Not found\n";
    }
 
// 通透比较演示
    std::set<FatKey, std::less<>> example2 = { {1, {} }, {2, {} }, {3, {} }, {4, {} } };
 
    LightKey lk = {2};
    auto search2 = example2.find(lk);
    if (search2 != example2.end()) {
        std::cout << "Found " << search2->x << '\n';
    } else {
        std::cout << "Not found\n";
    }
}

#include <iostream>
#include <set>
 
int main()
{
    std::set<int> example = {1, 2, 3, 4};
 
    for(int x: {2, 5}) {
        if(example.contains(x)) {
            std::cout << x << ": Found\n";
        } else {
            std::cout << x << ": Not found\n";
        }
    }
}

clear

  • clear:从容器擦除所有元素。此调用后 size() 返回零。非法化任何指代所含元素的引用、指针或迭代器。任何尾后迭代器保持合法。
#include <algorithm>
#include <iostream>
#include <set>
 
int main()
{
    std::set<int> container{1, 2, 3};
 
    auto print = [](const int& n) { std::cout << " " << n; };
 
    std::cout << "Before clear:";
    std::for_each(container.begin(), container.end(), print);
    std::cout << "\nSize=" << container.size() << '\n';
 
    std::cout << "Clear\n";
    container.clear();
 
    std::cout << "After clear:";
    std::for_each(container.begin(), container.end(), print);
    std::cout << "\nSize=" << container.size() << '\n';
}

erase、erase_if 

  • 从容器移除指定的元素。
  • 指向被擦除元素的引用和迭代器被非法化。其他引用和迭代器不受影响。
#include <set>
#include <iostream>
int main()
{
    std::set<int> c = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 从 c 擦除所有奇数
    for(auto it = c.begin(); it != c.end(); )
        if(*it % 2 == 1)
            it = c.erase(it);
        else
            ++it;
    for(int n : c)
        std::cout << n << ' ';
}

#include <set>
#include <iostream>
 
template<typename Os, typename Container>
inline Os& operator<<(Os& os, Container const& container)
{
    os << "{ ";
    for (const auto& item : container) {
        os << item << ' ';
    }
    return os << "}";
}
 
int main()
{
    std::set data { 0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7 };
    std::cout << "Original:\n" << data << '\n';
 
    auto divisible_by_3 = [](auto const& x) { return (x % 3) == 0; };
 
    const auto count = std::erase_if(data, divisible_by_3);
 
    std::cout << "Erase all items divisible by 3:\n" << data << '\n'
              << count << " items erased.\n";
}

merge

template<class C2>
void merge( std::set<Key, C2, Allocator>& source );

(1)(C++17 起)

template<class C2>
void merge( std::set<Key, C2, Allocator>&& source );

(2)(C++17 起)

template<class C2>
void merge( std::multiset<Key, C2, Allocator>& source );

(3)(C++17 起)

template<class C2>
void merge( std::multiset<Key, C2, Allocator>&& source );

(4)(C++17 起)
  • 试图提取(“接合”) source 中每个元素,并用 *this 的比较对象插入到 *this 。 若 *this 中有元素,其键等价于来自 source 中元素的键,则不从 source 提取该元素。 不复制或移动元素,只会重指向容器结点的内部指针。指向被转移元素的所有指针和引用保持合法,但现在指代到 *this 中而非到 source 中。
  • 若 get_allocator() != source.get_allocator() 则行为未定义。
#include <iostream>
#include <set>
 
// 打印出容器
template <class Os, class K>
Os& operator<<(Os& os, const std::set<K>& v) {
    os << '[' << v.size() << "] {";
    bool o{};
    for (const auto& e : v)
        os << (o ? ", " : (o = 1, " ")) << e;
    return os << " }\n";
}
 
int main()
{
    std::set<char>
        p{ 'C', 'B', 'B', 'A' }, 
        q{ 'E', 'D', 'E', 'C' };
 
    std::cout << "p: " << p << "q: " << q;
 
    p.merge(q);
 
    std::cout << "p.merge(q);\n" << "p: " << p << "q: " << q;
}

swap

void swap( set& other );                                  (C++17 前)
void swap( set& other ) noexcept(/* see below */);        (C++17 起)
  • 将内容与 other 的交换。不在单独的元素上调用任何移动、复制或交换操作。

#include <iostream>
#include <set>
 
template<class Os, class Co> Os& operator<<(Os& os, const Co& co) {
    os << "{";
    for (auto const& i : co) { os << ' ' << i; }
    return os << " } ";
}
 
int main()
{
    std::set<int> a1{3, 1, 3, 2}, a2{5, 4, 5};
 
    auto it1 = std::next(a1.begin());
    auto it2 = std::next(a2.begin());
 
    const int& ref1 = *(a1.begin());
    const int& ref2 = *(a2.begin());
 
    std::cout << a1 << a2 << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
    a1.swap(a2);
    std::cout << a1 << a2 << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
 
    // 注意交换前指代一个容器中的元素的每个迭代器在交换后都指代同一元素。对于引用同为真。
}

#include <algorithm>
#include <iostream>
#include <set>
 
int main()
{
    std::set<int> alice{1, 2, 3};
    std::set<int> bob{7, 8, 9, 10};
 
    auto print = [](const int& n) { std::cout << " " << n; };
 
    // 打印交换前的状态
    std::cout << "alice:";
    std::for_each(alice.begin(), alice.end(), print);
    std::cout << '\n';
    std::cout << "bob  :";
    std::for_each(bob.begin(), bob.end(), print);
    std::cout << '\n';
 
    std::cout << "-- SWAP\n";
    std::swap(alice,bob);
 
    // 打印交换后的状态
    std::cout << "alice:";
    std::for_each(alice.begin(), alice.end(), print);
    std::cout << '\n';
    std::cout << "bob  :";
    std::for_each(bob.begin(), bob.end(), print);
    std::cout << '\n';
}

insert、emplace、emplace_hint

  • insert:插入元素到容器
template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );
  • emplace :若容器中无拥有该关键的元素,则插入以给定的 args 原位构造的新元素到容器。
template <class... Args>
iterator emplace_hint( const_iterator hint, Args&&... args );
  • emplace_hint:插入新元素到容器中尽可能接近于恰在 hint  前的位置。原位构造元素,即不进行复制或移动操作。
#include <set>
#include <cassert>
#include <iostream>

int main()
{
    std::set<int> set;

    auto result_1 = set.insert(3);
    assert(result_1.first != set.end()); // 它是合法迭代器
    assert(*result_1.first == 3);
    if (result_1.second)
        std::cout << "insert done\n";

    auto result_2 = set.insert(3);
    assert(result_2.first == result_1.first); // 相同迭代器
    assert(*result_2.first == 3);
    if (!result_2.second)
        std::cout << "no insertion\n";
}

#include <set>
#include <cassert>
#include <iostream>
#include <functional>
#include <chrono>
#include <iomanip>

class Dew{
private:
    int a, b, c;

public:
    Dew(int _a, int _b, int _c)
            : a(_a), b(_b), c(_c)
    {}

    bool operator<(const Dew &other) const
    {
        if (a < other.a)
            return true;
        if (a == other.a && b < other.b)
            return true;
        return (a == other.a && b == other.b && c < other.c);
    }
};

const int nof_operations = 120;

int set_emplace(){
    std::set<Dew> set;
    for(int i = 0; i < nof_operations; ++i)
        for(int j = 0; j < nof_operations; ++j)
            for(int k = 0; k < nof_operations; ++k)
                set.emplace(i, j, k);

    return set.size();
}
int set_insert() {
    std::set<Dew> set;
    for(int i = 0; i < nof_operations; ++i)
        for(int j = 0; j < nof_operations; ++j)
            for(int k = 0; k < nof_operations; ++k)
                set.insert(Dew(i, j, k));

    return set.size();
}

void timeit(std::function<int()> set_test, std::string what = "") {
    auto start = std::chrono::system_clock::now();
    int setsize = set_test();
    auto stop = std::chrono::system_clock::now();
    std::chrono::duration<double, std::milli> time = stop - start;
    if (!what.empty() && setsize > 0) {
        std::cout << std::fixed << std::setprecision(2)
                  << time.count() << "  ms for " << what << '\n';
    }
}

int main()
{
    set_insert();
    timeit(set_insert, "insert");
    timeit(set_emplace, "emplace");
    timeit(set_insert, "insert");
    timeit(set_emplace, "emplace");
}

#include <set>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <functional>
 
const int nof_operations = 100500;
 
int set_emplace() {
  std::set<int> set;
  for(int i = 0; i < nof_operations; ++i) {
    set.emplace(i);
  }
  return set.size();
}
 
int set_emplace_hint() {
  std::set<int> set;
  auto it = set.begin();
  for(int i = 0; i < nof_operations; ++i) {
    set.emplace_hint(it, i);
    it = set.end();
  }
  return set.size();
}
 
int set_emplace_hint_wrong() {
  std::set<int> set;
  auto it = set.begin();
  for(int i = nof_operations; i > 0; --i) {
    set.emplace_hint(it, i);
    it = set.end();
  }
  return set.size();
}
 
int set_emplace_hint_corrected() {
  std::set<int> set;
  auto it = set.begin();
  for(int i = nof_operations; i > 0; --i) {
    set.emplace_hint(it, i);
    it = set.begin();
  }
  return set.size();
}
 
int set_emplace_hint_closest() {
  std::set<int> set;
  auto it = set.begin();
  for(int i = 0; i < nof_operations; ++i) {
    it = set.emplace_hint(it, i);
  }
  return set.size();
}
 
void timeit(std::function<int()> set_test, std::string what = "") {
  auto start = std::chrono::system_clock::now();
  int setsize = set_test();
  auto stop = std::chrono::system_clock::now();
  std::chrono::duration<double, std::milli> time = stop - start;
  if (what.size() > 0 && setsize > 0) {
    std::cout << std::fixed << std::setprecision(2)
              << time.count() << "  ms for " << what << '\n';
  }
}
 
int main() {
   timeit(set_emplace); // 栈加热
   timeit(set_emplace, "plain emplace");
   timeit(set_emplace_hint, "emplace with correct hint");
   timeit(set_emplace_hint_wrong, "emplace with wrong hint");
   timeit(set_emplace_hint_corrected, "corrected emplace");
   timeit(set_emplace_hint_closest, "emplace using returned iterator");
}

比较

#include <iostream>
#include <set>
 
int main()
{
    std::set<int> alice{1, 2, 3};
    std::set<int> bob{7, 8, 9, 10};
    std::set<int> eve{1, 2, 3};
 
    std::cout << std::boolalpha;
 
    // 比较不相等的容器
    std::cout << "alice == bob returns " << (alice == bob) << '\n';
    std::cout << "alice != bob returns " << (alice != bob) << '\n';
    std::cout << "alice <  bob returns " << (alice < bob) << '\n';
    std::cout << "alice <= bob returns " << (alice <= bob) << '\n';
    std::cout << "alice >  bob returns " << (alice > bob) << '\n';
    std::cout << "alice >= bob returns " << (alice >= bob) << '\n';
 
    std::cout << '\n';
 
    // 比较相等的容器
    std::cout << "alice == eve returns " << (alice == eve) << '\n';
    std::cout << "alice != eve returns " << (alice != eve) << '\n';
    std::cout << "alice <  eve returns " << (alice < eve) << '\n';
    std::cout << "alice <= eve returns " << (alice <= eve) << '\n';
    std::cout << "alice >  eve returns " << (alice > eve) << '\n';
    std::cout << "alice >= eve returns " << (alice >= eve) << '\n';
}

2、maps/multimaps

  • map的元素是成对的键值/实值,内部的元素依据其值自动排序
  • map内的相同数值的元素只能出现一次,multimaps内可以包含多个数值相同的元素
  • 内部由二叉树实现,便于查找

#include <cmath>
#include <iostream>
#include <map>
 
struct Point { double x, y; };
 
// 比较二个 Pointer 指针的 x 坐标
struct PointCmp {
    bool operator()(const Point *lhs, const Point *rhs) const { 
        return lhs->x < rhs->x; 
    }
};
 
int main() {
    // 注意尽管 x 坐标乱序,亦将按照递增的 x 坐标迭代 map
    Point points[3] = { {2, 0}, {1, 0}, {3, 0} };
 
    // mag 是发送结点地址到其在 x-y 平面中长度的 map
    // 尽管键为指向 Point 的指针,我们希望按照点的 x 坐标而非点的地址为 map 赋序。
    // 通过用 PointCmp 类的比较方法进行。
    std::map<Point *, double, PointCmp> mag({
        { points,     2 },
        { points + 1, 1 },
        { points + 2, 3 }
    });
 
    // 从 0 到长度更改每个 y 坐标
    for(auto iter = mag.begin(); iter != mag.end(); ++iter){
        auto cur = iter->first; // 指向 Point 的指针
        cur->y = mag[cur]; // 亦能用 cur->y = iter->second;
    }
 
    // 更新并打印每个点的长度
    for(auto iter = mag.begin(); iter != mag.end(); ++iter){
        auto cur = iter->first;
        mag[cur] = std::hypot(cur->x, cur->y);
        std::cout << "The magnitude of (" << cur->x << ", " << cur->y << ") is ";
        std::cout << iter->second << '\n';
    }
 
    // 以基于范围的 for 循环重复上述内容
    for(auto i : mag) {
        auto cur = i.first;
        cur->y = i.second;
        mag[cur] = std::hypot(cur->x, cur->y);
        std::cout << "The magnitude of (" << cur->x << ", " << cur->y << ") is ";
        std::cout << mag[cur] << '\n';
        // 注意与上述 std::cout << iter->second << '\n'; 相反,
        // std::cout << i.second << '\n'; 将不打印更新的长度
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值