1、什么是快速排序?
快速排序是通过比较和交换位置来排序的排序算法,是对冒泡算法的一种优化。
2、什么时候使用快速排序?
当数据量比较大的时候,推荐使用快速排序。
3、快速排序的主要思想是什么?
快速排序的思想是:通过一次排序将无序的数据分为两个独立部分,其中一部分数据的值均比另一部分要小,再对这两部分分别进行排序,使得整个序列有序。简单来说,就是从所有数据中取一个基准值,不大于该基准值的为一部分,大于基准值的为一部分,再在这两部分中分别取基准值再继续分别分为两部分,以此类推,直到剩下的部分只有一个元素,则结束。
4、快速排序的步骤是什么?
快速排序的两种方法:填坑法和前后指针法
填坑法步骤:
- 取序列A中第一个元素为基准值,并且取两个索引i=0,j=n-1,准备临时变量temp = A[i];
- 从右开始寻找小于基准值temp的数(j-- && j > i),得到值A[j],填坑:A[i]=A[j]
- 从左开始寻找不小于基准值temp的数(i++ && j>i),得到值A[i],填坑:A[j]=A[i]
- 重复操作2和操作3,直到i==j,则A[i]=temp
- 将序列分为0 ~ i-1 和i+1 ~ n-1两部分,继续重复步骤1、2、3、4,直到最后的序列只剩下一个元素。
填坑法的重点:不动的指针所指的位置是坑位
如图:
前后指针法的步骤:
- 取序列A中第一个元素为基准值,并且取两个索引i=0,j=n-1,准备临时变量begin= i;
- 从右开始寻找不大于基准值temp的数(j-- && j > i),得到值A[j]
- 从左开始寻找大于基准值temp的数(i++ && j>i),得到值A[i],交换值A[i]和值A[j]
- 重复操作2和操作3,直到i==j,交换A[i]和A[begin]的值
- 将序列分为0 ~ i-1 和i+1 ~ n-1两部分,继续重复步骤1、2、3、4,直到最后的序列只剩下一个元素。
如图:
5、快速排序的java代码
填坑法Java代码:
public class quickSort1 {
public static void main(String[] args) {
int[] arr = {0, 3, 7, 5, 1, 2, 8, 3, 2, 1, 5, 6, 2,
3, 4, 9, 1, 3, 4, 2, 3, 4, 4, 5, 3, 1, 8};
int begin = 0;
int end = arr.length - 1;
quickSort(arr, begin, end);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
/**
* 填坑法
*
* @param arr 序列
* @param begin 开始值
* @param end 结束值
*/
private static void quickSort(int[] arr, int begin, int end) {
if (begin >= end) {
return;
}
// 用于标记划分区域的大小
int m = begin;
int n = end;
// 获得基准值
int temp = arr[m];
while (m != n) {
// 注意序列中和基准值相同的元素,不然会死循环
while (m < n && arr[n] >= temp) {
n--;
}
arr[m] = arr[n];
while (m < n && arr[m] < temp) {
m++;
}
arr[n] = arr[m];
if (n == m) {
arr[m] = temp;
}
}
// 迭代
quickSort(arr, begin, m - 1);
quickSort(arr, m + 1, end);
}
}
前后指针交换法:
public class quickSort1 {
public static void main(String[] args) {
int[] arr = {0, 3, 7, 5, 1, 2, 8, 3, 2, 1, 5, 6, 2,
3, 4, 9, 1, 3, 4, 2, 3, 4, 4, 5, 3, 1, 8, 10};
int begin = 0;
int end = arr.length - 1;
quickSort(arr, begin, end);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
/**
* 前后指针法
*
* @param arr 数组
* @param begin 开始
* @param end 结束
*/
private static void quickSort(int[] arr, int begin, int end) {
if (begin >= end) {
return;
}
int temp = arr[begin];
int i = begin;
int j = end;
while (i != j) {
// 必须先从右边来其获取的是arr[j] <= temp
while (i < j && arr[j] > temp) {
j--;
}
while (i < j && arr[i] <= temp) {
i++;
}
int tb = arr[j];
arr[j] = arr[i];
arr[i] = tb;
}
// 交换初始值和i==j的值
arr[begin] = arr[i];
arr[i] = temp;
// 迭代
quickSort(arr, begin, i - 1);
quickSort(arr, i + 1, end);
}
}
6、快速排序代码中的疑问解答
1、为什么比较的时候必须有“>=”,不能都使用“>”吗?
不能,因为序列中可能存在和基准值一样大小的值,如果没有“=”,可能会出现死循环。
2、为什么从左边开始选基准值,要先从右边开始找不大于基准值的值?
主要是如果从右边开始找最差的情况是找到的值和基准值相等,这样的值还是可以放到基准值的左边的,如果从左边开始寻找,因为其找的是不小于基准值的值,这样的值最后放到了基准值的左边,但是左边的元素是不大于基准值,所以报错,例如序列{5,6,4,5,7},假如基准值为4,如果从左边开始找到的值找到为5,放到左侧报错,从右侧找,找到了值为4,不会出现问题。
7、快速排序和冒泡算法比较
快速排序其根据选取的基准值可以使序列中的数据远距离交换,但是冒泡排序只能是相邻元素之间的交换,快速排序的比较和交换次数相对而言比较少,所以快速排序的效率比较高。
时间复杂度 | 空间复杂度 | 稳定性 | |
快速排序 | nlogn | logn | 不稳定 |
冒泡排序 | n² | 1 | 稳定 |
快速排序时间复杂度最差的情况是:n²,这种情况下序列本来就是有序,基准值每次都选的是最大或者最小值。而快速排序的稳定性,则是快排会导致相同元素的相对位置发生改变,如:{3,4,2,6,2,6},这会导致2和4交换,会导致2的相对位置发生改变,所以是不稳定的
8、快速排序的总结
快速排序是一种分而治之的思想,通过基准值将序列划分为两段小序列,然后继续划分,直到划分到只有一个元素或者为空的序列,结束继续划分。其最坏时间复杂度是O(n²),平均的时间复杂度为O(nlogn),空间复杂度为O(logn),是一种不稳定算法。
9、快速排序类比
快速排序的类比:快速排序可以类比为建立一棵二叉树,其选取的基准值可以理解为父节点,其分的两部分可以理解为左子树集和右子树集。