关于算法:如何使用最多两次交换对三个变量进行排序?

How to sort three variables using at most two swaps?

下面的算法可以对K型的xyz三个变量进行排序,这些变量可以用operator<进行比较:

1
2
3
4
5
6
7
8
9
10
void sort2(K& x, K& y) {
   if(y < x)
      swap(x, y);
}      

void sort3(K& x, K& y, K& z) {
   sort2(x, y);
   sort2(y, z);
   sort2(x, y);
}

在"最坏情况"下,这需要三次交换。然而,基础数学告诉我们,三个值的排序只能用两个交换来完成。

示例:值(C、B、A)将使用三个交换进行排序:(C、B、A)->(B、C、A)->(B、A、C)->(A、B、C)。然而,一次交换就足够了:(C,B,A)->(A,B,C)。

在所有情况下,最简单的算法是什么,用最多两个交换对三个变量进行排序?


找到最小值,进行两次比较,然后将其交换到第一个位置。然后比较其余2个,必要时进行交换。

1
2
3
4
5
6
7
if (x < y) {
   if (z < x) swap(x,z);
} else {
  if (y < z) swap(x,y);
  else swap(x,z);
}
if(z<y) swap(y,z);

这需要3个比较,但只有两个交换。


1
2
3
4
5
void sort(int& a, int& b, int& c)
{
   swap(a, min(a, min(b, c)));
   swap(b, min(b, c));
}

2次交换,3次比较。


2到3个比较,0到1.7个交换

旧问题,新答案…下面的算法对xyz进行排序,根据它们的值和0到1.7的交换操作进行2到3次比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void sort3(K& x, K& y, K& z)
{    
    if (y < x) {
        if (z < x) {
            if (z < y) {
                swap(x, z);
            } else {
                K tmp = std::move(x);
                x = std::move(y);
                y = std::move(z);
                z = std::move(tmp);
            }
        } else {
            swap(x, y);
        }
    } else {
        if (z < y) {
            if (z < x) {
                K tmp = std::move(z);
                z = std::move(y);
                y = std::move(x);
                x = std::move(tmp);
            } else {
                swap(y, z);
            }
        }
    }
}

那么,它是如何工作的呢?它基本上是一个展开的插入排序:如果值已经排序(需要两个比较来检查),那么算法就不会交换任何内容。否则,它执行1或2个交换操作。但是,当需要2个交换操作时,算法是什么?&旋转?值,以便执行4个移动而不是6个(交换操作应花费3个移动,除非优化)。

只有6种可能的3值排列。这个算法进行必要的比较,以了解我们正在处理的排列。然后交换离开。因此,该算法有6个可能的路径(包括因为数组已经排序而不执行任何操作的路径)。虽然它仍然是人类可读的,但对4个值进行排序的等效优化算法将有24个不同的路径,并且更难读取(对于n个值,有n个!可能的排列)。

既然我们已经在2015,你似乎正在使用C++,我自由地使用EDCOX1,4,因此,确保交换旋转thigy将是足够的效率,甚至可以工作,但不可移动类型。


找到最小值并将其与第一个值交换。找到第二个最小值并将其与第二个值交换。最多两次交换。

这基本上是选择排序,最多执行n - 1交换。


如果你没有在适当的地方做,你可以在没有任何交换的情况下执行它。


我最近不得不解决一个类似的问题——高效地对三个值进行排序。在您的问题中,您专注于交换操作。如果您正在寻找性能,请集中精力比较操作和分支!当用三个值对这样一个"微小"的数组进行排序时,一个好主意是考虑使用额外的存储,这对于如此少的值是合适的。我想出了一个专门的"合并排序"(见下面的代码)。

正如Tenfour所建议的,我研究了这个程序集,下面的代码编译成一组紧凑的内联CPU寄存器操作,速度非常快。附加变量"arr12"也存储在CPU寄存器中。排序需要两个或三个比较操作。可以很容易地将函数转换为模板(此处不作说明)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
inline void sort3_descending( double * arr )
{
    double  arr12[ 2 ];

    // sort first two values
    if( arr[ 0 ] > arr[ 1 ] )
    {
        arr12[ 0 ] = arr[ 0 ];
        arr12[ 1 ] = arr[ 1 ];
    } // if
    else
    {
        arr12[ 0 ] = arr[ 1 ];
        arr12[ 1 ] = arr[ 0 ];
    } // else

    // decide where to put arr12 and the third original value arr[ 3 ]
    if( arr12[ 1 ] > arr[ 2 ] )
    {
        arr[ 0 ] = arr12[ 0 ];
        arr[ 1 ] = arr12[ 1 ];
    } // if
    else if( arr[ 2 ] > arr12[ 0 ] )
    {
        arr[ 0 ] = arr  [ 2 ];
        arr[ 1 ] = arr12[ 0 ];
        arr[ 2 ] = arr12[ 1 ];
    } // if
    else
    {
        arr[ 0 ] = arr12[ 0 ];
        arr[ 1 ] = arr  [ 2 ];
        arr[ 2 ] = arr12[ 1 ];
    } // else
}


好问题:)

如果程序集对您可用,并且值适合一个寄存器,那么您可以非常快速地将它们加载到寄存器中并进行一些比较,跳到正确的场景中将值放回。也许您的编译器已经进行了优化。

不管怎样,如果性能是你的目标,看看生成的机器代码并在那里进行优化。对于这样一个小的算法,您可以从中挤出性能。


我认为你想要的是在每个步骤中找到最佳的交换,而不仅仅是一个有效的交换。要做到这一点,只需在列表的后面找到元素和元素之间最大的区别,并交换它们。在三元组中,有三种可能的交换:1-3、1-2和2-3。在每一步中,找出这三个交换之间的最大差异,并做到这一点。很肯定,在最坏的情况下,这会给3个元素提供两个交换。只有当交换相对比较元素比较昂贵时才真正有意义,否则可能不值得预先进行额外的分析。


将排序网络编码到表中。我链接的wikipedia文章应该可以帮助您提供参考资料,以防您需要在其他情况下(即更大的数组)找出放在表中的内容。


这可以用一个与每种可能的比较组合相关的真值表来说明,看看我们如何最好地优化您在这里提到的交换。

值x

X,Y,Z_Y_Y_Y

X,Z,Y_Y_N_Y

Y,X,Z N Y Y

Y,Z,X N Y N

Z,X,Y_Y_N_N

Z,Y,X N N N

通过这种方式构建问题,我们可以很容易地看到,通过最初检查和交换第一个和第三个元素,交换后第一个元素中的最小值可以是x或y。这简化了之后的if检查,以便我们可以在x>y时交换第一个和第二个元素,或者在x>y时交换第二个和第三个元素。y>

1
2
3
4
5
6
7
8
9
if (x > z) {
    swap(x,z);
}

if (x > y) {
    swap(x,y);
} else if (y > z) {
    swap(y,z);
}

不需要任何嵌套的if条件。仅2-3个简单比较,最多2个交换。