接上文《数据结构和算法 | 快速排序算法原理及实现和优化(一)》,我们来讲讲快速排序的五种优化方案。
1、优化选取基准点
三数取中法:先找出三个关键字,然后排序,取中间的关键字(至少不会是两个极端)。在代码示例中我们选取相应区间的开始元素、中间元素和末尾元素。
按照所选取的基准点的顺序可以构造一颗顺序二叉树。二叉树的深度就是递归的深度。二叉树的平衡性越好,算法的性能越好。
2、优化不必要的交换
将交换改为赋值。
3、优化小数组时的排序方案
大数据排序时对快速排序算法性能的影响很小。但是,只有几个数据要排序时有效率更高的排序算法。例如,直接插入排序。直接插入排序是简单排序中性能最好的。
4、优化递归操作
递归对于算法的效率有着很大的影响。快速排序算法有两次递归操作,对我们来说太奢侈了,我们应该减少递归的调用,就可以最大程度的提高性能。我们可以利用尾递归
。
什么是尾递归呢?如果一个函数中递归形式的调用出现在函数的末尾,我们称这个递归函数是尾递归的。
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活跃记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行玫率会变得更高。
因此,只要有可能我就需要将递归函数写成尾递归的形式。
5、发挥尾递归的优势:处理小部分的的递归
对数据规模较小的部分递归,对数据规模较大的部分通过尾递归的调用分小。尽量把栈的空间利用起来。
具体优化代码
public class QuickSort01 {
public static int MAX_LENGTH_INSERT_SORT = 3;
public static void main(String[] args) {
int[] array = {5, 9, 1, 9, 5, 3, 7, 6, 1}; // 待排序数组
sort(array, 0, array.length - 1);
print(array);
}
/**
* 快速排序
*
* @param array 待排序的数组
* @param low 数组的起始地址
* @param high 数组的结束地址
*/
public static void sort(int array[], int low, int high) {
// 直到两个下标相遇,程序结束
// ------------优化点3:小规模排序使用直接插入排序------------
if (high - low > MAX_LENGTH_INSERT_SORT) {
// ------------优化点4:改为尾递归------------
while (low < high) {
// 查找 k 的位置
int k = partition(array, low, high);
// ------------优化点5:处理小部分的的递归------------
if (k - low < high - k) {
sort(array, low, k - 1);
low = k + 1;
} else {
sort(array, k + 1, high);
high = k - 1;
}
}
} else {
insertSort(array, low, high);
}
}
/**
* 快速排序,分割的过程
*
* @param array 待排序的数组
* @param low 数组的起始地址
* @param high 数组的结束地址
* @return k 值
*/
public static int partition(int array[], int low, int high) {
// ------------优化点1:优化选取基准点------------
selectPoint(array, low, high);
// 基准点
int point = array[low];
// 直到两个下标相遇,程序结束
while (low < high) {
// high 向左移动,直至遇到比point值小的记录,停止移动
while (low < high && array[high] >= point) {
high--;
}
// ------------优化点2:将交换改为赋值------------
array[low] = array[high];
//low 向右移动,直至遇到比point值大的记录,停止移动
while (low < high && array[low] <= point) {
low++;
}
// ------------优化点2:将交换改为赋值------------
array[high] = array[low];
}
// ------------优化点2:别忘记将 point 填回去------------
array[low] = point;
return low;
}
/**
* 优化选取基准点:选取相应区间的开始元素、中间元素和末尾元素。将值大小处在中间的元素放到low位置。
* @param array 待排序的数组
* @param low 数组区间的开始位置
* @param high 数组区间的结束位置
*/
public static void selectPoint(int[] array, int low, int high) {
int mid = low + (high - low)/2;
if (array[low] > array[high]) {
swap(array, low, high);
}
if (array[mid] > array[high]) {
swap(array, mid, high);
}
// 经过前面两步,最大值已经在 high 位置
// 下一步将值大小处在中间的元素放到low位置
if (array[mid] > array[low]) {
swap(array, mid, low);
}
}
public static void insertSort(int array[], int low, int high) {
// 注意:low是数组区间的开始,high是数组区间的结束
// 循环待排序的部分的数列
// 第一个数据(下标为low的数据)由于插入排序刚开始,有序表中没有任何记录,可以直接添加到有序表中
for (int i = low + 1; i < high + 1; i++) {
int temp = array[i];
int j = i;
// 如果前面的元素小于temp,则向后移
for (; j > low && array[j - 1] > temp; j--) {
array[j] = array[j - 1];
}
// 前一个元素(array[j - 1])和后一个元素(array[j])是相同的
// 在下一轮时,当array[j - 1]小于或等于temp时,将temp插入array[j](即上一轮的array[j - 1])
array[j] = temp;
}
}
/**
* 交换数组中两个元素的位置
*/
public static void swap(int array[], int low, int high) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
/**
* 打印数组
*/
public static void print(int array[]) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
5 1 3 5 9 6