C++ 关联容器:如何移除重复元素?一文即刻掌握

一、什么是重复元素?

移除重复元素只有对包含“multi”的四种关联容器有意义。因为其他容器永远没有重复元素。

对于 multimapunordered_multimap,重复元素的概念可以有多种含义:两个元素可能具有相同的键,也可能具有相同的键和值。

由于具有相同键的元素在容器中没有特定的顺序,无法在 O(n) 时间内移除 (键,值) 重复元素,因为它们可能不会相邻。因此,这里暂时不会考虑这种情况。这里只关注键来确定两个元素是否为重复元素。

对于集合,由于键和值本身就是一个,所以没有歧义。

注意,在 C++11 之前,是不知道最终保留哪个重复元素的。一般来说它将是迭代过程中遇到的第一个元素,但由于它们没有特定的顺序,因此意义不大。在 C++11 中,插入操作将元素添加到包含等效键的范围的上界。

此外,重复键在 multimapunordered_multimap 之间含义不同:前者使用等价性(使用“小于”语义),而后者使用相等性(使用“等于”语义)。这种差异也适用于 multisetunordered_multiset

因此,两个元素是“重复元素”可能有多种含义。所以,可以将其封装在一个比较策略中:DuplicatePolicy,它接受两个元素并返回一个布尔值,指示它们是否为重复元素。

在所有情况下,思路与根据谓词移除元素时相同:遍历集合并移除重复元素,同时注意不要使迭代器失效。

二、遍历算法

实现示例:

template<typename AssociativeContainer, typename DuplicatePolicy>
void unique(AssociativeContainer& container, DuplicatePolicy areDuplicates)
{
    if (container.size() > 1) {
        auto it = begin(container);
        auto previousIt = it;
        ++it;
        while (it != end(container)) {
            if (areDuplicates(*previousIt, *it)) {
                it = container.erase(it);
            } else {
                previousIt = it;
                ++it;
            }
        }
    }
}

接下来解释上面的代码原理。

判断是否需要移除重复项:

if (container.size() > 1)

该算法将同时考虑两个连续的迭代器,以进行比较。只有在容器至少包含一个元素时才能这样做。事实上,如果它没有至少两个元素,则根本没有重复元素需要移除。

auto it = begin(container);
auto previousIt = it;
++it;

这里使 it 指向容器的第二个元素,使 previousIt 指向第一个元素。

while (it != end(container))

it 是两个迭代器中领先的迭代器,循环会一直继续,直到它到达容器的末尾。

if (areDuplicates(*previousIt, *it)) {
    it = container.erase(it);
} else {
    previousIt = it;
    ++it;
}

这种结构是为了避免迭代器失效。请注意,当元素与前一个元素不等价时,将继续遍历容器,并将前一个元素移到下一个位置。

三、如何实现移除策略

可以让客户端代码通过传递一个描述如何识别两个重复元素的 lambda 来调用 unique。但这会带来几个问题:

  • 它会使 unique 的每个调用点都包含低级且冗余的信息,
  • 存在 lambda 错误的风险,尤其是在容器具有自定义比较器的情况下。

为了解决这个问题,可以提供默认值,这些默认值将对应于各种情况。

3.1、std::multimapstd::multiset

先从非哈希multi容器开始,即 std::multimapstd::multiset。它们都提供了一个名为 value_comp 的方法,该方法返回一个比较两个元素的键的函数。

看起来是比较值的函数,对吧?事实上,与它的名字相反,value_comp 对于映射来说并不比较值。它只比较键。很简单的道理,因为容器不知道如何比较与键关联的值。该方法被称为 value_comp是因为它接受值,并比较它们的键。

为了消除 std::multimap 中具有重复键的条目,可以这样实现:

[&container](std::pair<const Key, Value> const& element1,
             std::pair<const Key, Value> const& element2) {
    return !container.value_comp()(element1, element2) &&
           !container.value_comp()(element2, element1);
}

multimapmultiset 使用等价性,而不是相等性。这意味着 value_comp 返回一个比较元素在“小于”意义上的函数,而不是“等于”。要检查两个元素是否为重复元素,查看是否没有一个元素小于另一个元素。

因此,std::multimapunique 函数将是:

template<typename Key, typename Value, typename Comparator>
void unique(std::multimap<Key, Value, Comparator>& container)
{
    return unique(container, [&container](std::pair<const Key, Value> const& element1,
                                          std::pair<const Key, Value> const& element2)
    {
        return !container.value_comp()(element1, element2) &&
               !container.value_comp()(element2, element1);
    });
}

multiset 的函数遵循相同的逻辑:

template<typename Key, typename Comparator>
void unique(std::multiset<Key, Comparator>& container)
{
    return unique(container, [&container](Key const& element1,
                                          Key const& element2)
    {
        return !container.value_comp()(element1, element2) &&
               !container.value_comp()(element2, element1);
    });
}

3.2、std::unordered_multimapstd::unordered_multiset

现在转向哈希multi容器:std::unordered_multimapstd::unordered_multiset

记住,为了在一次遍历中有效地从容器中移除重复元素,这些重复元素需要彼此相邻。这样一来算法的时间复杂度是 O(n)。它不会对容器中的每个值执行完全搜索(这将是 O ( n 2 ) O(n^2) O(n2))。

但是 unordered_multimapunordered_multiset 是……无序的!所以它可能不会起作用?其实,它是可以起作用,这得益于这些容器的一个特性:具有相同键的元素在迭代顺序中保证是连续的。这就非常的好,省去了不少麻烦。

此外,这些容器遵循其键的相等性逻辑。即它们的比较函数具有“等于”的语义,而不是“小于”。

它们提供了一个方法来访问它们的比较器:key_eq,它返回一个比较键的函数。此方法是非哈希容器中 key_comp 的对应方法。但是没有 value_comp 的等价方法。没有 value_eq 可以接受两个元素并比较它们的键。因此,必须使用 key_eq,并自己将键传递给它。

以下是 std::unordered_multimap 的示例代码:

template<typename Key, typename Value, typename Comparator>
void unique(std::unordered_multimap<Key, Value, Comparator>& container)
{
    return unique(container, [&container](std::pair<const Key, Value> const& element1,
                                          std::pair<const Key, Value> const& element2)
    {
        return container.key_eq()(element1.first, element2.first);
    });
}

std::unordered_multiset 的代码遵循相同的逻辑:

template<typename Key, typename Comparator>
void unique(std::unordered_multiset<Key, Comparator>& container)
{
    return unique(container, [&container](Key const& element1,
                                          Key const& element2)
    {
        return container.key_eq()(element1, element2);
    });
}

3.3、完整示例代码

#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>

namespace details
{
    template<typename AssociativeContainer, typename DuplicatePolicy>
    void unique_associative(AssociativeContainer& container, DuplicatePolicy areDuplicates)
    {
        if (container.size() > 1)
        {
            auto it = begin(container);
            auto previousIt = it;
            ++it;
            while (it != end(container))
            {
                if (areDuplicates(*previousIt, *it))
                {
                    it = container.erase(it);
                }
                else
                {
                    previousIt = it;
                    ++it;
                }
            }
        }
    }
}
template<typename Key, typename Value, typename Comparator>
void unique(std::multimap<Key, Value, Comparator>& container)
{
    return details::unique_associative(container, [&container](std::pair<const Key, Value> const& element1,
                                                               std::pair<const Key, Value> const& element2)
    {
        return !container.value_comp()(element1, element2) &&
               !container.value_comp()(element2, element1);
    });
}

template<typename Key, typename Comparator>
void unique(std::multiset<Key, Comparator>& container)
{
    return details::unique_associative(container, [&container](Key const& element1,
                                                               Key const& element2)
    {
        return !container.value_comp()(element1, element2) &&
               !container.value_comp()(element2, element1);
    });
}

template<typename Key, typename Value, typename Comparator>
void unique(std::unordered_multimap<Key, Value, Comparator>& container)
{
    return details::unique_associative(container, [&container](std::pair<const Key, Value> const& element1,
                                                               std::pair<const Key, Value> const& element2)
    {
        return container.key_eq()(element1.first, element2.first);
    });
}

template<typename Key, typename Comparator>
void unique(std::unordered_multiset<Key, Comparator>& container)
{
    return details::unique_associative(container, [&container](Key const& element1,
                                                               Key const& element2)
    {
        return container.key_eq()(element1, element2);
    });
}

四、总结

通过本文学习了如何使用 C++ 中的关联容器来移除重复元素。从 multimapmultiset 开始,介绍了如何使用 value_comp 方法来判断重复元素,然后展示了如何利用 std::pair 来实现这一目标。讨论了 unordered_multimapunordered_multiset 的情况,介绍了如何利用 key_eq 方法来判断重复元素。提供了完整的示例代码,展示了如何编写一个通用的函数来移除重复元素。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lion 莱恩呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值