最近在准备面试,顺手又把十大排序手敲了一遍,在写到快速排序的时候却发现了一个有意思的现象。
如下图所示,我仔细分析了快速排序的逻辑与一些边界,发现逻辑没有问题,找来找去最终发现问题出在了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]简化为i,j类似)
第二种方法则是:
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 = 0和a ^ 0 = a。
换句话说,a和a异或,一定等于0,a和0异或,则得到a本身。
我们来看看图解:
问题所在
回到我们的故事,我的swap正是用了异或的方法,但是为什么会出错呢?
可能有小伙伴已经猜到答案了,因为异或交换的方法不能对同一个地址的变量进行交换,换句话说,我们对arr中的i和j需要做一个判断,如果二者相等,是不能交换的。
为了直观对比,我们先来看不同的地址进行同一个值的变量交换是如何的,即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];
}
这样就不会有问题了。