八大排序
1.快速排序的思想
平均时间复杂度:O(n logn)
空间复杂度:O(logn)~O(n)
稳定性:不稳定×
在 A[1…n]中任取一个数据元素作为比较的“基准”(不妨记为 X),将数据区划分为左右两个部分:A[1…i-1]和
A[i+1…n],且 A[1…i-1]≤X≤A[i+1…n] (1≤i≤n),当 A[1…i-1]和 A[i+1…n]非空时,分别对它们进行上述的划分过程,直至所有数据元素均已排序为止。
2.算法实现
可以使用递归函数实现这一算法。假定待排序序列的下标范围为 low~high。
借用两个整型变量 i、j 作为指针,约定初值分别为 low、high。
排序过程:
① 选定基准 X(不妨用 A[low])
② j 向前扫描,直到 A[j],交换 A[i]与 A[j],i+1。保证了 A[low…i-1]≤X
③ i 向后扫描,直到 A[i]>X,交换 A[i]与 A[j],j-1。保证了 X≤A[j…high]
④ 继续执行②、③,直到 i=j。这时,X 恰好在 A[i]位置上。
⑤ 对序列 A[low…i-1]及 A[i+1…high]按照上述规律继续划分,直到序列为空。
仔细分析算法,我们发现,在排序中,我们总是用基准 X 与另一数据交换,因此,一趟排序结束后,X 就能确切定位其最终位置。
3.排序过程示例
待排序数据: | 67 | 67 | 14 | 52 | 29 | 9 | 90 | 54 | 87 | 71 |
---|---|---|---|---|---|---|---|---|---|---|
X=67 | i | j | ||||||||
扫描 j | i | j | ||||||||
交换 | 54 | 67 | 14 | 52 | 29 | 9 | 90 | 67 | 87 | 71 |
扫描 i | i | j | ||||||||
交换 | 54 | 67 | 14 | 52 | 29 | 9 | 67 | 90 | 87 | 71 |
j=i,结束 | i j | |||||||||
第一趟排序后: | 54 | 67 | 14 | 52 | 29 | 9 | [67] | 90 | 87 | 71 |
X=54, 90 扫描j | i | j | i | j | ||||||
9 | 67 | 14 | 52 | 29 | 54 | [67] | 71 | 87 | 90 | |
扫描i | i | j | i j | |||||||
9 | 54 | 14 | 52 | 29 | 67 | [67] | 71 | 87 | 90 | |
扫描j | i | j | i j | |||||||
9 | 29 | 14 | 52 | 54 | 67 | [67] | 71 | 87 | 90 | |
扫描i | i j | i j | ||||||||
第二趟排序后: | 9 | 29 | 14 | 52 | [54] | 67 | [67] | 71 | 87 | [90] |
X=9,67,71 扫描j | i j | i j | i j | |||||||
第三趟排序后: | [9] | 29 | 14 | 52 | [54 | 67 | 67 | 71] | 87 | [90] |
X=29 ,87 扫描j | i | j | i j | |||||||
[9] | 14 | 29 | 52 | [54 | 67 | 67 | 71] | 87 | [90] | |
扫描i | i j | i j | ||||||||
第四趟排序后 | [9] | 14 | [29] | 52 | [54 | 67 | 67 | 71 | 87 | 90] |
第五趟排序后 | [9] | 14 | 29 | 52 | 54 | 67 | 67 | 71 | 87 | 90] |
4.程序代码
public class QuickSort {
public static void main(String[] args) {
int[] data = {67,67,14,52,29,9,90,54,87,71};
QuickSort qs = new QuickSort();
qs.data = data;
qs.print(data);
System.out.println();
qs.sort(0, data.length-1);
qs.print(data);
}
public int[] data;
/**
* 每一轮比较的逻辑
* 确定一个范围后
* 先从后往前扫描 找小的数交换
* 再从前往后扫描 找大的数交换
* 一轮结束
*/
public int quickSort(int[] data, int low, int high){
// 选取第一个数作为key
int key = data[low];
while (low < high){
// 从high位置向前扫描,直到data[high]小于key值
while (low < high && data[high] >= key){
high--;
}
// 找到第一位比key小的值 把小的值交换给key的位置
data[low] = data[high];
// 从low位置向后扫描, 直到有数比key大
while (low < high && data[low] <= key){
low++;
}
// 交换这个大的值给key新的位置
data[high] = data[low];
}
// 第一轮完成后 key放在刚刚交换的low下标处
data[low] = key;
// 返回这个low下标
return low;
}
/**
* 递归调用实现多轮排序 直到low==high 递归结束 排序完成
* @param low
* @param high
* @return
*/
public int[] sort(int low, int high){
if (low < high){
int result = quickSort(data, low, high);
sort(low, result-1);
sort(result+1, high);
}
return data;
}
public void print(int[] data){
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
}
}
67 67 14 52 29 9 90 54 87 71
9 14 29 52 54 67 67 71 87 90
5.算法分析
(1)稳定性:不稳定
(2)时间复杂度:
每趟排序所需的比较次数为待排序区间的长度-1,排序趟数越多,占用时间越多。
①最坏情况:
每次划分选取的基准恰好都是当前序列中的最小(或最大)值,划分的结果 A[low…i-1]为空区间或 A[i+1…high]是空区间,且非空区间长度达到最大值。这种情况下,必须进行 n-1 趟快速排序,第 i 次趟去见长度为 n-i+1,总的比较次数达到最大值:n(n-1)/2=O(n²)
②最好情况:
每次划分所取的基准都是当前序列中的“中值”,划分后的两个新区间长度大致相等。共需 lgn 趟快速排序,总的关键字比较次数:O(nlgn)
③基准的选择决定了算法性能。经常采用选取 low 和 high 之间一个随机位置作为基准的方式改善性能。
(3)空间复杂度:快速排序在系统内部需要一个栈来实现递归,最坏情况下为 O(n),最佳情况下为 O(lgn)。