使用不遵循严格弱排序的比较函数对列表进行排序'

时间:2017-07-25 11:45:59

标签: c++ algorithm sorting

我有10个项目的清单。我想以特定的方式对它们进行排序。

例如。项目为A1,B,C1,A2,A3,F,G,C2,H,A4

规则

  • C应始终在A
  • 之前
  • B应始终在A
  • 之后
  • 所有其他项目应保留其订单。

因此,排序后列表应按此顺序排列 C1 C2 A1 A2 A3 F G H A4 B

我正在尝试使用C ++ std::stable_sort()方法来实现这一目标。在我的程序中,所有项目都是结构的实例' SItem'有会员'类型'记下它的类别(A,B等)。我的比较函数看起来像这样

bool CompareItems(SItem const& item1, SItem const& item2) 
{
    if (item1.type == A && item2.type == C)
        return false;

    if (item1.type == B && item2.type == A)
        return false;

    return true;
}

根据我对stable_sort的理解,它要求比较功能遵循严格的弱排序'。显然我的方法不遵循,所以我不能使用stable_sort。他们的排序算法是否可以实现这种类型的订单?

完整代码

#include <list>
#include <algorithm>
#include <iostream>

enum ItemType
{
    A, B, C, D, E, F, G, H,
};

struct SItem
{
    SItem(ItemType t, int i) {
        type = t;
        index = i;
    }

    ItemType type;
    int index;
};

//do not follow strict week ordering
bool CompareItems(SItem const& item1, SItem const& item2) 
{
    if (item1.type == A && item2.type == C)
        return false;

    if (item1.type == B && item2.type == A)
        return false;

    return true;
}


int main()
{
    std::list<SItem> lstItems = { {A, 1}, {B, 1}, {C, 1}, {A, 2}, {A, 3}, {F, 1}, {G, 1}, {C, 2}, {H, 1}, {A, 4} };
    std::stable_sort(lstItems.begin(), lstItems.end(), CompareItems);

    return 0;
}

5 个答案:

答案 0 :(得分:11)

这不是排序

至少不是因为std库定义了它的种类。

你只想移动一些元素。

4个步骤:

  • 找到第一个A.这是我们想要推送C的地方。

  • 将所有C的稳定分区放在第一个A之前。

  • 找到最后一个A.这是我们想要推动B的地方。

  • 将Bs稳定分区到最后一个A之后。

第一个A之前的所有Cs保持静止。最后一个A之后的所有Bs都保持静止。

Cs保持相对顺序。 Bs保持相对顺序。两者都移动最少,以产生您需要的保证。

不是C或B的所有东西都保持相对顺序。

代码:

template<class It, class IsA, class IsB, class IsC>
void do_it(It s, It f, IsA a, IsB b, IsC c){
  auto first_a = std::find_if(s,f,a);
  first_a = std::stable_partition(first_a,f,c);
  auto last_a = std::find_if(std::make_reverse_iterator(f), std::make_reverse_iterator(first_a), a).base();
  std::stable_partition(s,last_a, [&b](auto&&x){return !b(x);});
}

Live example

有足够的备用内存,以上是O(n)。

当然,这可以简单地在一行中完成:

std::stable_partition(s,std::find_if(std::make_reverse_iterator(f), std::make_reverse_iterator(std::stable_partition(std::find_if(s,f,a),f,c)), a).base(), [&b](auto&&x){return !b(x);});

但我不推荐它。

答案 1 :(得分:3)

这不是一个严格的弱序,但它是partial ordering。通过部分排序进行排序的算法称为topological sort,就像这种天真的实现:

template <typename Iterator, typename Compare>
void stable_toposort(Iterator begin, Iterator end, Compare cmp)
{
    while (begin != end)
    {
        auto const new_begin = std::stable_partition(begin, end, [&](auto const& a)
        {
            return std::none_of(begin, end, [&](auto const& b) { return cmp(b, a); });
        });
        assert(new_begin != begin && "not a partial ordering");
        begin = new_begin;
    }
}

它对序列进行分区,以便将不大于任何其他元素的所有元素移动到前面。然后它将获取所有剩余的元素并以相同的方式对它们进行分区,直到不再有要分区的元素为止。它的复杂性是O(n²)比较和O(n)交换。

然后我们需要修复比较函数中的错误:

bool CompareItems(SItem const& item1, SItem const& item2)
{
    if (item1.type == C && item2.type == A) return true;
    if (item1.type == A && item2.type == B) return true;
    return false;
}

Live demo

作为参考,不稳定版本将使用partition代替stable_partition

答案 2 :(得分:2)

你想要的是某种稳定的拓扑排序。你的DAG是Cs指向Bs的As点。有关合理有效算法的说明,请参阅https://stackoverflow.com/a/11236027/585411,以实现词典(在原始列表中)订单中最低的拓扑排序。在你的情况下它的输出将是:

C1, F, G, C2, A1, A2, A3, H, A4, B

以这种方式思考可以很容易地概括您可能拥有的许多不同类型的规则,而不是特殊地说明此示例的工作方式。只要它们不加起来就是圆形路径,你的算法仍然有效。

答案 3 :(得分:0)

如果我理解你想要的算法,那么手动拆分成三个列表然后再拼接在一起可能是最简单的。

void slide_bs_and_cs( std::list<SItem>& all ) {
    // Don't touch if no A's found:
    if ( std::find(all.begin(), all.end(),
        [](const SItem& item) { return item->type == A; }) == all.end() )
        return;

    std::list<SItem> bs;
    std::list<SItem> cs;
    auto first_a = all.end();
    auto after_last_a = all.end();
    for ( auto it = all.begin(); it != all.end();
          /*advanced in loop*/ ) {
        auto next = it;
        ++next;
        if ( (*it)->type == A ) {
            after_last_a = next;
            if ( first_a == all.end() )
                first_a = it;
        } else if ( (*it)->type == B ) {
            bs.splice( bs.end(), it );
        } else if ( (*it)->type == C ) {
            cs.splice( cs.end(), it );
        }
        it = next;
    }
    all.splice( first_a, cs );
    all.splice( after_last_a, bs );
}

答案 4 :(得分:0)

非严格弱排序的问题是订单不足以定义排序列表。使用输入A1, B, C1, A2, A3, F, G, C2, H, A4,您提出了输出C1 C2 A1 A2 A3 F G H A4 B。但事实上,B在原始列表中出现在H之前,现在来之后不符合稳定排序。如果你想保留B&lt;你可以得到以下清单:

C1 C2 A1 A2 A3 F G A4 B H

但这里是A4&lt; H已被打破。

要构建稳定的排序,您必须定义严格的弱排序。要获得您建议的列表,可以使用此订单:

  • C来自A
  • B来自A
  • 所有其他字母相当于A

在这种情况下,比较功能变为:

bool CompareItems(SItem const& item1, SItem const& item2) 
{
    if (item1.type == 'C' && item2.type != 'C')
        return true;

    if (item1.type != 'B' && item2.type == 'B')
        return true;

    return false;
}

或者,您可以尝试指定接受非弱严格排序的算法,但是您必须指定当您拥有此原始列表时会发生什么 X Y ZZ < XX,YY,Z无法比较:您想要Z X YZ Y X还是Y Z X?事实上,它取决于Y是否应该被处理为等同于X或Z或......然后想知道在更复杂的用例中会发生什么......