快速排序
概要
分治法的典型应用。 选择一个枢轴值将数组分为两部分,递归地对这两部分继续进行快速排序,以达到整个序列有序。 平均时间复杂度为 O(n log n),但最坏情况下为 O(n^2)。
什么是分治法呢?
分治法(Divide and Conquer)是一种在计算机科学和数学中广泛使用的算法设计策略,其核心思想是将一个大问题分割成若干个小的、易于解决的子问题,逐个解决这些子问题,然后将子问题的解组合起来形成大问题的解。这种方法通常通过递归的方式实现,使得问题解决过程结构化且易于理解。
快速排序思想分析
根据下方代码的思路分析
①
首先,快速排序采用了分治化加递归的思想,我们拿到数组【2,7,8,9,1,5】,拿到右指针的索引和左指针的索引(分别是右指针5和左指针1)
②
(1)随后我们定义一个方法来负责将数组划分为两个部分,并返回分界线(左区域和右边的区域)的索引,并且我们定义这个分界线默认是右指针就是high=arr【5】,并且定义一个左索引-1的属性i,这个i是为了当我们判断时可以将数组的第一个元素进行交换(也是后面我们进行交换的重要条件)。
(2)从左指针开始遍历数组,到右指针也就是分界线时停止。
(3)如果当前拿到的元素,小于分界线的话将i值前移一位,并将该数和遍历到比分界线小的数据进行交换。
(4)当循环完成后将该分界线和i索引对应的数据进行交换。
(5)交换完成后的值的索引下一位是新的分界线,也就是我们下一步递归的重要条件。
③
上一步我们可以得到,一个分界线。这个分界线的左边是小于该分界线的元素,该分界线的右边就是比该分界线大的元素。
!!!!注意了,下面是最重要的分治思想
我们可以把这个分界线小的数据作为一个新的数组,比分界线大的部分作为另一个新的数组。右边的数组的左指针就是初始的左指针,右指针则是我们上一步最后返回的分界线的上一位数。那另一个大的数组呢????
自己动动脑子就知道啦!!!
随后我们创建一个排序方法,实现递归。
递归的终止条件是什么呢???那就是当左指针小于右指针的时,这个可以记住,左指针肯定是要比右指针要小的,至于为什么,可以自己思考一下喔,有知道的家人们可以在评论区中发出来,答对有奖喔!!
随后要递归的方法就是分区方法和分区后的小数组和大数组进行递归,就可以得到有序数组啦
快速排序算法之代码演示
low和high其实就是左右指针,可以理解为一个数组中第一个元素的索引就是左指针,最后一个元素的索引是右指针。
package com.status.demo.sotr;
public class QuckSort {
public static void quickSort(int[] arr, int low, int high) {
//判断递归的终止条件是右指针大于左指针的情况
if (low < high) {
//返回新的中心点
int pi = partition(arr, low, high);
//基准点-1是分区之后左边区域中的右边界
quickSort(arr, low, pi - 1); // 递归排序左子数组
//基准点-1是分区之后右边区域中的左边界
quickSort(arr, pi + 1, high); // 递归排序右子数组
}
}
//【2,7,8,9,1,5】
// 该函数负责将数组划分为两个部分,并返回中心点的索引
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为枢轴
int i = (low - 1); // 小于枢轴的元素的索引
for (int j = low; j < high; j++) {
// 如果当前元素小于或等于枢轴
if (arr[j] <= pivot) {
i++;
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 交换 arr[i+1] 和 arr[high] (或枢轴)
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
// 测试快速排序的主方法
public static void main(String[] args) {
int[] arr = {2, 7, 8, 9, 1, 5};
int n = arr.length;
//low是左指针,high是右指针
quickSort(arr, 0, n - 1);
System.out.println("Sorted array: ");
for (int i : arr) {
System.out.print(i + " ");
}
}
}
结果分析
小结
分治法的应用示例:
归并排序:
分解:将数组分成两半,然后再递归地对这两半进行归并排序。
解决:当分解到只有一个元素时,这一部分数组自然是排序好的。
合并:将两个排序好的半部分合并成一个完整的排序数组。
快速排序:
分解:选择一个枢轴元素,重新排列数组,所有比枢轴小的元素都移到枢轴的左边,所有比枢轴大的元素都移到右边。
解决:递归地在枢轴的左右两边执行快速排序。
合并:由于快速排序的处理方式,这一步实际上不需要执行任何操作,因为数组已经被原地重新排列了。
二分搜索:
分解:在有序数组中,选择中间的元素进行比较,以缩小搜索范围。
解决:确定继续在左半部分还是右半部分查找。
合并:一旦找到目标元素或确定元素不存在,则过程结束。
整数乘法(如Karatsuba算法):
分解:将大整数分解为较小的数字部分。
解决:递归地对这些小数字进行乘法运算。
合并:合并这些小的乘法结果以形成最终结果。
优势
分治法的优势在于它可以通过分解复杂问题为可管理的部分来简化问题的复杂度,特别是在问题可以自然分解为相似子问题的情况下。然而,这种方法可能会导致更高的空间复杂度,因为每一层递归通常需要其自己的调用栈和存储空间。此外,适当的基准情况和有效的合并步骤对于实现高效的分治算法至关重要。