查找不在std :: set <int> min-max中的第一个值

时间:2018-09-06 19:56:25

标签: c++ algorithm set

如何找到h中不是 的第一个值std::set<int> ids,以便将结果限制在[0, *ids.rbegin() + 1]范围内。

我看到这是一个非常简单的问题,但是我没有找到任何匹配的问题。基本上,我希望使用ids的倒置集,以便可以使用它们。

到目前为止,我有以下内容:

#incldue <set>
std::set<int> ids;
int h = 0;
for(; !ids.empty() && (h <= *ids.rbegin() + 1); ++h) {
    if(!ids.count(h)) {
        break;
    }
}
// h is now smallest value not in ids.

我怀疑是否需要进一步改进,例如不需要循环?

@edit:阐明集合中的值:在我的用例中,算法生成的值被插入到集合中。我应该说std::set<unsigned int>。我很高兴在这个问题上进行了如此多的讨论!

7 个答案:

答案 0 :(得分:5)

由于std::set的元素已排序,因此可以使用std::adjacent_find

std::adjacent_find(set.begin(), set.end(), [](int a, int b){ return a+1 != b; } );

这将使迭代器返回第一个元素a,该元素,后跟值a+1。如果没有这样的值,则为set.end()

示例用法:

std::set<int> ids { -2, -1, 0, 1, 2, 4, 5, 6 };

// This code assumes a non-empty set, since the range in the question requires same
if ( *ids.begin() > 0 )
{
    // Special case for [0
    std::cout << 0;
}
else
{
    auto res = std::adjacent_find(ids.begin(),
                                  ids.end(),
                                  [](int a, int b){ return a+1 != b; } );
    if ( res == ids.end() )
    {
        // Special case for *ids.rbegin() + 1]
        std::cout << *ids.rbegin() + 1;
    }
    else
    {
        // Print the value that should have followed this one.
        std::cout << *res + 1;
    }
}

输出:

3

答案 1 :(得分:2)

$.ajax({
    type: 'POST',
    url: 'xyz.php',
    data: {
        results: resultobj
    },
    success: function(callback){
        // Delete data from staticLS here
        localStorage.removeItem("tmemberData")
        // Output message of callback status
        $('#status').fadeIn(500).delay(5000).fadeOut(500).html(callback);
    }
});

答案 2 :(得分:2)

std::set尚未针对此问题进行优化。

天真的方法都可以为您提供O(n)性能和 O(n)性能,因为您正在遍历基于节点的数据结构。

您想要的是一个排序的“可跳跃”数据结构(它是某种树或跳过列表),其中记录了跳跃的大小。

然后,您可以跟踪增量ID和跳跃的大小;如果跨越小于id差,则其中有一个空id。如果没有,则其中没有空ID。

std中的任何关联容器都无法跟踪您所需的信息,并且由于无法访问基于“跨越”的迭代或结构,因此对其进行改造是不切实际的。

使用这种数据结构,插入和删除为O(lgn),就像“查找第一个未使用的ID”一样。没有它,找到的第一个未使用的id是O(n)。


这涉及一些繁琐的代码锻造。通过简单地存储一组范围并将其包装以确保范围不重叠,我们可以以更高的固定成本实现几乎同样的效果。

struct range {
  int base = 0;
  int length = 0;
  friend bool operator<( range lhs, range rhs ) {
    if (lhs.base != rhs.base) return lhs.base < rhs.base;
    return lhs.length < rhs.length;
  }
  bool operator()(int x) const {
    ERROR( if (length < 0) std::cout << "bad length\n"; )
    ERROR( if (base < 0) std::cout << "bad base\n"; )
    return (x>=base) && (x<(base+length));
  }
};

range是从[base, base+length)开始的半开放时间间隔。因此,[x,0)是所有x的空范围,而[x,1)仅包含x

base排序。如果您要求[x,0)

上有序集合的下限

现在我们制作一个std::set<range>并将其包装起来:

struct id_set {
  bool taken(int x) const;
  int find_unused() const;
  void take(int x);
  void recycle(int x);
  int take_unused() {
    auto r = find_unused();
    take(r);
    return r;
  }
  std::size_t count() const {
    std::size_t r = 0;
    for (auto e:state)
      r += e.length;
    ERROR( if (r!=counter) std::cout << "Counter failure\n"; )
    return r;
  }
private:
  std::set<range> state;
  using iterator = std::set<range>::iterator;
  iterator get_interval(int x) const;
  std::size_t counter = 0;
};

id_set::iterator id_set::get_interval(int x) const {
  auto start = state.lower_bound( {x,0} );
  if (start != state.end())
      if ((*start)(x))
        return start;

  if (start == state.begin() )
    return state.end();

  auto prev = std::prev(start);
  if ((*prev)(x))
    return prev;

  return state.end();
}
bool id_set::taken(int x) const {
  return get_interval(x) != state.end();
}

int id_set::find_unused() const {
  auto it = state.begin();
  if (it == state.end()) return 0;
  auto r = it->base + it->length; // we ensure these intervals are never adjacent; thus the element after the first interval is untaken
  ERROR( if (taken(r)) std::cout << "find_unused failed\n"; )
  return r;
}

void id_set::take(int x) {
  if (taken(x)) return; // nothing to do
  ++counter;

  auto merge_with_next = [&](auto next) {
    VERBOSE(std::cout << "merge_with_next\n"; )
    auto tmp = *next;
    tmp.base = x;
    ++tmp.length;
    state.erase(next);
    state.insert(tmp);
    ERROR( if (!taken(x)) std::cout << "merge_with_next failed\n"; )
  };
  auto merge_with_prev = [&](auto prev) {
    VERBOSE(std::cout << "merge_with_prev\n"; )
    auto tmp = *prev;
    ++tmp.length;
    state.erase(prev);
    state.insert(tmp);
    ERROR( if (!taken(x)) std::cout << "merge_with_prev failed\n"; )
  };
  auto merge_prev_and_next = [&](auto prev, auto next) {
    VERBOSE(std::cout << "merge_prev_and_next\n"; )
    auto tmp = *prev;
    tmp.length += next->length + 1;
    state.erase(prev);
    state.erase(next);
    state.insert(tmp);
    ERROR( if (!taken(x)) std::cout << "merge_prev_and_next failed\n"; )
  };
  auto insert_in_gap = [&] {
    VERBOSE(std::cout << "insert_in_gap\n"; )
    state.insert( {x, 1} );
    ERROR( if (!taken(x)) std::cout << "insert_in_gap failed\n"; )
  };

  if (state.empty())
    return insert_in_gap();

  auto start = state.lower_bound( {x,0} );

  // this is before the beginning, and there is a gap:
  if (start == state.begin() && start->base > x+1)
    return insert_in_gap();
  if (start == state.begin()) {
    // no gap and just before start
    return merge_with_next(state.begin());
  }
  // this is valid, because we are not begin:
  auto prev = std::prev(start);
  if (start == state.end() || start->base != x + 1) {
    if (prev->base + prev->length == x)
      return merge_with_prev(prev);

    return insert_in_gap();
  }     
  // both prev and start are valid iterators
  // start->base == x+1
  if (prev->base + prev->length == x)
    return merge_prev_and_next(prev, start);

  return merge_with_next(start);
}
// return an id:
void id_set::recycle(int x) {
  auto it = get_interval(x);
  if (it == state.end()) return; // nothing to do
  --counter;

  // create two intervals, one before and one after:      
  auto lhs = *it;
  lhs.length = x-lhs.base;
  auto rhs = *it;
  rhs.base = x+1;
  rhs.length -= lhs.length+1;
  // remove this interval:
  state.erase(it);
  // insert either non-zero length interval:
  if (lhs.length > 0)
    state.insert(lhs);
  if (rhs.length > 0)
    state.insert(rhs);
  ERROR( if (taken(x)) std::cout << "recycle failed\n"; )
}

上面可能有错别字。但是核心思想是takerecycle都是O(lgn)运算,find_unused也是。因此take_unused也是O(lgn)。

Live example

答案 3 :(得分:0)

您的版本是O(n log(n))
您可以在线性时间(O(n))中使用它:

int h = 0;
for (const int e : ids) {
    if (e != h) {
        return h;
    }
    ++h;
}
return h;

如果ids可以包含负数,则将循环更改为:

int h = 0;
for (auto it = ids.lower_bound(0); it != ids.end(); ++it) {
    if (*it != h) {
        return h;
    }
    ++h;
}
return h;

使用排序向量,您甚至可以将复杂度降低到O(log(n))

答案 4 :(得分:0)

集合中的元素已排序。因此,当您迭代元素时,只需检测第一个“泄漏”(例如,如果集合的值大于线性递增的整数值)。您可以定义从哪里开始,例如如果您想忽略值h=1,则可以写0,而且还覆盖了一组连续数字的特殊情况(结果为max + 1):

int main() {
    std::set<int> mySet = {5,4,6,2,1,0};
    int h=0;
    for(auto val : mySet) {
        if (val != h) {
            break;
        }
        h++;
    }

    cout << "not in mySet:" << h << endl;

    return 0;
}

答案 5 :(得分:0)

如果您坚持使用std::set存储一组整数,那么不幸的是,线性遍历(在其他答案中建议)是最好的选择。

只需对搜索树进行少量修改,就可以O(log n)搜索最小的丢失整数,同时保留所有其他属性-您只需要存储小于每个树节点的键的条目数即可。不确定是否可以在不重写整个std::set的情况下实现它。
UPD:事实证明您可以-通过@Yakk查看答案-Adam Nevraumont

实际上,通过在容器上进行每次操作后缓存最小值,可以使它“找到”您在O(1)中的数字,这在渐进性上是无与伦比的。当然,它不会为您带来任何实际的性能改善。

最后,如果您对整数有其他限制,则有时可以想出一些巧妙的技巧(通常发生在培训网站的编码任务中)-例如如果您知道某个范围内仅缺少一个数字,则可以在O(1)中进行算术计算。

答案 6 :(得分:0)

我已经接受@Drew Dormann的回答。但是,我修改了算法,以便可以允许将值插入std::list<int>中,从而使列表保持排序。 我还使它成为通用模板函数:

#include <algorithm>
#include <list>
#include <iterator>
#include <iostream>
// Make smallest sequential val that is not in c.
// returns [val, *c.rbegin() + 1] and suitable iterator to insert val into c.
template<typename Cont>
void make_minimal_id(const Cont & c, typename Cont::value_type & val,
                     typename Cont::const_iterator & x)
{
    if( c.empty()) {
        x = c.begin();
    } else if ( *c.begin() > val ) {
        x = c.begin();
        val = *x - 1;
    } else {
        // "one algo that gets lost in the back of the drawer and forgotten."
        x = std::adjacent_find(c.begin(), c.end(),
            [](typename Cont::value_type a, typename Cont::value_type b)
            { return a + 1 != b; } );
        if ( x == c.end() ) {
            val = *c.rbegin() + 1;
        } else {
            val = *x++ + 1;
        }
    }
}
// pretty print container 
template<typename C>
void print_cont(const C & c) {
    std::cout << std::accumulate(std::next(c.begin()), c.end(),
        std::to_string(c.front()), // start with first element
        [](std::string a, int b) {
            return a + ", " + std::to_string(b); }) << std::endl;
}

int main() {
    std::list<unsigned int> ids = { 3, 4, 5, 6, 9 };
    print_cont(ids);
    for(int i = 0; i < 6; ++i) {
        unsigned int id = 0;
        std::list<unsigned int>::iterator x;
        make_minimal_id(ids, id, x);
        ids.insert(x, id);
        print_cont(ids);
    }
    // prints:
    //{3, 4, 5, 6, 9}
    //{2, 3, 4, 5, 6, 9}
    //{1, 2, 3, 4, 5, 6, 9}
    //{0, 1, 2, 3, 4, 5, 6, 9}
    //{0, 1, 2, 3, 4, 5, 6, 7, 9}
    //{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    //{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    return 0;
}

make_minimal_id()现在可用于任何排序的容器和值。 :-)