QuickSort(递归)

本文介绍了一种快速排序算法的递归实现方式,并通过Java语言实现了该算法。具体包括输入一组待排序数字,通过递归调用快速排序方法对数组进行排序,并输出排序后的结果。

快速排序的递归实现算法。

输入:先输入进行排序元素的个数,然后依次随机输入(或随机生成)每个数字

输出:元素排序后的结果。

示例:输入:8  9 1  2  4 8  6  15  8,输出:1  2 4  6  8 8  9  15


package FinalTest_2;

import java.util.Scanner;

public class QuickSort {

	static void quicksort(int arr[], int left, int right) {
		if (left < right) {	  //序列有效,至少两个元素(相等也用不着排序、)
			int i = left;
			int j = right;
			int vot = arr[i]; //设第一个元素为基准值

			while (i != j) {	//相等元素不交换顺序
				while(i < j && vot <= arr[j]) j--;		//从右向左找第一个小于vot的数,未找到不移动基准值
				if(i<j)	arr[i++]=arr[j];   	//找到子序列后面小的往前移动
				
				while(i < j && vot >= arr[i]) i++;			//从左往右找比vot大的数,未找到的情况循环右移
				if(i<j) arr[j--]=arr[i];	//找到的情况子序列前面大的往后移(别忘了两端向中间渐近)
			}
			
			arr[i]=vot;		// 基准值达到最终位置i==j,将基准数填入arr[i]中		
			quicksort(arr,left,i-1);  //前端子序列递归调用
			quicksort(arr,j+1,right);
		}
	}

	public static void main(String[] args) {
		Scanner s = new Scanner(System.in);
		int n = s.nextInt();
		int arr[]=new int[n];
		for (int i = 0; i < arr.length; i++) {
			arr[i]=s.nextInt();
		}
		quicksort(arr,0,arr.length-1);
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i]+" ");
		}

	}
}


<think> 我们之前分析的函数 `int a(int *arr, int left, int right)` 实际上是快速排序中的分区(Partition)函数。结合用户提供的引用(尤其是引用[1]和引用[4]),我们可以明确该函数的作用是在快速排序中选取基准值(pivot),并将数组分为两部分:小于等于基准值的部分和大于基准值的部分,同时返回基准值的最终位置。 ### 1. 代码功能分析 原函数 `a` 实现的是快速排序的分区操作(Partition),具体步骤如下: 1. **选择基准值**:以最左侧元素 `arr[left]` 为基准值 `p`。 2. **双指针扫描**: - 指针 `i` 从左向右扫描,直到遇到大于 `p` 的元素。 - 指针 `j` 从右向左扫描,直到遇到小于等于 `p` 的元素。 - 交换 `arr[i]` 和 `arr[j]`,使小于等于 `p` 的元素移到左侧,大于 `p` 的元素移到右侧。 3. **放置基准值**:将基准值 `p` 交换到正确位置(索引 `i` 处),此时左侧元素均 `<=p`,右侧元素均 `>p`。 4. **返回基准值位置**:返回 `i` 作为下一次递归的分界点。 ### 2. 与快速排序的关联 - 在快速排序的主函数中(如引用[2]所示),会递归调用分区函数: ```c void QuickSort(int* arr, int begin, int end) { if (begin >= end) return; int key = PartSort(arr, begin, end); // 调用分区函数(即函数a) QuickSort(arr, begin, key-1); // 排序左子数组 QuickSort(arr, key+1, end); // 排序右子数组 } ``` - 其中 `PartSort` 就是函数 `a` 的功能。 ### 3. 原代码问题分析 原函数 `a` 存在以下问题: 1. **逻辑错误**: - 内层循环中,`arr[right]` 应为 `arr[j]`,`arr[left]` 应为 `arr[i]`(见错误行): ```c while (i < j && arr[right] <= p) j--; // 错误:应使用 arr[j] while (i < j && arr[left] >= p) i++; // 错误:应使用 arr[i] ``` 正确写法: ```c while (i < j && arr[j] > p) j--; // 从右找<=p的元素 while (i < j && arr[i] <= p) i++; // 从左找>p的元素 ``` > 注:原代码中的条件 `arr[right]<=p` 和 `arr[left]>=p` 不仅索引错误,且条件方向反了(应严格区分左右扫描条件)。 2. **基准值处理缺陷**: - 当数组已有序时,选择最左端元素作为基准值会导致最坏时间复杂度 $O(n^2)$。 3. **边界条件**: - 未处理元素全相等的情况(可能退化为 $O(n^2)$)。 ### 4. 优化建议 #### (1) 修复分区函数逻辑 修正扫描条件并修复指针移动逻辑: ```c int Partition(int *arr, int left, int right) { assert(arr != nullptr && left <= right); int p = arr[left]; // 基准值 int i = left; // 左指针 int j = right; // 右指针 while (i < j) { // 从右向左找第一个<=p的元素 while (i < j && arr[j] > p) j--; // 从左向右找第一个>p的元素 while (i < j && arr[i] <= p) i++; if (i < j) { swap(arr[i], arr[j]); // 交换 } } // 将基准值放到正确位置 swap(arr[left], arr[i]); return i; } ``` #### (2) 优化基准值选择 避免最坏情况,采用以下方法之一: - **三数取中法**:选择 `left`、`mid`、`right` 三个位置的中位数作为基准值。 ```c int mid = left + (right - left) / 2; if (arr[mid] > arr[right]) swap(arr[mid], arr[right]); if (arr[left] > arr[right]) swap(arr[left], arr[right]); if (arr[mid] > arr[left]) swap(arr[mid], arr[left]); // 此时 arr[left] 为左、中、右的中位数 ``` #### (3) 处理重复元素 - 使用三路快速排序(Dutch National Flag Algorithm)将数组分为三部分:`<p`、`=p`、`>p`,减少重复元素的递归调用。 #### (4) 递归优化 - 尾递归优化:先处理较短子数组,减少递归深度(见引用[2]非递归实现思路)。 ```c void QuickSort(int *arr, int left, int right) { while (left < right) { int p = Partition(arr, left, right); // 先处理较短部分 if (p - left < right - p) { QuickSort(arr, left, p - 1); left = p + 1; } else { QuickSort(arr, p + 1, right); right = p - 1; } } } ``` ### 总结 函数 `a` 是快速排序的核心分区操作,修复逻辑错误后可用于实现高效的快速排序。优化方向包括: 1. 修正分区逻辑(修复扫描条件)。 2. 优化基准值选择(三数取中)。 3. 处理重复元素(三路分区)。 4. 减少递归开销(尾递归或非递归实现)。 优化后的快速排序平均时间复杂度为 $O(n \log n)$,最坏情况下通过基准值优化可避免 $O(n^2)$。 ### 快速排序完整示例(递归+三数取中) ```c // 三数取中 int MedianOfThree(int *arr, int left, int right) { int mid = left + (right - left) / 2; if (arr[left] > arr[mid]) swap(arr[left], arr[mid]); if (arr[left] > arr[right]) swap(arr[left], arr[right]); if (arr[mid] > arr[right]) swap(arr[mid], arr[right]); swap(arr[left], arr[mid]); // 将中位数放到left位置 return arr[left]; } // 分区函数(修复版) int Partition(int *arr, int left, int right) { MedianOfThree(arr, left, right); // 基准值优化 int p = arr[left]; int i = left, j = right; while (i < j) { while (i < j && arr[j] > p) j--; while (i < j && arr[i] <= p) i++; if (i < j) swap(arr[i], arr[j]); } swap(arr[left], arr[i]); return i; } // 快速排序主函数 void QuickSort(int *arr, int left, int right) { if (left >= right) return; int p = Partition(arr, left, right); QuickSort(arr, left, p - 1); QuickSort(arr, p + 1, right); } ``` §§相关问题§§ 1. 快速排序的分区方法有哪些变体? 2. 如何避免快速排序的最坏时间复杂度? 3. 三路快速排序如何实现? </think> ### 快速排序实现分析(结合函数 `a`) #### 1. 函数 `a` 的功能定位 您提供的函数 `a` 实现了快速排序的核心部分——**分区操作(Partition)**,这是快速排序算法的关键步骤: ```c int a(int *arr, int left, int right) { // ... 分区操作实现 ... return i; // 返回基准值的最终位置 } ``` 其作用是将数组分为两个子序列: 1. 左侧:所有元素 ≤ 基准值 2. 右侧:所有元素 > 基准值 并返回基准值的最终位置(索引 `i`)[^4]。 #### 2. 与标准快速排序的关联 函数 `a` 需嵌入递归框架才能构成完整快速排序,如引用[2]所示: ```c void QuickSort(int* arr, int begin, int end) { if (begin >= end) return; int key = a(arr, begin, end); // 调用分区函数 QuickSort(arr, begin, key-1); // 递归左子序列 QuickSort(arr, key+1, end); // 递归右子序列 } ``` ##### 执行流程: 1. **选择基准**:`p = arr[left]`(首元素为基准) 2. **双指针分区**: - `j` 从右向左找 **≤ p** 的元素 - `i` 从左向右找 **≥ p** 的元素 - 交换不符合条件的元素对(`swap(arr[i], arr[j])`) 3. **基准归位**:`swap(arr[left], arr[i])` 4. **递归排序**:以基准位置 `i` 为界分割子数组[^3] #### 3. 代码问题分析 ##### 关键逻辑错误: ```c while (i < j && arr[right] <= p) j--; // ❌ 错误1:应使用 arr[j] 而非 arr[right] while (i < j && arr[left] >= p) i++; // ❌ 错误2:应使用 arr[i] 而非 arr[left] ``` **后果**: - 错误1:`arr[right]` 固定指向右边界,导致死循环 - 错误2:`arr[left]` 固定指向左边界,无法移动左指针 ##### 修正后的分区逻辑: ```c while (i < j) { while (i < j && arr[j] > p) j--; // ✅ 从右找 ≤ p 的元素 while (i < j && arr[i] < p) i++; // ✅ 从左找 ≥ p 的元素 if (i < j) swap(arr[i], arr[j]); } ``` #### 4. 优化建议(结合引用) ##### (1) 基准值选择优化(三数取中法) 解决最坏情况 $O(n^2)$ 复杂度[^1]: ```c // 在分区前添加: int mid = left + (right-left)/2; if (arr[mid] < arr[left]) swap(arr[mid], arr[left]); if (arr[right] < arr[left]) swap(arr[right], arr[left]); if (arr[mid] < arr[right]) swap(arr[mid], arr[right]); // 此时 arr[left] 是左/中/右的中位数 ``` ##### (2) 重复元素优化(三路分区) 处理大量重复元素时的退化问题[^4]: ```c void QuickSort3Way(int* arr, int low, int high) { if (low >= high) return; int lt = low, gt = high; int i = low + 1; int pivot = arr[low]; while (i <= gt) { if (arr[i] < pivot) swap(arr[lt++], arr[i++]); else if (arr[i] > pivot) swap(arr[i], arr[gt--]); else i++; } QuickSort3Way(arr, low, lt-1); QuickSort3Way(arr, gt+1, high); } ``` ##### (3) 递归深度优化(尾递归消除) 防止栈溢出[^2]: ```c void QuickSort(int* arr, int left, int right) { while (left < right) { // ✅ 改用循环减少递归深度 int p = Partition(arr, left, right); if (p-left < right-p) { QuickSort(arr, left, p-1); left = p+1; // 尾递归转迭代 } else { QuickSort(arr, p+1, right); right = p-1; // 尾递归转迭代 } } } ``` #### 5. 完整快速排序实现(优化版) ```c // 分区函数修正版 int Partition(int* arr, int left, int right) { // 三数取中优化基准选择 int mid = left + (right-left)/2; if (arr[mid] < arr[left]) swap(&arr[mid], &arr[left]); if (arr[right] < arr[left]) swap(&arr[right], &arr[left]); if (arr[mid] < arr[right]) swap(&arr[mid], &arr[right]); swap(&arr[left], &arr[mid]); int p = arr[left]; int i = left, j = right; while (i < j) { while (i < j && arr[j] >= p) j--; // 从右找 <p 的元素 while (i < j && arr[i] <= p) i++; // 从左找 >p 的元素 if (i < j) swap(&arr[i], &arr[j]); } swap(&arr[left], &arr[i]); return i; } // 快速排序主函数(含尾递归优化) void QuickSort(int* arr, int left, int right) { while (left < right) { int p = Partition(arr, left, right); if (p - left < right - p) { QuickSort(arr, left, p-1); left = p+1; } else { QuickSort(arr, p+1, right); right = p-1; } } } ``` ### 关键优化效果对比 | 优化方法 | 时间复杂度 | 空间复杂度 | 解决的核心问题 | |-------------------|------------------|------------|------------------------------| | 原始实现 | $O(n^2)$ 最坏 | $O(n)$ | - | | 三数取中基准 | $O(n \log n)$ 平均 | $O(\log n)$ | 输入有序时退化 | | 三路分区 | $O(n)$ 重复元素多时 | $O(\log n)$ | 大量重复元素时的性能退化 | | 尾递归消除 | $O(n \log n)$ | $O(1)$ | 递归深度过大导致栈溢出 | > 注:优化后平均时间复杂度 $O(n \log n)$,最坏情况 $O(n^2)$ 概率极低[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值