快速排序(Quick Sort)
原理:通过一趟排序将待排序的数组分割成独立的两部分,其中一部分均比另部分小,然后分别对着两部分递归排序即可,最终达到排序目的
就是找到一个枢轴,然后枢轴左边的数字均小于该枢轴,右边的均大于该枢轴,然后对左右部分递归调用即可。本质上是冒泡法的升级,属于交换排序,只不过增大了移动和比较的距离,将大的数字从后面直接移动到后面,小的数字直接移动到前面。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
import java.util.Arrays;
public class test {
public static void QuickSort(int[] arr, int low, int high) {
int pivot;
if (low < high) {
//将数组arr[low.....high]一分为2,返回枢轴位置
//这个时候枢轴左边的数均小于枢轴,右边的数均大于枢轴
pivot = Partition(arr, low, high);
//分别对枢轴左右部分递归
QuickSort(arr, low, pivot-1);
QuickSort(arr, pivot+1, high);
}
}
//交换数组中数字,使得枢轴记录到位,并返回其所在位置(索引)
//此时在它之前的数字均小于等于该枢轴,后面的数字均大于等于该枢轴
public static int Partition(int[] arr, int low, int high) {
//初始化数组的第一个数字为枢轴
int pivotkey = arr[low];
//从数组两端交替向中间扫描
while(low < high) {
//如果右边的数字大于枢轴,那么继续向后扫描,直到找到右边比枢轴小的数字
while(low < high && arr[high] >= pivotkey) high--;
//将比枢轴还小的数字交换到左边
swap(arr, low, high);
while(low < high && arr[low] <= pivotkey) low++;
swap(arr, low, high);
}
return low;//返回枢轴位置(索引)
}
public static void swap(int[] arr, int low, int high) {
int temp;
temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
public static void main(String[] args) {
int [] nums = {9, 1, 5, 8, 3, 7, 4, 6, 2};
QuickSort(nums, 0, nums.length-1);
System.out.println(Arrays.toString(nums));
}
}
优化1:选取枢轴
思想:因为一般取第一个数字作为第一个枢轴,最好的情况是枢轴是中间数字。。
三数取中:取左端、右端和中间数先进行排序,然后取中间的数作为枢轴
public static int Partition(int[] arr, int low, int high) {
//################################ 优化1 ###############################
int m = low + (high - low)/2;
if(arr[low] > arr[high]) swap(arr, low, high); //保证左端较小
if(arr[m] > arr[high]) swap(arr, high, m); //保证中间较小
if(arr[m] > arr[low]) swap(arr, m, low); //保证左端较小
//初始化数组的第一个数字为枢轴
int pivotkey = arr[low];
//从数组两端交替向中间扫描
while(low < high) {
//如果右边的数字大于枢轴,那么继续向后扫描,直到找到右边比枢轴小的数字
while(low < high && arr[high] >= pivotkey) high--;
//将比枢轴还小的数字交换到左边
swap(arr, low, high);
while(low < high && arr[low] <= pivotkey) low++;
swap(arr, low, high);
}
return low;//返回枢轴位置(索引)
}
优化2:不必要的替换
思想:枢轴这个关键字其实没必要一直交换,可以采用替换而不是交换
public static int Partition(int[] arr, int low, int high) {
//################################ 优化1 ###############################
int m = low + (high - low)/2;
if(arr[low] > arr[high]) swap(arr, low, high); //保证左端较小
if(arr[m] > arr[high]) swap(arr, high, m); //保证中间较小
if(arr[m] > arr[low]) swap(arr, m, low); //保证左端较小
//初始化数组的第一个数字为枢轴
int pivotkey = arr[low];
//备份枢轴关键字
int temp = pivotkey; //change 1
//从数组两端交替向中间扫描
while(low < high) {
//如果右边的数字大于枢轴,那么继续向后扫描,直到找到右边比枢轴小的数字
while(low < high && arr[high] >= pivotkey) high--;
//采用替换而不是交换
arr[low] = arr[high]; //change 2
while(low < high && arr[low] <= pivotkey) low++;
//采用替换而不是交换
arr[high] = arr[low]; //change 3
}
//将枢轴数字返回左端
arr[low] = temp; //change 4
return low;//返回枢轴位置(索引)
}
优化3:小数组
思想:快速排序算法适用于大的数组,如果数组特别小,建议适用直接插入排序,所以可以提前判断一下数组长度,一般选7
优化4:尾递归
思想:将尾部的两次递归减少成一次
因为第一次递归后,变量low就没用了,所以直接将pivot+1赋值给low;再循环后,来一次Partition(arr, low, high),效果等同于QuickSort(arr, pivot+1, high)
public static void QuickSort(int[] arr, int low, int high) {
int pivot;
while (low < high) { //change1 while替换if
//将数组arr[low.....high]一分为2,返回枢轴位置
//这个时候枢轴左边的数均小于枢轴,右边的数均大于枢轴
pivot = Partition(arr, low, high);
//分别对枢轴左右部分递归
QuickSort(arr, low, pivot-1);
low = pivot + 1; //change2 尾递归
}
}