Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
🌱🌱个人主页:奋斗的明志
🌱🌱所属专栏:数据结构、LeetCode专栏
📚本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
快速排序
前言
一、快速排序
1.概念
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2.快排递归实现的主框架
java
// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int[] array, int left, int right) {
if (right - left <= 1) return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分
int div = partion(array, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right) // 递归排[left, div)
QuickSort(array, left, div);
// 递归排[div+1, right)
QuickSort(array, div + 1, right);
}
与二叉树前序遍历规则非常像,在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
3.Hoare法(方法一)
【图解】
【主要代码展示】
java
/**
* Hoare法
* @param array
* @param left
* @param right
* @return
*/
private static int partitionHoare(int[] array, int left, int right) {
//这里以最左边的元素为基准元素
int tmp = array[left];
//要先记录下来 left 的值,因为在下面操作过程中,left 的值会变
int i = left;
while (left < right){
while (left < right && array[right] >= tmp){
right--;
}
while (left < right && array[left] <= tmp){
left++;
}
//进行交换
swap(array,left,right);
}
//将基准元素交换到中间
swap(array,i,left);
//返回此时基准元素的下标
return left;
}
【测试这个方法】
java
public static void testQuickArray(int[] array){
int[] tmpArray = Arrays.copyOf(array,2 * array.length);
long startTime = System.currentTimeMillis();
Sort.quickSort(tmpArray);
long endTime = System.currentTimeMillis();
System.out.println("快速排序所用时间:" + (endTime - startTime));
}
java
public static void main(String[] args) {
System.out.println("给100000个数据");
int[] array = new int[10_0000];
inorderArray(array);
// notInorderArray(array);
// initArray(array);
testInsertArray(array);
testShellArray(array);
testSelectArray(array);
testQuickArray(array);
}
栈会溢出,可以修改idea里面的配置
因为这个方法时间复杂度:* 最好情况下:O(N*logN)
* 最坏情况下:O(N^2) 有序 / 逆序
* 空间复杂度:
* 最好情况下:O(logN)
* 最坏情况下:O(N)
* 稳定性:
* 不稳定的排序
4.挖坑法(方法二)
【图解】
【主要代码展示】
java
/**
* 挖坑法
* @param array
* @param left
* @param right
* @return
*/
public static int partition(int[] array,int left,int right){
int tmp = array[left];
while (left < right){
while (left < right && array[right] >= tmp){
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp){
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
【测试这个方法】
java
public static void main(String[] args) {
int[] array = {3,5,2,10,9,8,17};
Sort.quickSort(array);
System.out.println(Arrays.toString(array));
}
5.前后指针法(方法三)
【写法一】
java
/**
* 前后指针法
*/
private static int partition2(int[] array, int left, int right) {
int prev = left;
int cur = left + 1;
while (cur <= right) {
if (array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array, cur, prev);
}
cur++;
}
swap(array, prev, left);
return prev;
}
【写法二】
java
private static int partition3(int[] array, int left, int right) {
int d = left + 1;
int pivot = array[left];
for (int i = left + 1; i <= right; i++) {
if (array[i] < pivot) {
swap(array, i, d);
d++;
}
}
swap(array, d - 1, left);
return d - 1;
}
二、快速排序优化
1.三数取中法选key
java
private static int middleNum(int[] array, int left,int right){
//中间位置的下标
int mid = left + ((right - left) >> 1);
//怎么拿到这个下标对应的中间数字呢
if (array[left] < array[right]){
if (array[mid] < array[left]){
return left;
}else if (array[right] < array[mid]){
return right;
}else {
return mid;
}
}else {
if (array[right] > array[mid]){
return right;
}else if (array[mid] > array[left]){
return left;
}else {
return mid;
}
}
}
2.递归到小的子区间时,可以考虑使用插入排序
java
public static void insertSort2(int[] array,int start,int end) {
//要求数组不为空
for (int i = start + 1; i <= end; i++) {
//将 i 下标的值放入 tmp 中
int tmp = array[i];
int j = i - 1;
for (; j >= start; j--) {
//j下标的值和i下标的值进行比较
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
// array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
3.快速排序非递归
java
/**
* 快排非递归
*/
public static void quickSortNor(int[] array){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length - 1;
int pivot = partition(array,left,right);
if (pivot - 1 > left){
stack.push(left);
stack.push(pivot - 1);
}
if (pivot + 1 < right){
stack.push(right);
stack.push(pivot + 1);
}
while (!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
pivot = partition(array,left,right);
if (pivot - 1 > left){
stack.push(left);
stack.push(pivot - 1);
}
if (pivot + 1 < right){
stack.push(right);
stack.push(pivot + 1);
}
}
}
三、完整代码
java
public static void quickSort(int[] array) {
//首先要对待排序的元素进行划分
//找到一个点,使左边比这个点小,右边比这个点大
//可以以最左边或者最右边的元素作为基准
//调用下面的方法进行划分
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
if ((end - start + 1) <= 19) {
insertSort2(array, start, end);
}
//找基准之前,先对三数取中
int index = middleNum(array, start, end);
//找到中间元素下标之后
//进行交换
//把中间大的下标和第一个数字交换
swap(array, index, start);
//找到划分之后 基准元素的下标
int pivot = partition(array, start, end);
//以基准元素为点,又分左右两边
//类似与二叉树的前序遍历
quick(array, start, pivot - 1);//左边
quick(array, pivot + 1, end);//右边
}
public static void insertSort2(int[] array, int start, int end) {
//要求数组不为空
for (int i = start + 1; i <= end; i++) {
//将 i 下标的值放入 tmp 中
int tmp = array[i];
int j = i - 1;
for (; j >= start; j--) {
//j下标的值和i下标的值进行比较
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
// array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
private static int middleNum(int[] array, int left, int right) {
//中间位置的下标
int mid = left + ((right - left) >> 1);
//怎么拿到这个下标对应的中间数字呢
if (array[left] < array[right]) {
if (array[mid] < array[left]) {
return left;
} else if (array[right] < array[mid]) {
return right;
} else {
return mid;
}
} else {
if (array[right] > array[mid]) {
return right;
} else if (array[mid] > array[left]) {
return left;
} else {
return mid;
}
}
}
/**
* Hoare法
*
* @param array
* @param left
* @param right
* @return
*/
private static int partitionHoare(int[] array, int left, int right) {
//这里以最左边的元素为基准元素
int tmp = array[left];
//要先记录下来 left 的值,因为在下面操作过程中,left 的值会变
int i = left;
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
while (left < right && array[left] <= tmp) {
left++;
}
//进行交换
swap(array, left, right);
}
//将基准元素交换到中间
swap(array, i, left);
//返回此时基准元素的下标
return left;
}
/**
* 挖坑法
*
* @param array
* @param left
* @param right
* @return
*/
public static int partition(int[] array, int left, int right) {
int tmp = array[left];
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
if (left >= right) {
break;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
if (left >= right) {
break;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
/**
* 前后指针法
*/
private static int partition2(int[] array, int left, int right) {
int prev = left;
int cur = left + 1;
while (cur <= right) {
if (array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array, cur, prev);
}
cur++;
}
swap(array, prev, left);
return prev;
}
private static int partition3(int[] array, int left, int right) {
int d = left + 1;
int pivot = array[left];
for (int i = left + 1; i <= right; i++) {
if (array[i] < pivot) {
swap(array, i, d);
d++;
}
}
swap(array, d - 1, left);
return d - 1;
}
总结
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度: O(N*logN)
- 空间复杂度: O(logN)
- 稳定性:不稳定