写在开头的话
经过了前两天的学习,相信大家都对排序算法有了相当的了解,那么今天我们来学习一下面试高频的快速排序算法吧。
第一节
知识点:
(1)基本算法(2)性能特性(3)划分策略
基本算法
快速排序是一种基于分治思想的排序算法,其基本原理可以概括为以下步骤:
-
选择基准元素 :从待排序数组中选择一个元素作为基准元素。通常选择数组的第一个元素、最后一个元素或者随机选择。
-
分区过程 :将数组中的其他元素按照与基准元素的大小关系分为两部分,比基准元素小的放在基准元素的左边,比基准元素大的放在右边。这个过程称为分区操作。
-
递归排序 :对分区后的两个子数组分别递归地进行快速排序。即对左侧子数组和右侧子数组重复步骤1和步骤2,直到数组长度为1或0,此时数组已经有序。
-
合并结果 :当递归调用结束后,整个数组已经被分成了以基准元素为界的左右两部分,左边部分小于等于基准元素,右边部分大于等于基准元素。此时整个数组已经有序。
快速排序的核心在于分区过程,也就是如何将数组中的元素按照基准元素的大小关系分成两部分。一般采用的方法是通过双指针法,从数组的两端向中间扫描,交换元素位置,直到两个指针相遇。
图示

代码实现
C++代码实现
cpp
#include <iostream>
#include <vector>
using namespace std;
int partition(vector<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++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort(vector<int>& arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
vector<int> arr = {12, 5, 7, 3, 8, 10, 1};
int n = arr.size();
quickSort(arr, 0, n - 1);
cout << "Sorted array: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
Java代码实现
java
import java.util.Arrays;
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
public 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++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
int[] arr = {12, 5, 7, 3, 8, 10, 1};
int n = arr.length;
quickSort(arr, 0, n - 1);
System.out.print("Sorted array: ");
System.out.println(Arrays.toString(arr));
}
}
Python代码实现
python
def partition(arr, low, high):
pivot = arr[high] # 选择最后一个元素作为基准
i = low - 1
for j in range(low, high):
if arr[j] < pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
def quickSort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
quickSort(arr, low, pi - 1)
quickSort(arr, pi + 1, high)
arr = [12, 5, 7, 3, 8, 10, 1]
n = len(arr)
quickSort(arr, 0, n - 1)
print("Sorted array:", arr)
运行结果

时间复杂度和空间复杂度
时间复杂度
快速排序的时间复杂度主要取决于两个因素:分区的方式和选取的基准元素。
- 最佳情况时间复杂度 :在最佳情况下,每次分区操作都能均匀地将数组分成两部分,这样每次递归调用都会将问题规模减半。因此,最佳情况下时间复杂度为 O(nlogn) ,其中 n 是数组的长度。
- 最坏情况时间复杂度 :在最坏情况下,每次分区操作只能将数组分成一个较小的部分和一个较大的部分,这样快速排序就退化成了类似于冒泡排序的时间复杂度。最坏情况下时间复杂度为 O(
) ,其中 n 是数组的长度。最坏情况通常发生在选择的基准元素是最小或最大值的情况下。
- 平均情况时间复杂度 :在平均情况下,快速排序的时间复杂度为 O(nlogn) 。这是由于在平均情况下,基准元素被选取得相对均匀,使得每次分区操作能够将问题规模减半。
举个例子,对于一个已经排序好的数组来说,如果我们每次取的基准值还是最小或者最大值,那么就会出现时间复杂度退化成 O() 的情况。为了尽可能避免这种情况的发生,我们在每次选择基准值时可以随机选择一个数来进行。这样时间复杂度能保持在 O(nlogn) 。
空间复杂度
快速排序的空间复杂度主要由递归调用和分区过程中使用的额外空间决定。
- 递归调用的空间复杂度 :快速排序通常使用递归来实现,递归调用会占用一定的栈空间。在最坏情况下,快速排序的递归调用栈的深度可以达到 O(n) ,因此最坏情况下空间复杂度为 O(n) 。
- 额外空间复杂度 :在原地排序实现的情况下,快速排序的额外空间复杂度为 O(logn),其中 logn 是递归调用栈的深度。这是由于每次递归调用只需要常数级别的额外空间来存储分区过程中的一些临时变量。
总的来说,当时间复杂度是最坏的情况下,空间复杂度也是最坏的。我们在每次选择基准值时可以随机选择一个数,这样能将空间复杂度保持在 O(logn) 。
划分策略
快速排序的划分策略是指如何选择基准元素并将数组中的其他元素按照与基准元素的大小关系分成两部分。这个过程对快速排序的性能有重要影响。常见的划分策略包括以下几种:
固定基准元素:
- 最简单的划分策略是选择数组的第一个元素、最后一个元素或者中间元素作为基准元素。
- 优点是实现简单,适用于大多数情况。
- 缺点是如果输入数据是有序的或者接近有序的,可能会导致快速排序的性能退化为 O(
) 。
随机选择基准元素:
- 在每次排序前随机选择一个数组中的元素作为基准元素。
- 优点是能够在一定程度上避免最坏情况的发生,使得快速排序的平均性能更稳定。
- 缺点是实现略微复杂,需要引入随机数生成器。
三数取中法:
- 选择数组的头、尾和中间位置的元素,然后取它们的中值作为基准元素。
- 优点是在大多数情况下能够选择到较为合适的基准元素,避免了最坏情况的发生。
- 缺点是略微增加了额外的比较操作。
双路快速排序:
- 使用两个指针分别从数组的头和尾开始向中间扫描,同时进行比较和交换操作。
- 这样可以避免在有大量重复元素的情况下导致分区不均匀,提高了算法的性能。
三路快速排序:
- 将数组分为小于、等于和大于基准元素三个部分,分别对这三个部分进行递归排序。
- 特别适用于处理有大量重复元素的情况,能够提高算法的性能。
简单总结
本节主要实现了快速排序算法,并且学习了快速排序在不同的划分策略下时空复杂度的不同。
第二节
本节将学习多路快速排序算法,这是快速排序的一种改进版本,能够更有效地处理数组中存在大量重复元素的情况。在过程中,我们将编写代码来实现多路快速排序算法,并对随机生成的数组进行排序。通过本节,我们将深入了解多路快速排序的实现原理和算法思想,以及它在处理具有重复元素的数组时的性能表现。
知识点:
(1)多路快排优化(2)多路快排的递归与非递归实现(3)多路快排的最坏情况改(4)多路快排与普通快排的对比
多路快排优化
多路快排优化简介
多路快速排序是快速排序的一种改进版本,用于高效地对数组进行排序。与传统的快速排序不同,多路快速排序不仅将数组分成两部分,而是将数组划分为多个区域,以处理数组中存在大量重复元素的情况。
在多路快速排序中,通常会选择多个基准值,并将数组划分为小于某个基准值、等于某个基准值和大于某个基准值的三个区域。然后,递归地对小于和大于区域进行排序,直到整个数组有序。
多路快速排序的时间复杂度与传统快速排序相同,为 O(nlogn),但在处理包含大量重复元素的数组时,其性能更优。
多路快排的递归代码实现
C++代码实现
cpp
#include <iostream>
#include <vector>
// 交换数组中两个元素的值
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 多路快速排序的划分函数
void partition(std::vector<int>& nums, int left, int right, int& i, int& j) {
if (right - left <= 1) {
if (nums[right] < nums[left])
swap(nums[right], nums[left]);
i = left;
j = right;
return;
}
int mid = left;
int pivot = nums[right];
while (mid <= right) {
if (nums[mid] < pivot)
swap(nums[left++], nums[mid++]);
else if (nums[mid] == pivot)
mid++;
else if (nums[mid] > pivot)
swap(nums[mid], nums[right--]);
}
i = left - 1;
j = mid;
}
// 多路快速排序递归函数
void quickSort(std::vector<int>& nums, int left, int right) {
if (left >= right)
return;
int i, j;
partition(nums, left, right, i, j);
quickSort(nums, left, i);
quickSort(nums, j, right);
}
// 多路快速排序的接口函数
void multiwayQuickSort(std::vector<int>& nums) {
quickSort(nums, 0, nums.size() - 1);
}
int main() {
std::vector<int> nums = {3, 5, 1, 6, 8, 2, 4, 7};
multiwayQuickSort(nums);
std::cout << "Sorted array: ";
for (int num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Java代码实现
java
import java.util.Arrays;
public class MultiwayQuickSort {
// 多路快速排序的划分函数
private static void partition(int[] nums, int left, int right, int[] i, int[] j) {
if (right - left <= 1) {
if (nums[right] < nums[left])
swap(nums, right, left);
i[0] = left;
j[0] = right;
return;
}
int mid = left;
int pivot = nums[right];
while (mid <= right) {
if (nums[mid] < pivot)
swap(nums, left++, mid++);
else if (nums[mid] == pivot)
mid++;
else if (nums[mid] > pivot)
swap(nums, mid, right--);
}
i[0] = left - 1;
j[0] = mid;
}
// 交换数组中两个元素的值
private static void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
// 多路快速排序递归函数
private static void quickSort(int[] nums, int left, int right) {
if (left >= right)
return;
int[] i = new int[1];
int[] j = new int[1];
partition(nums, left, right, i, j);
quickSort(nums, left, i[0]);
quickSort(nums, j[0], right);
}
// 多路快速排序的接口函数
public static void multiwayQuickSort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public static void main(String[] args) {
int[] nums = {3, 5, 1, 6, 8, 2, 4, 7};
multiwayQuickSort(nums);
System.out.print("Sorted array: ");
System.out.println(Arrays.toString(nums));
}
}
Python代码实现
python
def partition(nums, left, right):
if right - left <= 1:
if nums[right] < nums[left]:
nums[right], nums[left] = nums[left], nums[right]
i = left
j = right
return i, j
mid = left
pivot = nums[right]
while mid <= right:
if nums[mid] < pivot:
nums[left], nums[mid] = nums[mid], nums[left]
left += 1
mid += 1
elif nums[mid] == pivot:
mid += 1
elif nums[mid] > pivot:
nums[mid], nums[right] = nums[right], nums[mid]
right -= 1
i = left - 1
j = mid
return i, j
def quick_sort(nums, left, right):
if left >= right:
return
i, j = partition(nums, left, right)
quick_sort(nums, left, i)
quick_sort(nums, j, right)
def multiway_quick_sort(nums):
quick_sort(nums, 0, len(nums) - 1)
nums = [3, 5, 1, 6, 8, 2, 4, 7]
multiway_quick_sort(nums)
print("Sorted array:", nums)
运行结果

递归版本多路快速排序代码原理
partition函数用于将数组划分为小于、等于和大于基准值的三个部分,返回两个索引值i和j,表示小于基准值的最后一个元素和大于基准值的第一个元素的索引位置。quickSort函数是递归的核心部分,它通过调用partition函数将数组划分为三个部分,并对左右两部分分别递归调用quickSort函数进行排序。multiwayQuickSort函数是多路快速排序的入口函数,它接受一个数组作为参数,并调用quickSort函数对整个数组进行排序。
整体而言,该算法在每次递归调用中,将数组划分为三个部分,然后分别对左右两部分进行递归排序,直到排序完成。
多路快排的非递归代码实现
C++代码实现
cpp
#include <iostream>
#include <vector>
#include <stack>
#include <utility>
// 交换数组中两个元素的值
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 多路快速排序的划分函数
std::pair<int, int> partition(std::vector<int>& nums, int left, int right) {
int pivot = nums[right];
int i = left - 1;
int j = left;
int k = right;
while (j < k) {
if (nums[j] < pivot)
swap(nums[++i], nums[j++]);
else if (nums[j] == pivot)
j++;
else
swap(nums[j], nums[--k]);
}
swap(nums[k], nums[right]);
return std::make_pair(i, j);
}
// 非递归多路快速排序函数
void multiwayQuickSort(std::vector<int>& nums) {
std::stack<std::pair<int, int>> st;
st.push(std::make_pair(0, nums.size() - 1));
while (!st.empty()) {
int left = st.top().first;
int right = st.top().second;
st.pop();
if (left >= right) continue;
std::pair<int, int> p = partition(nums, left, right);
st.push(std::make_pair(left, p.first));
st.push(std::make_pair(p.second, right));
}
}
int main() {
std::vector<int> nums = {3, 5, 1, 6, 8, 2, 4, 7};
multiwayQuickSort(nums);
std::cout << "Sorted array: ";
for (int num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Java代码实现
java
import java.util.*;
public class NonRecursiveMultiwayQuickSort {
// 交换数组中两个元素的值
private static void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
// 多路快速排序的划分函数
private static int[] partition(int[] nums, int left, int right) {
int pivot = nums[right];
int i = left - 1;
int j = left;
int k = right;
while (j < k) {
if (nums[j] < pivot)
swap(nums, ++i, j++);
else if (nums[j] == pivot)
j++;
else
swap(nums, j, --k);
}
swap(nums, k, right);
return new int[] {i, j};
}
// 非递归多路快速排序函数
public static void multiwayQuickSort(int[] nums) {
Stack<int[]> stack = new Stack<>();
stack.push(new int[] {0, nums.length - 1});
while (!stack.isEmpty()) {
int[] pair = stack.pop();
int left = pair[0], right = pair[1];
if (left >= right) continue;
int[] p = partition(nums, left, right);
stack.push(new int[] {left, p[0]});
stack.push(new int[] {p[1], right});
}
}
public static void main(String[] args) {
int[] nums = {3, 5, 1, 6, 8, 2, 4, 7};
multiwayQuickSort(nums);
System.out.print("Sorted array: ");
System.out.println(Arrays.toString(nums));
}
}
Python代码实现
python
def partition(nums, left, right):
pivot = nums[right]
i = left - 1
j = left
k = right
while j < k:
if nums[j] < pivot:
nums[i+1], nums[j] = nums[j], nums[i+1]
i += 1
j += 1
elif nums[j] == pivot:
j += 1
else:
nums[j], nums[k-1] = nums[k-1], nums[j]
k -= 1
nums[k], nums[right] = nums[right], nums[k]
return i, j
def multiway_quick_sort(nums):
stack = [(0, len(nums) - 1)]
while stack:
left, right = stack.pop()
if left >= right:
continue
i, j = partition(nums, left, right)
stack.append((left, i))
stack.append((j, right))
nums = [3, 5, 1, 6, 8, 2, 4, 7]
multiway_quick_sort(nums)
print("Sorted array:", nums)
运行结果

非递归版本多路快速排序代码原理
partition函数用于将数组划分为小于、等于和大于基准值的三个部分,返回两个索引值i和j,表示小于基准值的最后一个元素和大于基准值的第一个元素的索引位置。multiwayQuickSort函数是非递归多路快速排序的入口函数,它利用栈来模拟递归过程,不断划分子数组并将其压入栈中,直到所有子数组都排好序为止。
整体而言,该算法使用非递归方式实现了多路快速排序,通过维护一个栈来避免递归调用的开销,提高了算法的效率。
快速排序其他改进方法
-
随机化选择基准值: 在每次划分过程中,随机选择一个元素作为基准值,而不是固定地选择最后一个元素。这样可以降低出现最坏情况的概率,提高算法的性能。
-
使用三数取中法: 不再只使用一个基准值进行划分,而是选择三个元素,并取其中位数作为基准值。这样可以进一步降低最坏情况的发生概率,并且在某些情况下可以提高排序的效率。
-
优化递归过程: 在递归过程中,对于小规模子数组可以使用插入排序等简单但效率更高的排序算法,避免过多的递归调用。
这些改进方法可以有效地降低多路快速排序的最坏情况发生的概率,提高算法的稳定性和性能。
多路快排和普通快排的对比

不过在随机数据的情况下,虽然两种快排时间复杂度一样,但普通快排的代码运行常数是要小于多路快排的。因此在随机数据情况下,普通快排还是要比多路快排要快一点。同时,数据量也会对排序造成影响,一般情况下,可以认为计算机 1 秒可以执行 次指令。如果数据过小,不太容易得到比较结果。
针对有序数据进行排序的代码
C++代码实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
std::swap(arr[++i], arr[j]);
}
}
std::swap(arr[++i], arr[high]);
quickSort(arr, low, i - 1);
quickSort(arr, i + 1, high);
}
}
void multiWayQuickSort(std::vector<int>& arr, int low, int high) {
if (low >= high) return;
int pivot = arr[low + rand() % (high - low + 1)];
std::vector<int> less, equal, greater;
for (int i = low; i <= high; ++i) {
if (arr[i] < pivot) less.push_back(arr[i]);
else if (arr[i] > pivot) greater.push_back(arr[i]);
else equal.push_back(arr[i]);
}
int k = low;
for (int x : less) arr[k++] = x;
for (int x : equal) arr[k++] = x;
for (int x : greater) arr[k++] = x;
multiWayQuickSort(arr, low, low + less.size() - 1);
multiWayQuickSort(arr, high - greater.size() + 1, high);
}
int main() {
srand(time(0));
const int SIZE = 10000;
std::vector<int> arr1(SIZE), arr2(SIZE);
for (int i = 0; i < SIZE; ++i) {
int value = i;
arr1[i] = value;
arr2[i] = value;
}
clock_t start = clock();
quickSort(arr1, 0, SIZE - 1);
clock_t end = clock();
std::cout << "QuickSort time: " << double(end - start) / CLOCKS_PER_SEC << " seconds\n";
start = clock();
multiWayQuickSort(arr2, 0, SIZE - 1);
end = clock();
std::cout << "MultiWayQuickSort time: " << double(end - start) / CLOCKS_PER_SEC << " seconds\n";
return 0;
}
Java代码实现
java
import java.util.*;
public class QuickSortComparison {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
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++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void multiWayQuickSort(int[] arr, int low, int high) {
if (low >= high) return;
int pivot = arr[low + new Random().nextInt(high - low + 1)];
List<Integer> less = new ArrayList<>();
List<Integer> equal = new ArrayList<>();
List<Integer> greater = new ArrayList<>();
for (int i = low; i <= high; i++) {
if (arr[i] < pivot) less.add(arr[i]);
else if (arr[i] > pivot) greater.add(arr[i]);
else equal.add(arr[i]);
}
int k = low;
for (int x : less) arr[k++] = x;
for (int x : equal) arr[k++] = x;
for (int x : greater) arr[k++] = x;
multiWayQuickSort(arr, low, low + less.size() - 1);
multiWayQuickSort(arr, high - greater.size() + 1, high);
}
public static void main(String[] args) {
final int SIZE = 10000;
int[] arr1 = new int[SIZE];
int[] arr2 = new int[SIZE];
Random rand = new Random();
for (int i = 0; i < SIZE; i++) {
int value = i;
arr1[i] = value;
arr2[i] = value;
}
long start = System.currentTimeMillis();
quickSort(arr1, 0, SIZE - 1);
long end = System.currentTimeMillis();
System.out.println("QuickSort time: " + (end - start) + " milliseconds");
start = System.currentTimeMillis();
multiWayQuickSort(arr2, 0, SIZE - 1);
end = System.currentTimeMillis();
System.out.println("MultiWayQuickSort time: " + (end - start) + " milliseconds");
}
}
Python代码实现
python
import random
import time
def quick_sort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
quick_sort(arr, low, pi - 1)
quick_sort(arr, pi + 1, high)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] < pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
def multi_way_quick_sort(arr, low, high):
if low >= high:
return
pivot = arr[random.randint(low, high)]
less, equal, greater = [], [], []
for i in range(low, high + 1):
if arr[i] < pivot:
less.append(arr[i])
elif arr[i] > pivot:
greater.append(arr[i])
else:
equal.append(arr[i])
k = low
for x in less:
arr[k] = x
k += 1
for x in equal:
arr[k] = x
k += 1
for x in greater:
arr[k] = x
k += 1
multi_way_quick_sort(arr, low, low + len(less) - 1)
multi_way_quick_sort(arr, high - len(greater) + 1, high)
if __name__ == "__main__":
SIZE = 20000
arr1 = list(range(SIZE))
arr2 = list(arr1)
start = time.time()
quick_sort(arr1, 0, SIZE - 1)
end = time.time()
print(f"QuickSort time: {end - start} seconds")
start = time.time()
multi_way_quick_sort(arr2, 0, SIZE - 1)
end = time.time()
print(f"MultiWayQuickSort time: {end - start} seconds")
运行结果

需要注意的是,由于这里调用了时间函数,不同的电脑运行速度不同,导致的结果也是不同,重点在于在特定数据的情况下,多路快排要远胜于普通快排。
简单总结
多路快速排序是一种高效的排序算法,通过利用多个基准值来划分数组,能够有效地降低排序的时间复杂度。在本次实验中,我们深入学习了多路快排的原理和实现方式,并进行了实际的编程练习。
-
理解多路快速排序的思想: 多路快速排序是在传统的快速排序基础上进行的改进,通过多个基准值将数组划分成多个部分,避免了传统快速排序中出现的退化情况,提高了排序的稳定性和性能。
-
掌握非递归实现方式: 通过使用栈来模拟递归过程,我们实现了非递归版本的多路快速排序算法,避免了递归调用的开销,提高了算法的效率。
-
考虑最坏情况下的优化策略: 多路快速排序虽然平均情况下性能良好,但在最坏情况下仍可能退化为 O(
) 的时间复杂度。我们学习了一些优化策略,如随机化选择基准值和使用三取样划分等,来降低最坏情况发生的概率。
通过本节,我们对多路快速排序算法有了更深入的理解,并且掌握了实现该算法的技巧和方法。