💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
对于快排有三种版本。 # 1. 版本1 在前面的简单排序算法中所实现的排序算法为第一种,即在数组的左边或者右边选定端点值作为轴值,规定左边下标`i`及其之前的元素为小于等于轴值的部分,而下标`j`及其之后的元素为大于轴至的元素。然后开始在左右两端进行扫描,其过程可以描述为下面的图示: <img src="http://weizu_cool.gitee.io/gif-source/images/屏幕截图 2022-01-05 103301.png"/> 对应代码: ~~~ /** * 快排版本1,即选择左边或者右边的端点值来作为轴值,然后进行改数的最终位置的放置,并将数据划分为两个部分 * @param arr 待排序数组 */ public void quickSortVersion_1(int[] arr){ if(arr == null || arr.length == 0) return; quickSortV_1(arr, 0, arr.length - 1); } private void quickSortV_1(int[] arr, int left, int right) { if(left < right){ int mid = partitionV_1(arr, left, right); quickSortV_1(arr, left, mid - 1); quickSortV_1(arr, mid + 1, right); } } private int partitionV_1(int[] arr, int left, int right) { int value = arr[left]; while(left < right){ while(left < right && arr[right] > value) right--; swap(arr, left, right); while(left < right && arr[left] <= value) left++; swap(arr, left, right); } return left; } ~~~ # 2. 版本2 注意到一点,就是如果数组中的当前轴值在数组中含有多个,比如上面的案例中第一次划分使用`arr[0]=4`,不妨打印一下每趟排序的结果,如下图所示: <img src="http://weizu_cool.gitee.io/gif-source/images/屏幕截图 2022-01-05 104908.png"/> 注意到一点就是在第一趟排序的结果中,倒数第二个数,也就是第一个`4`放置在了最终位置,但是对于数组中其余的两个重复的`4`,却还需要再次递归的进行快排。这个过程无疑是重复了。 所以在版本`2`中,考虑使用前面的[快排的一次数据划分](https://www.kancloud.cn/the_road_of_android/algroithm/2591416)中的三类数据划分算法,也就是: ~~~ /** * 快排第二个版本,改动部分为partition,也就是将数据划分为了三个部分,小于轴值、等于轴值和大于轴值 * @param arr 待排序数组 */ public void quickSortVersion_2(int[] arr){ if(arr == null || arr.length == 0) return; quickSortV_2(arr, 0, arr.length - 1); } private void quickSortV_2(int[] arr, int left, int right) { if(left < right){ int[] partition = partitionV_2(arr, left, right); for (int i : arr) { System.out.print(i+"\t"); } System.out.println(); quickSortV_2(arr, left, partition[0] - 1); quickSortV_2(arr, partition[1] + 1, right); } } private int[] partitionV_2(int[] arr, int left, int right) { int i = left - 1, j = right, k = left, value = arr[left]; // 假定0-i为小于value,j及其之后为大于value,均为其边界 while(k <= j){ if(arr[k] > value){ // 大于轴值,就放置在右边,并且右边界左移一位 swap(arr, k, j); j--; } else if(arr[k] < value) { // 小于轴值,放置在左边,左边界右移一位 i++; swap(arr, k, i); } else{ // 等于轴值,不移动,即放置在中间,直接后移即可 k++; } } return new int[]{i+1, j}; // 返回数组中等于轴值的这些数排序后的端点下标 } ~~~ 对于数组`arr=[4, 4, 1, 0, 4, 6, 3, 4, 7, 4, 8, 0, 1, 4]`,在这个版本的排序中,每趟排序的结果为: <img src="http://weizu_cool.gitee.io/gif-source/images/屏幕截图 2022-01-05 112531.png"/> # 3. 版本3 对于版本`2`中解决了数组中重复元素选为轴值的问题,但是值得注意到一点:比如当选定的左边的轴值的右边的所有元素都比其大,那么问题就会退化,而不是折半递归了。所以对于快排划分中轴值的划分可以采用随机值的方式。 ~~~ /** * 快排第三个版本,改动部分为轴值的选择为随机选择 * * @param arr 待排序数组 */ public void quickSortVersion_3(int[] arr) { if (arr == null || arr.length == 0) return; quickSortV_3(arr, 0, arr.length - 1); } private void quickSortV_3(int[] arr, int left, int right) { if (left < right) { int[] partition = partitionV_3(arr, left, right); quickSortV_2(arr, left, partition[0] - 1); quickSortV_2(arr, partition[1] + 1, right); } } private int[] partitionV_3(int[] arr, int left, int right) { int i = left - 1, j = right, k = left; // 随机选择一个值作为轴值 int index = left + (int) (Math.random() * (right - left)); int value = arr[index]; // 假定0-i为小于value,j及其之后为大于value,均为其边界 while (k <= j && i < j) { if (arr[k] > value) { // 大于轴值,就放置在右边,并且右边界左移一位 swap(arr, k, j); j--; } else if (arr[k] < value) { // 小于轴值,放置在左边,左边界右移一位 i++; swap(arr, k, i); } else { // 等于轴值,不移动,即放置在中间,直接后移即可 k++; } } return new int[]{i + 1, j}; // 返回数组中等于轴值的这些数排序后的端点下标 } ~~~