仅在相等可用时排序

时间:2014-08-15 12:03:57

标签: c++ algorithm sorting partition

假设我们有一对对矢量:

std::vector<std::pair<A,B>> v;

对于类型A,仅定义了相等性:

bool operator==(A const & lhs, A const & rhs) { ... }

您如何对具有相同first元素的所有对最终结束进行排序?需要说明的是,我希望实现的输出应该与以下内容相同:

std::unordered_multimap<A,B> m(v.begin(),v.end());
std::copy(m.begin(),m.end(),v.begin());

但是,如果可能的话,我想:

  • 进行分类。
  • 避免为等式定义哈希函数。

修改:其他具体信息。

在我的情况下,元素的数量并不是特别大(我希望N = 10~1000),尽管我必须多次重复这种排序(~400)作为更大算法的一部分,并且数据类型称为A非常大(其中包含unordered_map,其中包含~20 std::pair<uint32_t,uint32_t>,这是阻止我发明排序的结构,并且难以构建哈希功能)

4 个答案:

答案 0 :(得分:3)

如果您可以提供一个为每个唯一元素分配唯一编号的函数,那么您可以使用此唯一编号构建辅助数组,然后对辅助数组进行排序,并将其作为主数据,例如通过合并排序。

但是在这种情况下,您需要为每个唯一元素分配唯一编号的函数,即没有冲突的散列函数。我认为这应该不是问题。

如果散列函数具有O(1),则该解决方案渐近,则构建二级数组为O(N),并且使用primary进行排序为O(NlogN)。并且总结O(N + NlogN)= O(N logN)。 而这个解决方案的不好的一面是它需要双重内存。

总之,这个解决方案的主要意义是快速将您的元素转换为可以快速比较的元素。

答案 1 :(得分:3)

第一个选项:cluster()sort_within()

@MadScienceDreams的手写双循环可以写成cluster()算法,O(N * K)复杂度为N个元素和K个群集。它反复调用std::partition(使用C ++ 14样式和通用lambdas,通过编写自己的函数对象很容易适应C ++ 1,甚至是C ++ 98样式):

template<class FwdIt, class Equal = std::equal_to<>>
void cluster(FwdIt first, FwdIt last, Equal eq = Equal{}) 
{
    for (auto it = first; it != last; /* increment inside loop */)
        it = std::partition(it, last, [=](auto const& elem){
            return eq(elem, *it);    
        });    
}

您将输入vector<std::pair>称为

cluster(begin(v), end(v), [](auto const& L, auto const& R){
    return L.first == R.first;
});

要编写的下一个算法是sort_within,它接受​​两个谓词:相等和比较函数对象,并重复调用std::find_if_not以查找当前范围的结尾,然后是{{1}在该范围内排序:

std::sort

在已集群的输入上,您可以将其命名为:

template<class RndIt, class Equal = std::equal_to<>, class Compare = std::less<>>
void sort_within(RndIt first, RndIt last, Equal eq = Equal{}, Compare cmp = Compare{})
{
    for (auto it = first; it != last; /* increment inside loop */) {
        auto next = std::find_if_not(it, last, [=](auto const& elem){
            return eq(elem, *it);
        });
        std::sort(it, next, cmp);
        it = next;
    }
}

Live Example使用sort_within(begin(v), end(v), [](auto const& L, auto const& R){ return L.first == R.first; }, [](auto const& L, auto const& R){ return L.second < R.second; } ); 显示某些实际数据。

第二个选项:用户定义的比较

即使std::pair<int, int>上没有定义operator<,您也可以自行定义。这里有两个广泛的选择。首先,如果A可以播放,您可以定义

A

直接写bool operator<(A const& L, A const& R) { return std::hash<A>()(L) < std::hash<A>()(R); } 。如果您不想将所有唯一哈希值缓存在单独的存储中,则会对std::sort(begin(v), end(v))进行O(N log N)次调用。

其次,如果std::hash不可播放,但确实有数据成员getter Ax()y(),则唯一确定z()上的相等性:你可以做到

A

您可以再次直接撰写bool operator<(A const& L, A const& R) { return std::tie(L.x(), L.y(), L.z()) < std::tie(R.x(), R.y(), R.z()); }

答案 2 :(得分:2)

就地算法

for (int i = 0; i < n-2; i++)
{
   for (int j = i+2; j < n; j++)
   {
      if (v[j].first == v[i].first)
      {
         std::swap(v[j],v[i+1]);
         i++;
      }
 }

可能有更优雅的方式来编写循环,但这是O(n * m),其中n是元素的数量,m是键的数量。因此,如果m远小于n(最好的情况是所有键都相同),这可以用O(n)近似。最坏的情况下,密钥数〜= n,所以这是O(n ^ 2)。我不知道你对密钥数量的期望是什么,所以我不能真正做到平均情况,但对于普通情况来说,它很可能是O(n ^ 2)。

对于少数按键,这可能比无序多重图更快,但您必须进行测量才能找到答案。

注意:群集的顺序是完全随机的。

编辑:(在部分群集的情况下效率更高,不会改变复杂性)

for (int i = 0; i < n-2; i++)
{
   for(;i<n-2 && v[i+1].first==v[i].first; i++){}

   for (int j = i+2; j < n; j++)
   {
      if (v[j].first == v[i].first)
      {
         std::swap(v[j],v[i+1]);
         i++;
      }
 }

编辑2:在/ u / MrPisarik的评论中,删除了多余的我检查内循环。

答案 3 :(得分:2)

我很惊讶没有人建议使用std::partition。它使解决方案更好,更优雅,更通用:

template<typename BidirIt, typename BinaryPredicate>
void equivalence_partition(BidirIt first, BidirIt last, BinaryPredicate p) {
  using element_type = typename std::decay<decltype(*first)>::type;

  if(first == last) {
    return;
  }

  auto new_first = std::partition
    (first, last, [=](element_type const &rhs) { return p(*first, rhs); });

  equivalence_partition(new_first, last, p);
}

template<typename BidirIt>
void equivalence_partition(BidirIt first, BidirIt last) {
  using element_type = typename std::decay<decltype(*first)>::type;
  equivalence_partition(first, last, std::equal_to<element_type>());
}

示例here