有没有更有效的方法来排序两个数字?

时间:2010-11-09 15:48:07

标签: c++

我希望dLowerdHigher分别具有两个双值的较低和较高值 - 即,如果它们是错误的方式,则对它们进行排序。最直接的答案似乎是:

void ascending(double& dFirst, double& dSecond)
{
    if(dFirst > dSecond)
        swap(dFirst,dSecond);
}

ascending(dFoo, dBar);

但这似乎是一件显而易见的事情,我想知道我是不是在使用正确的术语来找到标准例程。

另外,你会如何制作这种通用的?

6 个答案:

答案 0 :(得分:4)

这是接近它的好方法。它会像你要的那样高效。 我怀疑这个特定的函数有一个公认的名字。这显然叫做比较交换。

在类型上对其进行推广非常简单:

template <typename T>
void ascending(T& dFirst, T& dSecond)
{
    if (dFirst > dSecond)
        std::swap(dFirst, dSecond);
}

证实这个功能:

int main() {
    int a=10, b=5;
    ascending(a, b);
    std::cout << a << ", " << b << std::endl;

    double c=7.2, d=3.1;
    ascending(c, d);
    std::cout << c << ", " << d << std::endl;

    return 0;
}

打印:

5, 10
3.1, 7.2

答案 1 :(得分:3)

玩“非常通用”的游戏:

template <typename T, typename StrictWeakOrdering>
void comparison_swap(T &lhs, T &rhs, StrictWeakOrdering cmp) {
    using std::swap;
    if (cmp(rhs, lhs)) {
        swap(lhs, rhs);
    }
}

template <typename T>
void comparison_swap(T &lhs, T &rhs) {
    comparison_swap(lhs, rhs, std::less<T>());
}

这会勾选以下方框:

  • 使用一个小于比较器,因为它用于标准算法,因此更容易用于用户定义的类型。
  • 比较器可选可配置,默认为合理的(如果您愿意,可以使用std::greater<T>作为默认值并相应地进行修改)。它也保证对同一类型的任意指针有效,operator<不是。
  • 使用std::swap的特化或ADL找到的swap函数,以防T类型提供一个但不提供另一个。

但可能会有一些我忘记的盒子。

答案 2 :(得分:2)

正如你也问过,

  

是否有更有效的排序方式   两个数字?

考虑到效率,您可能需要编写自己的交换函数并针对std :: swap测试其性能。

以下是Microsoft实施。

template<class _Ty> inline
    void swap(_Ty& _Left, _Ty& _Right)
    {   // exchange values stored at _Left and _Right
    if (&_Left != &_Right)
        {   // different, worth swapping
        _Ty _Tmp = _Left;

        _Left = _Right;
        _Right = _Tmp;
        }
    }

如果您认为不需要检查条件if (&_Left != &_Right),则可以省略它以提高代码的性能。您可以编写自己的交换,如下所示。

template <class T>
inline void swap(T &left, T& right)
{
    T temp = left;
    left = right;
    right = temp;
}

对于我而言,10千万次调用的性能略有改善。 无论如何,您需要正确地测量与性能相关的更改。不要假设。

某些库函数的运行速度可能不如考虑通用用法,错误检查等。如果性能在您的应用程序中并不重要,建议使用库函数,因为它们经过了充分测试。

如果性能像硬实时系统一样至关重要,那么编写自己的系统并使用它是没有错的。

答案 3 :(得分:2)

让我在这里抛出一个特殊情况,只有当性能是一个绝对关键的问题并且浮点精度足够时才适用:你可以考虑矢量管道(如果你的目标CPU有一个)。

有些CPU可以通过每个指令获取向量的每个组件的最小值和最大值,因此您可以一次处理4个值 - 完全没有任何分支。

同样,这是一个非常特殊的案例,很可能与你正在做的事情无关,但我想提出这个问题,因为“更有效率”是问题的一部分。

答案 4 :(得分:1)

为什么不将std :: sort()与lambda或functor一起使用?

答案 5 :(得分:1)

除EboMike专注于编程通用性之外,所有答案均使用相同的底层比较和交换方法。我对这个问题很感兴趣,因为拥有专门的分支避免流水线效率会很好。我正在草拟一些未经测试/未进行基准测试的实现,这些实现可以通过利用条件移动指令(例如cmovl)来避免分支,从而比以前的答案更有效地进行编译。我不知道这是否体现在实际的性能提升中,但是...

可以通过在通用情况下使用“比较并交换”方法来进行这些专长来增加编程的一般性。这是一个足够普遍的问题,我真的很希望看到它正确地实现为库中一组体系结构调整的专业。

我在注释中包含了godbolt的x86程序集输出。

/*
        mov     eax, dword ptr [rdi]
        mov     ecx, dword ptr [rsi]
        cmp     eax, ecx
        mov     edx, ecx
        cmovl   edx, eax
        cmovl   eax, ecx
        mov     dword ptr [rdi], edx
        mov     dword ptr [rsi], eax
        ret
*/
void ascending1(int &a, int &b)
{
  bool const pred = a < b;
  int const _a = pred ? a : b;
  int const _b = pred ? b : a;
  a = _a;
  b = _b;
}


/*
        mov     eax, dword ptr [rdi]
        mov     ecx, dword ptr [rsi]
        mov     edx, ecx
        xor     edx, eax
        cmp     eax, ecx
        cmovle  ecx, eax
        mov     dword ptr [rdi], ecx
        xor     ecx, edx
        mov     dword ptr [rsi], ecx
        ret
*/
void ascending2(int &a, int &b)
{
  bool const pred = a < b;
  int const c = a^b;
  a = pred ? a : b;
  b = a^c;
}


/*
The following implementation changes to a function-style interface,
which I feel is more elegant, although admittedly always forces assignment
to occur, so will be more expensive if assignment is costly.
See foobar() to see that this rather nicely inlines.

        mov     eax, esi
        mov     ecx, esi
        xor     ecx, edi
        cmp     edi, esi
        cmovle  eax, edi
        xor     ecx, eax
        shl     rcx, 32
        or      rax, rcx
        ret
*/
std::pair<int,int> ascending3(int const a, int const b)
{
  bool const pred = a < b;
  int const c = a^b;
  int const x = pred ? a : b;
  int const y = c^x;
  return std::make_pair(x,y);
}


/*
This is to show that ascending3() inlines very nicely
to only 5 assembly instructions.
        # inlined ascending3().
        mov     eax, esi
        xor     eax, edi
        cmp     edi, esi
        cmovle  esi, edi
        xor     eax, esi
        # end of inlining.
        add     eax, esi
        ret
*/
int foobar(int const a, int const b)
{
    auto const [x,y] = ascending3(a,b);
    return x+y;
}
相关问题