「小技巧」不使用中间变量交换数据

415 阅读2分钟

最近在准备面试,顺手又把十大排序手敲了一遍,在写到快速排序的时候却发现了一个有意思的现象。

如下图所示,我仔细分析了快速排序的逻辑与一些边界,发现逻辑没有问题,找来找去最终发现问题出在了swap上,也就是我们常用来交换数组中两个元素的方法。

快速排序出错了

交换变量

我们最常用的方法如下:

public static void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
}

如果你喜欢coding的奇淫异技,那么你也会了解到另外两种不需要使用中间变量temp的交换方法:

public static void swap(int[] arr, int i, int j) {
  	if (i == j) return; // 注意,这种方法一样会遇到相同的问题
    arr[i] = arr[i] + arr[j];
    arr[j] = arr[i] - arr[j];
    arr[i] = arr[i] - arr[j];
}

这种方法的交换过程如下图所示:(我们将arr[i]简化为ij类似)

交换过程

第二种方法则是:

public static void swap(int[] arr, int i, int j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

异或不熟悉的同学可能会看不懂,没关系,我们只需要记住两个规律:

对于任意整数a,存在a ^ a = 0a ^ 0 = a

换句话说,aa异或,一定等于0a0异或,则得到a本身。

我们来看看图解:

交换过程

问题所在

回到我们的故事,我的swap正是用了异或的方法,但是为什么会出错呢?

可能有小伙伴已经猜到答案了,因为异或交换的方法不能对同一个地址的变量进行交换,换句话说,我们对arr中的ij需要做一个判断,如果二者相等,是不能交换的。

为了直观对比,我们先来看不同的地址进行同一个值的变量交换是如何的,即arr[i] = arr[j] = a

不同地址的相同值进行变量交换

接下来我们再看看同样的事情,发生在同一个地址上时会发生什么事情:

对同一地址的变量做异或交换

显然,问题出在了这里,当i == j时,arr[i]就会因为这个问题变为0,也就导致我们最终得到的结果中有如此多的0

结果比较

因此,异或交换虽然酷炫,但使用时必须注意陷阱,我们将代码优化一下:

public static void swap(int[] arr, int i, int j) {
    if (i == j) return;
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

这样就不会有问题了。