繁体   English   中英

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

[英]Sorting a list with a comparison function that doesn't follow 'strict weak ordering'

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

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

规则是

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

所以排序后的列表应按此顺序C1 C2 A1 A2 A3 FGH 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;
}

这不是一种

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

你只想移动一些元素。

4个步骤:

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

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

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

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

第一个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);});
}

实例

有足够的备用内存,以上是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);});

但我不推荐它。

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

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;
}

现场演示

作为参考,不稳定版本将使用partition而不是stable_partition

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

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

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

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

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 );
}

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

C1 C2 A1 A2 A3 F G A4 B H

但这里A4 <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;
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM