【问题标题】:Get the closest element to a given element in an std::set获取与 std::set 中给定元素最近的元素
【发布时间】:2019-11-20 08:24:35
【问题描述】:

我有一组(排序的)未签名的int。我需要找到最接近给定数字的元素。

我正在寻找使用标准库的解决方案, 我的第一个解决方案是使用二进制搜索,但 STL 的实现仅在元素存在时才返回。 这篇 Find Closest Element in a Set 的帖子很有帮助,我实现了一个基于 std::lower_bound 方法的解决方案,

(*假设集合有超过 2 个元素,则不进行空/边界检查):

#include <iostream>
#include<set>
#include<algorithm>
#include<cmath>

int main()
{
    std::set<unsigned int> mySet = {34, 256, 268, 500, 502, 444};
    unsigned int searchedElement = 260;
    unsigned int closestElement;

    auto lower_bound = mySet.lower_bound(searchedElement);
    if (lower_bound == mySet.end()){
        closestElement = *(--lower_bound);
    }

    std::set<unsigned int>::iterator prevElement = --lower_bound;
    bool isPrevClosest = std::abs(*prevElement - searchedElement) > std::abs(*lower_bound - searchedElement);
    closestElement = isPrevClosest ? *prevElement : *lower_bound;

    std::cout << closestElement << std::endl;

    return 0;
}

有没有更简单更标准的解决方案?

【问题讨论】:

  • 可能我没看懂问题,但是find有什么问题?它返回一个迭代器。你只需要移动一步。
  • @ZDF,是的,成员.find() 确实是对数的。但是这里怎么用呢?它查找特定元素,与std::find 相比,它不需要谓词(这就是我假设std::find 的原因)。
  • 我假设 OP 正在寻找集合中的一个元素。我的错。

标签: c++ stl set


【解决方案1】:

我认为没有比使用.lower_bound 更好的解决方案了。您可以将算法包装到函数模板中:

template<typename Set>
auto closest_element(Set& set, const typename Set::value_type& value)
    -> decltype(set.begin())
{
    const auto it = set.lower_bound(value);
    if (it == set.begin())
        return it;

    const auto prev_it = std::prev(it);
    return (it == set.end() || value - *prev_it <= *it - value) ? prev_it : it;
}

此函数正确处理所有极端情况(空集、一个元素、第一个元素、最后一个元素)。

例子:

std::set<unsigned int> my_set{34, 256, 268, 500, 502, 444};

std::cout << *closest_element(my_set, 26);   // Output: 34
std::cout << *closest_element(my_set, 260);  // Output: 256
std::cout << *closest_element(my_set, 620);  // Output: 502

请注意,您的代码中的std::abs(几乎)什么都不做:它的参数具有无符号类型并且始终为非负数。但是我们知道std::set 元素是有序的,因此我们知道*prev_it &lt;= value &lt;= *it,不需要std::abs()

【讨论】:

    【解决方案2】:

    您可以使用 std::min_element() :作为比较器,给它一个返回绝对差异的 lambda,例如

    std::min_element(mySet.begin(), mySet.end(), [searchedElement](const unsigned int a, const unsigned int b) {
      return std::abs(searchedElement - a) < std::abs(searchedElement - b);
    });
    

    但是,我确实认为这将不再适用二分搜索...

    编辑:另外,如下面的 cmets 所述,当 x &lt; y 时,对于 unsigned int 值的 std::abs(x - y) 可能会返回一个意外的大整数。

    【讨论】:

    • std::abs(x) 在这里毫无意义:x 具有无符号类型并且始终为非负数。
    • @Evg 两个正数的差不一定是正数
    • @foreknownas_463035818,在 C++ 中,两个无符号数之间的差异总是无符号且非负数:static_assert(3u - 5u &gt; 0)
    • @Evg 但随后删除对std::abs 的调用是错误的解决方法,因为3u - 5u &gt; 4u - 3u。不是没有意义,而是用错了
    • @Evg 不,它不是[正确的词],因为意图很明确,如果它按预期工作,它会做正确的事情,只是它没有;)
    【解决方案3】:

    std::set 容器适用于查找相邻元素,即查找在给定元素之后或之前的元素。考虑到您面临的问题:

    我正在寻找使用标准库的解决方案,我的第一个解决方案是使用二进制搜索,但 STL 的实现只有在元素存在时才返回

    仍然有一种方法可以在不改变逻辑的情况下遵循:如果集合中不存在您要查找的最接近的元素,那么您只需将其插入集合中(它需要对数时间)集合的大小)。接下来,您将找到与刚刚添加的元素最近的元素。最后,完成后将其从集合中移除,以使集合与之前保持一致。

    当然,如果元素已经在集合中,则无需向集合中插入或移除任何内容。因此,您需要跟踪是否添加了该元素。


    下面的函数是上面阐述的想法的一个例子:

    #include <set>
    
    unsigned int find_closest_element(std::set<unsigned int> s, unsigned int val) {
       bool remove_elem = false;
       auto it = s.find(val);
    
       // does val exist in the set?
       if (s.end() == it) {
          // element does not exist in the set, insert it
          s.insert(val); 
          it = s.find(val);
          remove_elem = true;
       }
    
       // find previous and next element
       auto prev_it = (it == s.begin()? s.end(): std::prev(it));
       auto next_it = std::next(it);
    
       // remove inserted element if applicable
       if (remove_elem)
          s.erase(it);
    
       unsigned int d1, d2;
       d1 = d2 = std::numeric_limits<unsigned int>::max();
    
       if (prev_it != s.end())
          d1 = val - *prev_it;
    
       if (next_it != s.end())
          d2 = *next_it - val;
    
       return d1 <= d2? *prev_it: *next_it;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      • 2018-08-02
      • 2015-05-22
      • 2012-05-08
      • 2014-06-12
      • 2011-11-09
      相关资源
      最近更新 更多