快速排序
快速排序(Quick Sort)是由冒泡排序改进而得的。在冒泡排序过程中,只对相邻的两个记录进行比较,因此每次交换两个相邻的记录时只能消除一个逆序。如果能通过两个(不相邻)记录的一次交换,消除多个逆序,则会大大加快排序的速度。快速排序方法中的一次交换可能消除多个逆序。
算法步骤
在待排序的n个记录中任取一个记录(通常取第一个记录)作为枢轴(或支点),设其关键字为pivotkey。经过一趟排序后,把所有关键字小于pivotkey的记录交换到前面,把所有关键字大于pivotkey的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别对左、右子表重复上述过程,直至每一个子表只有一个记录时,排序完成。
其中,一趟快速排序的具体步骤如下。
1)选择待排序表中的第一个记录作为枢轴,将枢轴记录暂存在r[0]的位置上。附设两个指针low和high,初始时分别指向表的下界和上界(第一趟时,low=1;high=L.length)。
2)从表的最右侧位置依次向左搜索,找到第一个关键字小于枢轴关键字pivotkey的记录,将其移到low处。具体操作是当low<high时,若high所指记录的关键字大于等于pivotkey,则向左移动指针high(执行操作high- -);否则将high所指记录与枢轴记录录交换。
3)然后再从表的最左侧位置,依次向右搜索找到第一个关键字大于pivotkey的记录和枢轴记录交换。具体操作是:当low<high时,若low所指记录记录关键字小于等于pivotkey,则向右移动指针low(执行操作low++);否则将low所指记录与枢轴记录交换。
4)重复步骤2)和3),直至low与high相等为止。此时low或high的位置即为枢轴在此趟排序中的最终位置,原表被分成两个子表。
在上述过程中,记录的交换都是与枢轴之间发生,每次交换都要移动3次记录,可以先将枢轴记录暂存在r[0]的位置上,排序过程中只要移动与枢轴交换的记录,即只做r[low]或r[high]的单向移动,直至一趟排序结束后再将枢轴记录移至正确位置上
java代码的实现
package com.hxy.sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr ={8,5,7,6,9,5,4,2,10};
System.out.println("排序之前的顺序");
System.out.println(Arrays.toString(arr));
//int [] arr = {4,6};
//int[] arr = {1,2,3,4,5,6,7,7,7,9};
quickSort(arr,0,arr.length-1);
System.out.println("排序之后的顺序");
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr , int leftBound, int rightBound){
if(leftBound>=rightBound){
return;
}
int mid = partition(arr, leftBound, rightBound);
quickSort(arr,leftBound,mid-1);
quickSort(arr,mid+1,rightBound);
}
public static int partition(int[] arr,int leftBound,int rightBound){
int pivot = arr[rightBound];
int left = leftBound ;
int right = rightBound-1;
while (left<right){
while (arr[left]<=pivot && left<=right)left++;
while (arr[right]>pivot && left<right)right--;
if(left<right) {
swap(arr, left, right);
}
}
if(arr[left]>pivot)
swap(arr,left,rightBound);
return left;
}
static void swap(int[] arr, int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j]= temp;
}
}
结果如下图所示
动画示意图
时间复杂度
从快速排序算法的递归树可知,快速排序的趟数取决于递归树的深度。
最好情况下:每一趟排序后都能将记录序列均匀地分割成两个长度大致相等的子表,类似折半查找。在n个元素的序列中,对枢轴定位所需时间为O(n).若设T(n)是对n个元素的序列进行排序所需的时间,而且每次对枢轴正确定位后,正好把序列划分为长度相等的两个子表,此时,设Cn是一个常数,表示n个元素进行一趟快速排序的时间,则总排序的时间为
T(n)=Cn+2T(n/2)
≤n+2T(n/2)
≤n+2(n/2+2T(n/4))=2n+4T(n/4)
≤2n+4(n/4+2T(n/8))=3n+8T(n/8)
……
≤kn+2kT(n/2k)
∵k=n
∴T(n) ≤nn+nT(1)≈O(nn)
最坏情况下:在待排序序列已经排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个记录的子序列。这样必须经过n-1趟才能将所有记录定位,而且第i趟需要经过n-i次比较。这样,总的关键字比较次数为n(n-1)/2≈n2/2
这种情况下,快速排序的速度已经退化到简单排序的水平。枢轴记录的合理选择可避免这种最坏情况的出现,如利用“三者取中”的规则:比较当前表中第一个记录,最后一个记录和中间一个记录的关键字,取关键字居中的记录作为枢轴记录,事先调换到第一个记录的位置。
理论上可以证明,平均情况下,快速排序的时间复杂度为O(nlog2n)。
空间复杂度
快速排序是递归的,执行时需要一个栈来存放相应的数据。最大递归调用次数与递归输的深度一致,所以最好的情况下的空间复杂度为O(log2n) 最坏情况下为O(n)
算法特点
1)记录非顺次的移动导致排序方法是不稳定的。
2)排序过程中需要定位表的下界和上界,所以适合用于顺序结构,很难用于链式结构。
3)当n较大时,在平均情况下快速排序是所有内部排序方法中速度最快的一种,所以其适合初始记录无序、n较大时的情况。