【问题标题】:Sorting 3 numbers without branching对 3 个数字进行排序而不进行分支
【发布时间】:2011-12-27 05:49:12
【问题描述】:

在 C# 或 C++ 中,如何实现三个(整数)数字的无分支排序?

这可能吗?

【问题讨论】:

  • 嗯?这些是随机的句子吗?
  • 我不明白你的问题。请告诉我们您的最佳猜测。无条件排序是什么意思?
  • 我并不是要刻薄,但您的句子格式不正确,几乎无法理解。试着清理你的语法,更明确地说明你想要达到的目标,你将更有可能得到答案。正如@MooingDuck 所说,示例非常有帮助。
  • 解释一下“无条件”是什么意思。
  • 我很确定问题是“如何在不使用条件运算符的情况下对 3 个整数值进行排序”。更实用的版本是“如何为 3 个值编写无分支排序”。如果您的英语不是那么好,这里肯定会很糟糕,但我想大多数国际软件开发也是如此。

标签: c# c++


【解决方案1】:

没有条件。只有一个转换为 uint。完美的解决方案。

int abs (int a) 
{
    int b = a;
    b = (b >> (sizeof(int)*CHAR_BIT-1) & 1);
    return 2 * b * (a) + a; 
}
int max (int a, int b) { return (a + b + abs(a - b)) / 2; }
int min (int a, int b) { return (a + b - abs(a - b)) / 2; }


void sort (int & a, int & b, int & c)
{       
   int maxnum = max(max(a,b), c);
   int minnum = min(min(a,b), c);
   int middlenum = a + b + c - maxnum - minnum;
   a = maxnum;
   b = middlenum;
   c = minnum;
}

【讨论】:

  • 不起作用,因为(以及其他更容易修复的错误)abs 不返回 a 的绝对值。
  • 更正了所有错误。我不喜欢依赖知道 int 的大小,但在不知道的情况下,我需要一个 while 循环,这是一个条件。由于它首先执行 2 * b,因此它是安全的。但是,如果您想确定,可以在 2 * b 周围添加括号
  • @Xaade:为什么不直接问编译器 int 的大小是多少 :)
  • 对于负值的>> 的结果通常存在存在疑问。使用 C# 可能会解决问题,问题允许这样做 ;-)
  • b = (b >> (sizeof(int)*CHAR_BIT-1) & 1); 具有负数的实现行为。 2 * b * (a) + a; 应该是 2 * b * (-a) + a; 并且在算术溢出的情况下会有未定义的行为。 return (a + b + abs(a - b)) / 2; 潜在的算术溢出,int middlenum = a + b + c - maxnum - minnum; 也一样...恐怕不是一个完美的解决方案。
【解决方案2】:

您可以在 C++ 中使用:

#include <iostream>

void sort(int *in) {
  const int sum = in[0]+in[1];
  const int diff = abs(in[1]-in[0]);
  in[0] = (sum + diff) / 2;
  in[1] = (sum - diff) / 2;
}

int main() {
  int a[] = {3,4,1};
  sort(a);
  sort(a+1);
  sort(a);
  std::cout << a[0] << "," << a[1] << "," << a[2] << std::endl;

  int b[] = {1,2,3};
  sort(b);
  sort(b+1);
  sort(b);
  std::cout << b[0] << "," << b[1] << "," << b[2] << std::endl;
}

诀窍在于将最小/最大元素表示为算术运算,而不是分支然后在对上调用 sort 足够的时间来“冒泡排序”它们。


我制作了一个完全通用的版本,使用模板元编程调用sort 正确的次数。在我的 x86 机器上使用 gcc 4.7.0 时,这一切都完全内联(尽管 call 在 x86 上是无条件的)。我还实现了一个 abs 函数,该函数避免了 x86 上的分支(它对整数做了一些假设,使其不那么可移植,但它基于 gcc 的 x86 的 __builtin_abs 实现):

#include <iostream>
#include <limits.h>

void myabs(int& in) {
  const int tmp = in >> ((sizeof(int) * CHAR_BIT) - 1);
  in ^= tmp;
  in = tmp - in;
}

template <int N, int I=1, bool C=false>
struct sorter {
  static void sort(int *in) {
    const int sum = in[I-0]+in[I-1];
    int diff = in[I-1]-in[I-0];
    myabs(diff);
    in[I-0] = (sum + diff) / 2;
    in[I-1] = (sum - diff) / 2;
    sorter<N, I+1, I+1>=N>::sort(in);
  }
};

template <int N,int I>
struct sorter<N,I,true> {
  static void sort(int *in) {
    sorter<N-1>::sort(in);
  }
};

template <int I, bool C>
struct sorter<0,I,C> {
  static void sort(int *) {
  }
};

int main() {
  int a[] = {3,4,1};
  sorter<3>::sort(a);
  std::cout << a[0] << "," << a[1] << "," << a[2] << std::endl;
}

【讨论】:

  • 两次交换不足以使所有 6 个排列都按正确的顺序排列。例如,1 2 3 被“排序”为2 3 1
  • 我很确定 abs 分支的默认实现,但您可以通过十六进制 ANDing 来避免分支,以删除负性位。还取决于 OP 是否将函数调用视为一个分支,您可能希望显式内联该方法。但这些都是微不足道的 - 很好的答案!
  • @FredOverflow - 你是对的,我已经通过添加额外的交换来解决这个问题。
  • @Dracorat - 我添加了另一个版本,其中包括myabs,它是无分支的。 (不过,GCC 在幕后就是这样做的)。调用函数是无条件的,因此不能真正被视为分支。
  • 好吧,我不能多次 +1 你的代码 =)
【解决方案3】:

您可以编写maxminswap 无分支函数。一旦你有了这些函数,你就可以用它们把sort函数写成:

void sort(int &a, int &b, int &c)
{
    int m1 = max(a,b,c);
    int m2 = min(a,b,c);
    b = a + b + c - m1 - m2;
    swap(m1, a);
    swap(m2, c);
}

这里是辅助函数:

void swap(int &a, int &b)
{
   int tmp = a; a = b; b = tmp;
}

int max( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a<b ], c };
   return l2[ l2[0] < c ];
}
int min( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a>b ], c };
   return l2[ l2[0] > c ];
}

测试代码:

int main() {
        int a,b,c;
        std::cin >> a >> b >> c;
        sort(a,b,c);
        std::cout << a <<"," << b << "," << c << std::endl;
        return 0;
}

输入:

21 242 434

输出(降序):

434, 242, 21

演示:http://ideone.com/3ZOzc

我已经从here 的@David 的回答中获取了max 的实现,并且几乎没有扭曲地实现了min

【讨论】:

  • 在 x86 上将成为分支语句,
  • @awoodland:不是这样。只需通过优化编译即可。
  • 确实如此,例如 g++ -O4 -S (4.7.0) min 有两条 jle 指令,max 有两条 jge 指令,它们都在查看 cmpls 的结果
  • 您引用的答案不正确cmpl 后跟cmovg 一个分支。 cmovgecmpl 读取条件代码,它之后的代码确实取决于之前的结果。查看条件代码就是分支的定义。
  • @awoodland:好的。我改变了我的解决方案,采用大卫的max 实现。
猜你喜欢
  • 2018-06-06
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 2012-07-05
  • 1970-01-01
  • 2013-11-29
  • 1970-01-01
  • 2018-11-04
相关资源
最近更新 更多