C++ 快速排序算法:从原理到优化的实战指南

快速排序(Quick Sort)作为 C++ 中最常用的高效排序算法之一,凭借其平均 O (n log n) 的时间复杂度和原地排序的特性,在实际开发和算法竞赛中被广泛应用。本文将从基本原理出发,逐步深入快速排序的实现细节、优化策略,并结合 C++ 特性探讨其在实际场景中的最佳实践。

一、快速排序的核心原理:分治思想的经典应用

快速排序的核心思想是 “分而治之”,通过以下三个步骤递归实现排序:

  1. 选择基准值(Pivot):从数组中选取一个元素作为基准(通常选第一个、最后一个或中间元素);
  1. 分区(Partition):将数组重新排列,所有比基准值小的元素移到基准值左侧,比基准值大的元素移到右侧(相等元素可放任意一侧);
  1. 递归排序:对基准值左右两侧的子数组重复上述步骤,直至子数组长度为 1 或 0(天然有序)。

形象理解:如同整理书籍时,先选一本作为参考,把比它薄的放左边,比它厚的放右边,再分别整理左右两堆书,最终实现整体有序。

二、基础实现:手写快速排序的关键步骤

1. 分区函数(Partition)的实现

分区是快速排序的核心操作,直接影响算法效率。以下是最常用的  Lomuto 分区法 (单边扫描):

#include 

using namespace std;

// 分区函数:返回基准值最终位置的索引

int partition(vector& arr, int low, int high) {

	int pivot = arr[high]; // 选最右侧元素作为基准值

	int i = low - 1; // i指向小于基准值区域的最后一个元素

// 遍历数组,将小于基准值的元素移到左侧

	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; // 返回基准值的索引

}

2. 递归排序函数

基于分区函数,实现快速排序的递归逻辑:

void quickSort(vector& arr, int low, int high) {

	if (low < high) {

// 分区并获取基准值位置

		int pi = partition(arr, low, high);

// 递归排序基准值左侧和右侧的子数组

		quickSort(arr, low, pi - 1); // 左侧子数组:[low, pi-1]

		quickSort(arr, pi + 1, high); // 右侧子数组:[pi+1, high]

	}

}

示例执行流程

对数组 [3, 6, 8, 10, 1, 2, 1] 排序时,首次分区以最后一个元素 1 为基准,经过交换后数组变为 [1, 6, 8, 10, 3, 2, 1],基准值位置为 0,随后递归处理右侧子数组。

三、进阶优化:解决快排的性能缺陷

快速排序在最坏情况(如数组已排序或所有元素相同)下时间复杂度会退化为 O (n²),通过以下优化可显著提升稳定性:

1. 基准值优化:三数取中法

当数组有序时,选择两端元素作为基准会导致分区失衡(一侧子数组为空)。三数取中法通过比较首、中、尾三个元素,选择中间值作为基准:

// 三数取中:选择首、中、尾三个元素的中间值作为基准

int medianOfThree(vector& arr, int low, int high) {

	int mid = low + (high - low) / 2; // 避免溢出的中间索引计算

// 排序三个位置的元素,使中间值移到high-1位置

	if (arr[low] > arr[mid]) swap(arr[low], arr[mid]);

	if (arr[low] > arr[high]) swap(arr[low], arr[high]);

	if (arr[mid] > arr[high]) swap(arr[mid], arr[high]);

	swap(arr[mid], arr[high - 1]); // 将基准值藏在high-1位置

	return arr[high - 1]; // 返回基准值

}

2. 处理重复元素:三路快排

当数组中存在大量重复元素时,传统快排会将元素分为 “小于” 和 “大于” 两部分,导致重复元素集中在一侧。三路快排将元素分为 “小于”“等于”“大于” 三部分,减少递归范围:

void quickSort3Way(vector& arr, int low, int high) {

	if (low >= high) return;

	int pivot = arr[low]; // 基准值

	int lt = low; // [low, lt) 存放小于基准值的元素

	int gt = high; // (gt, high] 存放大于基准值的元素

	int i = low + 1; // 当前遍历的元素索引

	while (i <= gt) {

		if (arr[i] < pivot) {

			swap(arr[lt++], arr[i++]);

		} else if (arr[i] > pivot) {

			swap(arr[i], arr[gt--]); // 交换后i不变,需重新检查新元素

		} else {

			i++; // 等于基准值的元素不交换,直接跳过

		}

	}

// 递归处理小于和大于基准值的子数组

	quickSort3Way(arr, low, lt - 1);

	quickSort3Way(arr, gt + 1, high);

}

3. 小规模数组优化:切换插入排序

递归到小规模子数组(如长度≤10)时,插入排序的实际效率高于快排(减少递归调用开销):

// 插入排序:用于小规模子数组

void insertionSort(vector& arr, int low, int high) {

	for (int i = low + 1; i <= high; i++) {

		int key = arr[i];

		int j = i - 1;

// 将大于key的元素后移

		while (j >= low && arr[j] > key) {

			arr[j + 1] = arr[j];

			j--;

		}

		arr[j + 1] = key;

	}

}

// 优化后的快排:小规模数组使用插入排序

void optimizedQuickSort(vector& arr, int low, int high) {

	const int THRESHOLD = 10; // 阈值,可根据实际情况调整

	if (high - low + 1 <= THRESHOLD) {

		insertionSort(arr, low, high); // 小规模数组用插入排序

		return;

	}

// 大规模数组用快排(结合三数取中)

	int pivot = medianOfThree(arr, low, high);

// ... 分区和递归逻辑(省略,参考前文)

}

四、C++ 标准库中的快排:std::sort 的实现细节

C++ 标准库的 std::sort 并非纯快排,而是混合排序算法(通常为 introsort,即内省排序):

  1. 首先使用快排,当递归深度超过 log2(n) 时(避免栈溢出),切换为堆排序;
  1. 对小规模子数组(通常长度≤16)使用插入排序;
  1. 针对重复元素较多的情况,部分实现会自动启用三路划分。

使用示例

 
  
#include  // 包含std::sort

#include 

int main() {

	vector arr = {3, 1, 4, 1, 5, 9, 2, 6};

// 对整个数组排序(默认升序)

	sort(arr.begin(), arr.end());

// 对部分区间排序(从索引1到5,左闭右开)

	sort(arr.begin() + 1, arr.begin() + 6);

	return 0;

}

优势:std::sort 经过编译器优化和工程实践验证,在绝大多数场景下性能优于手写快排,实际开发中应优先使用。

五、快排的适用场景与局限性

适用场景:

  • 处理大规模随机数据(平均 O (n log n) 复杂度,效率高);
  • 内存有限的场景(原地排序,空间复杂度 O (log n),仅递归栈开销);
  • 自定义排序规则的场景(通过传入比较函数实现,如降序排序)。

局限性:

  • 不稳定排序(相等元素的相对位置可能改变);
  • 最坏情况性能差(需通过优化避免);
  • 对链表排序效率低(快排依赖随机访问,链表更适合归并排序)。

六、总结:从手写优化到工程实践

快速排序是 C++ 算法中的基础且重要的知识点,掌握其原理不仅能应对面试和算法竞赛,更能理解 “分治” 思想在复杂问题中的应用。实际开发中:

  1. 优先使用 std::sort(兼顾性能和稳定性);
  1. 需自定义排序逻辑时,可基于快排思想实现,并结合三数取中、插入排序等优化;
  1. 处理特殊数据(如全重复、几乎有序)时,针对性选择三路快排或其他算法。

通过本文的实现与优化分析,希望能帮助你从 “会用” 快排进阶到 “理解并优化” 快排,在面对不同场景时做出最佳选择。

参考资料

  • 《C++ Primer》(第 5 版)第 10 章 泛型算法
  • C++ 标准库文档:https://en.cppreference.com/w/cpp/algorithm/sort
  • 算法导论(第 3 版)第 7 章 快速排序

你可能感兴趣的:(算法,数据结构,排序算法,快排)