使用不遵循"严格弱排序"的比较功能对列表进行排序

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

本文关键字:排序 功能 比较 列表      更新时间:2023-10-16

>我有一个包含 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;
}
这不是

一个排序

至少不是std库定义其排序。

你只想移动一些元素。

4 个步骤:

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

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

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

  • 稳定分区 B 紧随最后一个 A 之后。

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

C 保持其相对顺序。 B 保持其相对顺序。 两者都尽可能少地移动以生成所需的保证。

所有不是 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 是 C 指向 B 的 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 F G H A4 B。但事实上,B 在原始列表中排在 H 之前,现在排在 H 之后,它不符合稳定的排序。如果要保留 B <H,可以获取以下列表:>

C1 C2 A1 A2 A3 F G A4 B H

但这里是 A4

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

  • 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,或者......然后想知道在更复杂的用例中会发生什么......