快速排序是我们经常接触的一种排序, 属于交换排序, 是对冒泡排序的一种改进。
快速排序的核心思想是: 先设定数组的第1个元素为基准, 用此基准将数组分成二部分, 使左边的小于基准, 右边的大于基准,然后在左边再选出一个新基准, 递归分成二部分的过程,右边同样递归过程, 基准位于最终排序的位置i上。
一 简介
在快速排序中,记录关键字的比较和记录的交换是从两端向中间进行的, 待排序关键字较大的记录一次就能够交换到后面单元中,而关键字较小的记录一次就能够交换到前面单元中, 记录每次移动的距离较远, 因此总的比较和移动次数较小,速度较快, 故称为 “快速排序” 。
正因如此,
因为存在着不相邻记录之间的交换, 此排序是不稳定的。
这句话必须牢牢记住!!! 这是回答某种排序不稳定的关键原因。
二 具体操作
一趟快速排序的具体操作是: 设两个指针 i 和 j, 它们的初值分别为 low 和 high,基准记录 x = R[i], 也就是说将第1个值设为基准, 首先从 j 所指位置起向前搜索找到第一个关键字小于基准 x.key 的记录存入当前 i 所指向的位置上,i自增1, 然后再从 i 所位置起向后搜索, 找到第一个关键字大于 x.key的记录存入当前 j所指向的位置上, j自减1; 重复这两步,直至 i 等于 j为止。
快速排序是一个递归的算法。
以java为例, 看一个demo。
public class Main {
static ArrayList<Integer> list_ = new ArrayList<Integer>(Arrays.asList(30,45,20,30,10));
public static void main(String[] args) {
quickSort(list_,0, 4);
}
static void quickSort(ArrayList<Integer> list, int low, int high){
int p;
if(low < high){ //长度大于1
System.out.println(list);
p = partition(list,low,high); //做一次划分排序
quickSort(list, low,p-1); //对左区间递归排序
quickSort(list, p+1, high); //对右区间递归排序
}
}
/**划分算法, 即一趟排序的算法
* 对list 从 i 到 j 区间内的值进行一次划分排序
* ***/
static int partition(ArrayList<Integer> list, int i, int j){
int x = list.get(i); //用区间的第一个记录为基准
while(i<j){
while(i<j && list.get(j)>= x){
j--; //从j所指位置起向前(左)搜索
}
if(i<j){
list.set(i, list.get(j));
i++;
}
while(i<j && list.get(i)<x){
i++; //从i所指位置起向后(右)搜索
}
if(i<j){
list.set(j, list.get(i));
j--;
}
}
list.set(i, x); //基准记录 x 位于最终排序的位置 i 上
// System.out.println(list);
return i;
}
}
动画如下所示
快速排序
排序结果: [10, 20, 30, 30, 45]
三. 时间复杂度与空间复杂度
快速排序有非常好的时间复杂度, 优于其他各种排序算法。对n个记录进行快速排序的平均时间复杂度为 .
但是当待排序文件的记录已按关键字有序或基本有序时, 复杂度反而增大了, 原因晨第一趟快速排序中经过 n-1 次比较后,第一个记录仍定位在它原来的位置上, 并得到一个包含 个记录的子文件, 第二次递归调用, 经过
次比较,第二个记录仍定位在它原来的位置上, 从而得到一个包括
个记录的子文件, 依次类推,最后得到排序的总比较次数为
这使得快速排序变成了冒泡排序,其时间复杂度为 .
从空间上来看, 由于快速排序是递归的,因此需要一个栈空间来实现递归,栈的大小取决于递归调用的深度。 若每一趟排序都能使待排序文件比较均匀地分割成两个子区间, 则栈的最大深度为
, 即使在最坏的情况下, 栈的最大深度也不会超过 n. 因此快速排序需要附加空间为
四. 我的总结
在排序算法中, 我举例,数值一般是5个,并且有相同的数值,利于体现是否稳定 ,不会弄很多数值, 否则只是搞复杂,
我一直以来的看法是: 关键是把原理搞懂, 吃透, 我绝不会弄超过5个的数值,让读者看着费劲。