【问题标题】:c# using bitwise XOR for swappingc# 使用按位异或进行交换
【发布时间】:2010-10-13 19:58:53
【问题描述】:
void swap(ref int x, ref int y)
{       x = x ^ y;   y = y ^ x;   x = x ^ y;  }

我正在学习按位异或。这种交换是如何发生的?这让我大吃一惊。这种方法应该交换X和Y的内容,但我完全不明白发生了什么?

【问题讨论】:

  • 用二进制的 x 和 y 值示例在纸上写下操作。
  • 保持这种状态,不要使用它。总有一天谁会维护你的代码也不会。你会在 C++ 标签下找到几个关于它的线程,寻找被否决的线程。否则用 0 和 1 写出来以查看它的工作原理。两个比特就足够了。
  • 这是我在第一年的 compsci 课上学到的交换技巧之一。在没有任何理由的情况下执行这种最微小的微优化时,我不禁感到愚蠢,而明显的替代方案 (temp = x; x = y; y = temp;) 清晰一百万倍。并且对于编译器来说,优化也可能容易上百万倍。
  • 尝试交换(参考 x,参考 x)。惊喜! :-)
  • 这是在 60 年代末和 70 年代初编写的 HLASM 应用程序中的常见做法,即使是大型机也几乎没有内存来托管应用程序。 XOR 技术的好处是它使用更少的指令来交换寄存器或变量,因此需要更少的空间。不幸的是,我一直在维护这样一个程序,直到它被 COBOL 重写为 Y2K。我认为该技术在现代系统中没有任何用处。

标签: c# algorithm


【解决方案1】:

小心。这种方法有一个非常非常讨厌的特性。它被用于Underhanded C Contest 的其中一个条目中。它会很愉快地工作......直到有一天有人尝试交换两个数组元素 a[i] 和 a[j] 之类的事情,但没有保证 i!=j。

当两个引用都引用同一个变量时,此方法会将其归零。

【讨论】:

  • 原则上你是正确的。 Swap(ref x, ref x) 将变量 x 清零。在 C/C++ 中,这种方法存在您描述的缺陷。但是,在 C# 中,无法通过 ref 传递数组元素,因此特定情况实际上不会发生。
  • @LBushkin:我相信你错了。您不能通过引用传递的是索引器的输出,因此如果aList,则swap(ref a[0], ref a[0]) 将不会编译,但如果它是一个整数数组,则将(并将执行)。
【解决方案2】:

维基百科对Swap-By-XOR algorithm 有很好的解释。

这个算法有效的形式证明有点复杂,需要使用二进制数的数学特性。

但在简化的形式中,我们可以将二进制值的每一位视为单独的,因为 XOR 操作独立地作用于每一位。因此,证明它适用于 1 位值就足够了,因为通过归纳我们可以证明它适用于任何长度的二进制值。为这些操作构建合适的真值表非常简单,因此我将其省略。

异或交换并不是唯一可能的“创造性”交换算法。使用算术运算也可以得到类似的结果:

void Swap( ref int x, ref int y )
{
  x = x + y; 
  y = x - y;
  x = x - y; 
}

从实践的角度来看,这是一种在大多数情况下都应该避免的技术。 正如您自己所认识到的,这种方法的逻辑并不是很明显,它可能会导致可维护性和可扩展性问题...其中最重要的一点是Swap( ref x, ref x ) 不会按照方法名称的含义执行(实际上它会将值归零)。

【讨论】:

    【解决方案3】:

    一次只看一点,一步一步。

    x|y -> x = x ^ y x|y -> y = y ^ x x|y -> x = x ^ x x|y
    0|0              0|0              0|0              0|0
    0|1              1|1              1|0              1|0
    1|0              1|0              1|1              0|1
    1|1              0|1              0|1              1|1
    

    在这里您可以清楚地看到每种情况下的结果都是位交换。从这里很清楚为什么它在一般情况下有效。更多formal解释是可能的。

    每当您发现自己说“我根本不明白发生了什么”时,这强烈表明您应该找到一种更清晰的方法来编写语义等效的代码。

    【讨论】:

    • 你有一个错字,最后一步是 x = x ^ y;但表格条目当然是正确的.. :)
    【解决方案4】:

    swap() 可以在大多数现代架构(包括 i686 和 x86_64)上使用单个 CPU 指令来实现。因此,您最好以编译器可以识别和相应转换的方式编写它,而不是尝试以实际上会使您的代码更慢且可读性更低的方式进行微优化。

    【讨论】:

      【解决方案5】:

      首先看看异或是如何工作的:

      a | b | a^b
      - - - - - -
      0 | 0 | 0
      1 | 0 | 1
      0 | 1 | 1
      1 | 1 | 0
      

      因此,如果输入不同,则异或运算的结果为 1 或 true,如果输入相等,则为 0。另一种看待它的方式是将其视为不带进位的加法,我将其表示为 (+) :

      x = x ^ y <=> x = x (+) y;
      y = y ^ x <=> y = y (+) x;
      x = x ^ y <=> x = x (+) y;
      

      第一步:将 x 中为 0 但 y 中为 1 的所有位设置为 1。现在 x 中的某些位是错误的,因为如果 x 中的位为 1 且 y 中的位为 1,它将设置为0。其他位正确。

      第二步:现在将我们在 x 中设置为 1 的相同位位置设置为 y 中的 0(我们现在完成了 y)。这是可行的,因为 x 已经将所有不同的位设置为 1,因此现在将 y 与 x 进行异或基本上意味着:切换 y 中设置为 1 的 x 中的位。我们现在完成了 y :)

      第三步:现在我们仍然需要将x中的那些位设置为1,这些位最初已经设置为1,并且在第一步回到1之后被重置为0。如何?我们最后一次将 x 与 y 异或,因为 xor 是做什么的?如果 2 个输入不同,它会将位设置为 1,这正是我们所需要的。

      如果您仍然对此感到困惑,您应该将其画在纸上并试玩以了解其工作原理和/或参考 Jason 上面所做的表格。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多