快速排序是一种分治(Divide and conquer)递归算法。快速排序使用分治法把一个序列分割成两个子序列,其中一部分序列均比另一部分序列小,之后再递归地分别对这两部分序列继续进行排序。
快速排序是实践中的一种快速的排序算法,在对C++和Java的基本类型的排序中特别有效。它的平均时间复杂度是O(n log n),虽然它最坏情况下的时间复杂度为 O(n²),但经过一些优化可以让这种情况很难出现。
排序算法 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
快速排序 | O(n log n) | O(n log n) | O(n2) | O(log n) | 内部排序 | 不稳定 |
通过前面的步骤分析我们知道第一步先选取元素作为基准,第二步再把大于基准的元素移到基准的左边,小于基准的元素移到基准的右边,那么我们在实际当中,在代码中应该怎么去实现这两步?
怎么样选取基准?
//固定位置:选取序列的第一个元素作为基准
public static int SelectPivot(int array[],int low,int high)
{
return array[low];
}
//随机选取基准:选取序列中的随机一个元素作为基准
public static int SelectPivotRandom(int array[],int low,int high){
Random random = new Random();
// nextInt(n)生成的随机数的范围不包括n,所以我们下面加1。
int rd = random.nextInt(high - low + 1) + low;
int temp = array[high];
array[high] = array[rd];
array[rd] = temp;
return array[high];
}
假设待排序列为:8,1, 4, 9, 6, 3, 5, 2, 7, 0,
它最最左端的元素为:8,最右端的元素为:0,中心位置 (left + right)/2 上的元素为:6。
这三个位置上的元素的中值为:6,所以选取6作为基准。
Java代码实现:
/三值取中:选取序列中最左端,最右端,中间的元素的中值作为基准
public static int SelectPivotMedianOfThree(int[] array, int low, int high) {
//序列中间的元素的下标
int mid = low + ((high - low) >> 1);
//目标: array[mid] <= array[high]
if (array[mid] > array[high])
{
int temp = array[mid];
array[mid] = array[high];
array[high] = temp;
}
//目标: array[low] <= array[high]
if (array[low] > array[high])
{
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
//目标: array[low] >= array[mid]
if (array[mid] > array[low])
{
int temp = array[mid];
array[mid] = array[low];
array[low] = temp;
}
//此时,array[mid] <= array[low] <= array[high]
//low的位置上保存这三个位置中间的值
//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数
return array[low];
}
怎么将把序列中比基准值小的元素移动到基准左边,比基准值大的元素移动到基准的右边?
从右往左扫描,第一个比基准值小的元素与基准值互换;
从右往左扫描,第一个比基准值大的元素与基准值互换。
第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值小的元素与基准值互换。
第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值大的元素与基准值互换。
······
第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)个比基准值小的元素与基准值互换。
第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)比基准值大的元素与基准值互换。
······
直到指针low = high,完成第一次分割操作。
Java代码实现:
public static int[] quickSort(int array[], int low, int high) {
int pivot = SelectPivotRandom(array, low, high);
int i = low;
int j = high;
while (i < j) {
//从右往左扫描,当元素值小于等于基准值时跳出循环
while ((i < j) && (array[j] > pivot)) {
j--;
}
//从左往右扫描,当元素值大于等于基准值时跳出循环
while ((i < j) && (array[i] < pivot)) {
i++;
}
if ((array[i] == array[j]) && (i < j)) {
i++;
} else {
swap(array, i, j);
}
}
if (i - 1 > low) array = quickSort(array, low, i - 1);
if (j + 1 < high) array = quickSort(array, j + 1, high);
return (array);
}
import java.util.Random;
public class QuickSort {
public static void main(String[] args) {
int array[] = new int[]{
1, 3, 7, 6, 4, 5, 0, 2, 9, 10, 8};
int low = 0;
int high = array.length - 1;
array = quickSort(array, low, high);
for (int arr : array) {
System.out.print(arr + "\t");
}
}
public static int[] quickSort(int array[], int low, int high) {
int pivot = SelectPivotMedianOfThree(array, low, high);
//打印
for (int arr : array) {
System.out.print(arr + "\t");
}
System.out.println();
System.out.println("基准:" + pivot);
int i = low;
int j = high;
while (i < j) {
//从右往左扫描,当元素值小于等于基准值时跳出循环
while ((i < j) && (array[j] > pivot)) {
j--;
}
//从左往右扫描,当元素值大于等于基准值时跳出循环
while ((i < j) && (array[i] < pivot)) {
i++;
}
//打印
System.out.println("交换的元素下标:" + i + " " + j);
if ((array[i] == array[j]) && (i < j)) {
i++;
} else {
swap(array, i, j);
}
}
if (i - 1 > low) array = quickSort(array, low, i - 1);
if (j + 1 < high) array = quickSort(array, j + 1, high);
return (array);
}
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
//固定位置:选取序列的第一个元素作为基准
public static int SelectPivot(int array[],int low,int high)
{
return array[low];
}
//随机选取基准:选取序列中的随机一个元素作为基准
public static int SelectPivotRandom(int array[],int low,int high){
Random random = new Random();
// nextInt(n)生成的随机数的范围不包括n,所以我们下面加1。
int rd = random.nextInt(high - low + 1) + low;
int temp = array[high];
array[high] = array[rd];
array[rd] = temp;
return array[high];
}
//三值取中:选取序列中最左端,最右端,中间的元素的中值作为基准
public static int SelectPivotMedianOfThree(int[] array, int low, int high) {
//序列中间的元素的下标
int mid = low + ((high - low) >> 1);
//目标: array[mid] <= array[high]
if (array[mid] > array[high])
{
int temp = array[mid];
array[mid] = array[high];
array[high] = temp;
}
//目标: array[low] <= array[high]
if (array[low] > array[high])
{
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
//目标: array[low] >= array[mid]
if (array[mid] > array[low])
{
int temp = array[mid];
array[mid] = array[low];
array[low] = temp;
}
//此时,array[mid] <= array[low] <= array[high]
//low的位置上保存这三个位置中间的值
//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数
return array[low];
}
}