快速排序及改进
快速排序
三向切分适用于对存在大量重复元素的数组,而随机快速排序则避免了因为数组有序带来的对普通快速排序效率的降低。
1、简单快速排序(拆东墙补西墙)
取出数组最左边的元素作为temp,将比这个元素小的元素放在左边,比它大的放在右边,第一次排序可以将最左边的元素放在正确的位置。接下来以最左边的元素为边界,调用递归,循环此过程,分别排序比它大和比它小的元素,即它左右两边的元素。
- 优点:简单快速排序可以比普通的冒泡、插入、选择排序等更快的排序数组
- 缺点:因为调用了递归,所以占用的内存空间会比较大。快速排序不稳定,其时间复杂度取决于数组元素,但数组元素是有序排序的时候,它的效率会与插入排序相近,而没有任何优势(取左边第一个元素为temp后,接下来需要递归的数组为:一个长度为N的数组)。
![image]()
点击查看代码
public static void sort(Comparable[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
//至少要有两个元素时才调用排序
if (lo >= hi) return;//递归的结束语句
int left = lo;
int right = hi;
Comparable temp = a[lo];//待排序的元素的第一个元素作为基准元素
while (left < right) {
while (right > left && a[right].compareTo(temp) >= 0) {
//因为temp拆了最左边的节点,所以要拆右边的节点去补它
right--;//扫描右边元素,找到第一个小于基准元素的元素,拆掉补回右边
}
a[left] = a[right];
while (left < right && a[left].compareTo(temp) <= 0) {
left++;//扫描左边的元素,找到第一个大于基准元素的元素,补回刚刚被拆的左边元素
}
a[right] = a[left];
}
a[left] = temp;//最后的基准元素归位
sort(a, lo, left - 1);//搞定基础元素左边元素
sort(a, right + 1, hi);//搞定基础元素右边元素
}
2、三向切分
与普通快速排序相比,这种方法将数组砍了三刀,分成了4个区域。
维护一个指针i,i不断向前移动,并且与gt、lt的值对比,满足条件时调换位置。
- 当arr[i]==temp时,i++;
- 当arr[i]>temp时,arr[i]与arr[gt]调换位置,同时gt--;(原gt对应的元素其实不能确定与temp的关系,但是交换后的gt一定是比temp大的。所以每次与gt交换位置后,gt--,而i不变,等待下一次判断与temp关系)
- 当arr[i]<temp时,arr[i]与arr[lt]调换位置,同时i++,lt++;(lt位置元素其实始终等于temp);
当i>=gt时,循环结束,调用递归排序lo-(lt-1)和(gt+1)-hi,直到lo>=hi时结束递归。
- 优点:当数组存在大量重复元素时,调用三向切分可以极大的提高速率,原因在于中间有了一段lt-gt(=temp)的数据不用继续递归排序。
- 当没有相同的元素时,三向切分的速度会略小于普通的快速排序
![image]()
点击查看代码
//三向分段
public static void sortThreeWays(Comparable[] arr) {
sortThreeWays(arr, 0, arr.length - 1);
}
private static void sortThreeWays(Comparable[] arr, int lo, int hi) {
if (hi <= lo) return;
int lt = lo, gt = hi, i = lo + 1;
Comparable v = arr[lo];
while (i <= gt) {
int cmp = arr[i].compareTo(v);
if (cmp < 0) {
Comparable temp = arr[i];
arr[i++] = arr[lt];
arr[lt++] = temp;
} else if (cmp > 0) {
Comparable temp = arr[i];
arr[i] = arr[gt];
arr[gt--] = temp;
} else {
i++;
}
}
sortThreeWays(arr, lo, lt - 1);
sortThreeWays(arr, gt + 1, hi);
}
3、取随机值快速排序
与简单快排相比,这里多了一个步骤,即在数组里面取一个随机的元素,并且将它与最左边的元素调换位置。相当于就是在排序前先调换一下两个数组的位置。然后接下来依然是和简单快速排序一样的步骤。
- 优点:通过将普通快速排序改善,我们不再重复挑选最左边的值作为temp,而是在lo-hi随机取值作为temp,这样可以避免由于数组有序带来的不稳定性,提高排序效率。(如果数组有序,由于我们挑选的temp不是最左边的,理想状态下是左右对半分,接下来的需要递归的数组为两个长度为N/2的数组)。
- 缺点:占用内存空间。
点击查看代码
//随机值快排
public static void sort(Comparable[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(Comparable[] arr, int lo, int hi) {
if (lo >= hi)return;
// 当一定的排序后换为插入排序,发现栈溢出了,而且速度更慢
/*{
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j].compareTo(arr[j - 1]) < 0; j--) {
Comparable temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}*/
int left = lo;
int right = hi;
int index=(int)(lo+Math.random()*(hi-lo));
Comparable temp = arr[index];
arr[index]=arr[lo];
arr[lo]=temp;
while (left < right) {
while (left < right && arr[right].compareTo(temp) >= 0) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left].compareTo(temp) <= 0) {
left++;
}
arr[right] = arr[left];
}
arr[left] = temp;
sort(arr, lo, left - 1);
sort(arr, right + 1, hi);
}



浙公网安备 33010602011771号