综述
快速排序算法是一种高效的排序算法,它采用了分治法的思想来进行排序。在快速排序中,我们选取一个元素作为基准值,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
示例
1. 初始状态
假设我们有一个无序的数组 [5, 3, 8, 6, 9, 2, 7]。
2. 选择基准值
在这个例子中,我们选择数组的第一个元素 5 作为基准值。
3. 划分数组
将数组中比基准值小的元素放在它的左边,比基准值大的元素放在它的右边。这一步也称为“分区”操作。
分区后的数组为:[2, 3, 5, 6, 9, 8, 7]
4. 递归排序子数组
递归地对基准值左边的子数组 [2, 3] 和右边的子数组 [6, 9, 8, 7] 进行快速排序。即重复前面1、2、3的步骤
5. 合并有序子数组
当递归到子数组长度为1或0时,停止递归。然后,将两个有序子数组合并成一个有序数组。
合并后的数组为:[2, 3, 5, 6, 7, 8, 9]
代码
以下是代码示例
python代码
def quick_sort(arr):
"""
这段代码实现的是经典快速排序算法,其基本思想是选择一个基准元素,
将列表分成两个子数组,小于等于基准的元素放在左边,大于基准的元素放在右边,
然后递归地对左右两个子列表进行排序。
:param arr: 原排序前无序列表
:return: 新排序后有序列表
"""
# 递归终止条件:列表长度小于或等于1,直接返回列表
if len(arr) <= 1:
return arr
# 选择基准元素,这里选择中间元素
pivot = arr[len(arr) // 2]
# 创建三个列表,分别存储小于、等于、大于基准的元素
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
# 递归调用快速排序,将小于和大于基准的元素分别排序,再拼接中间元素,返回结果
return quick_sort(left) + middle + quick_sort(right)
if __name__ == '__main__':
arr = [5, 3, 8, 6, 9, 2, 7]
print(quick_sort(arr))
java代码
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[]{5, 3, 8, 6, 9, 2, 7};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
/***
* 这段代码实现的是经典快速排序算法,其基本思想是选择一个基准元素,
* 将数组分成两个子数组,小于等于基准的元素放在左边,大于基准的元素放在右边,
* 然后递归地对左右两个子数组进行排序。
* 其中partition函数用于将小于等于基准的元素放在左边,大于基准的元素放在右边,并返回基准的索引。
* @param arr 原排序前无序列表
* @param left arr的起始索引
* @param right arr的结束索引
*/
private static void quickSort(int[] arr, int left, int right) {
// 递归终止条件:左指针大于等于右指针,返回
if (left >= right) {
return;
}
// 选择基准元素,这里选择最右边的元素
int pivotIndex = partition(arr, left, right);
// 递归调用快速排序,对基准左边和右边的子数组分别进行排序
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
private static int partition(int[] arr, int left, int right) {
// 选择基准元素,这里选择最右边的元素
int pivot = arr[right];
int i = left - 1;
// 遍历数组,将小于等于基准的元素放在左边,大于基准的元素放在右边
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
// 交换元素位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准放在正确的位置上
int temp = arr[i + 1];
arr[i + 1] = arr[right];
arr[right] = temp;
// 返回基准的索引
return i + 1;
}
}
时间复杂度分析
下面分别从最好、平均和最坏情况来介绍快速排序的时间复杂度。
1. 最好情况- O(nlogn)
当每次划分都能将数组分成两个等长的子数组时,快速排序的时间复杂度为 O(nlogn)。这是因为每次划分都能将数组分成两个等长的子数组,所以递归树的深度为 logn,而每个节点都需要进行一次比较和一次交换操作,所以总的时间复杂度为 O(nlogn)。
2. 最坏情况- O(n^2)
当每次划分都不能将数组分成两个等长的子数组时,快速排序的时间复杂度为 O(n^2)。这是因为每次划分只能将数组分成一个比原数组更小的子数组和一个比原数组更大的子数组,所以递归树的深度为 n,每个节点都需要进行一次比较和一次交换操作,所以总的时间复杂度为 O(n^2)。
3. 平均情况- O(nlogn)
快速排序的平均时间复杂度为 O(nlogn)。这是因为每次划分都能将数组分成两个等长的子数组的概率是存在的,所以快速排序的平均时间复杂度为 O(nlogn)。
总结
快速排序的时间复杂度取决于基准值的选择,最好情况是 O(nlogn),最坏情况是 O(n^2)。然而,在实际应用中,通过随机化选择基准值或使用其他技巧,可以使得快速排序在大多数情况下表现良好。