C++ 中最快的排序算法28 Aug 2024 | 阅读 20 分钟 排序是计算机编程中常见的操作,选择正确的排序算法可以显著影响程序的效率。在 C++ 中,有几种排序算法可供选择,每种算法都有其优点和缺点。在这些算法中,快速排序(Quick Sort)作为最快、最高效的排序方法之一脱颖而出。 在本指南中,我们将深入探讨快速排序,研究其在 C++ 中的实现,理解其时间复杂度,并考虑何时以及如何使用它。读完本文,您将清楚地了解为什么快速排序通常被认为是 C++ 中最快的排序算法。 理解快速排序快速排序是一种高效的、基于比较的排序算法,它采用“分而治之”的策略来对数组或列表中的元素进行排序。它由 Tony Hoare 于 1960 年开发,并以其出色的平均情况性能而闻名。快速排序的基本思想是,从数组中选择一个“枢轴”(pivot)元素,并将其他元素划分为两个子数组:小于枢轴的元素和大于枢轴的元素。然后,此过程会递归地应用于子数组,直到整个数组排序完成。 该算法的效率归功于其能够快速地就地(in place)对小数组进行排序,从而降低了整体时间复杂度。 快速排序的关键特征在深入研究 C++ 中快速排序的实现之前,让我们先了解一下使其成为排序任务的吸引人选择的关键特征。
在 C++ 中实现快速排序现在,让我们深入研究 C++ 中快速排序的实现。我们将提供一个简单的示例,说明如何使用快速排序算法对整数数组进行排序。 输出 Sorted array: 5 6 7 11 12 13 解释 在上面的代码中,我们有两个主要函数:partition(分区)和 quickSort(快速排序)。partition 函数接受输入数组,并重新排列元素,使所有小于枢轴的元素位于左侧,所有大于枢轴的元素位于右侧。quickSort 函数递归地应用分区过程来排序子数组,直到整个数组排序完成。
分析快速排序的时间复杂度快速排序的时间复杂度是理解其效率的关键因素。以下是时间复杂度的分析: 最佳情况时间复杂度:最佳情况发生在每一步选择的枢轴将数组几乎均等地分成两个部分。在这种情况下,快速排序的时间复杂度为 O(n * log(n))。 平均情况时间复杂度:快速排序的平均时间复杂度也为 O(n * log(n))。这对于大多数实际数据集都是如此。 最坏情况时间复杂度:在最坏情况(即,枢轴始终选择为最小或最大的元素)下,时间复杂度可能降至 O(n^2)。然而,遇到这种情况的概率相对较低,尤其是在使用随机枢轴选择方法时。 实际上,快速排序通常比其他排序算法(即使是具有相同平均时间复杂度的算法)更快。这是因为它具有良好的缓存行为和最少的数据移动,使其成为高效排序大型数据集的首选。 快速排序的优点快速排序提供了许多优点,使其成为 C++ 和其他编程语言中排序任务的流行选择:
快速排序的应用快速排序是一种流行的排序算法,它通过将数组分成两个子数组并递归地对它们进行排序来工作。它以其效率而闻名,并广泛用于需要排序的各种应用程序。以下是快速排序的一些常见应用:
何时使用快速排序快速排序是一种多功能的排序算法,可在各种场景中使用。以下是快速排序特别适合的几种情况:
注意事项和变体虽然快速排序在许多情况下是一种出色的排序算法,但仍有一些注意事项需要牢记:
1. 快速排序 vs. 归并排序快速排序
就地排序:是 稳定性:否(可能改变相等元素的相对顺序) 主要优点:快速的平均情况性能、就地排序、缓存效率以及对各种数据类型和结构的适应性。 合并排序
就地排序:否 稳定性:是(保持相等元素的相对顺序) 主要优点:稳定的排序、恒定的性能以及对大型数据集的适应性。 比较 总体而言,快速排序在实践中通常比归并排序更快,尤其是在大型数据集上。但是,归并排序提供了更可预测、更一致的性能。 快速排序是一种就地排序算法,这在内存资源有限时很有用,而归并排序需要额外的内存。 归并排序是一种稳定的排序算法,这意味着它能保留相等元素的顺序。快速排序不稳定。 2. 快速排序 vs. 堆排序快速排序
就地排序:是 稳定性:否 主要优点:快速的平均情况性能、就地排序、缓存效率以及对各种数据类型和结构的适应性。 堆排序
就地排序:是 稳定性:否 主要优点:可预测的性能、就地排序、无需额外内存,以及适用于排序优先队列(通过堆数据结构)。 比较 快速排序和堆排序具有相似的平均和最坏情况时间复杂度,使其在效率方面相对具有竞争力。 快速排序在实践中通常优于堆排序,因为它具有良好的缓存行为,可减少数据移动。 堆排序是可预测且稳定的排序算法,而快速排序不稳定,且可能出现罕见的最坏情况。 3. 快速排序 vs. Tim Sort快速排序
就地排序:是 稳定性:否 主要优点:快速的平均情况性能、就地排序、缓存效率以及对各种数据类型和结构的适应性。 Tim Sort
就地排序:否 稳定性:是 主要优点:稳定的排序、对真实数据集的适应性,以及结合了归并排序和插入排序以提高效率。 比较 Tim Sort 结合了归并排序和插入排序的优点,提供了一种混合排序算法,在真实世界的数据中具有良好的性能。 在某些情况下,快速排序可能比 Tim Sort 更快,尤其是在数据集非常大或包含大部分唯一元素时。 Tim Sort 稳定且对于具有现有顺序或部分排序数据的数据集非常高效,使其成为实际应用的绝佳选择。 总而言之,快速排序因其平均情况性能、就地排序和适应性而受到青睐,使其成为许多排序任务的首选。然而,排序算法的选择应基于您项目的具体要求、数据的性质以及稳定性、内存使用和可预测性能等因素之间的权衡。这些竞争性的排序算法各有优缺点,适用于不同的用例。 快速排序的历史快速排序是最广泛使用的排序算法之一,其悠久的历史可以追溯到 20 世纪 60 年代初。它由英国计算机科学家 Tony Hoare 发明,他最初在剑桥大学的 Ferranti Mark 1 计算机上工作时构思了该算法。 以下是快速排序的历史和演变的简要概述: 1960 年:快速排序的诞生快速排序由 Tony Hoare 在 1960 年发明,当时他正在研究早期的计算机 Ferranti Mark 1。那时,他正在寻找一种高效的数据排序方法,并提出了快速排序算法作为解决方案。 该算法首次由 Hoare 在他 1962 年 1 月发表于《ACM 通讯》(Association for Computing Machinery)期刊上的研究论文《Algorithm 64: Quicksort》中介绍。这篇论文概述了快速排序的概念并提供了初始算法。 1961 年:Hoare 的首次实现1961 年,Tony Hoare 首次实现了快速排序算法。他最初的实现使用递归将数据分成更小的子数组并高效地对其进行排序。 1962 年:发表论文如前所述,Hoare 于 1962 年发表的论文《Algorithm 64: Quicksort》正式将快速排序介绍给了计算界。这一出版物在普及该算法方面发挥了关键作用。 20 世纪 70 年代:广泛采用快速排序在 20 世纪 70 年代在计算界广受欢迎。其效率和简洁性使其成为学术界和工业界排序任务的有吸引力的选择。 1995 年:引入 C++ 标准库快速排序的重要性促使其作为 C++ 标准库中可供开发人员使用的排序算法之一。自那时以来,它一直是库的标准排序函数的一部分。 变体和优化多年来,人们提出了快速排序的各种变体和优化,以提高其性能。这些包括用于选择枢轴元素的策略,在某些情况下切换到替代排序算法(例如,对小数组使用插入排序),以及调整算法以适应并行和多核计算环境。 快速排序经历了各种优化和改进。提出了该算法的不同变体,例如随机快速排序和三路分区快速排序,以进一步提高其性能并处理特定场景。 如今,快速排序在实践中被广泛使用,并且通常是许多编程语言和库中排序的默认选择。其高效的性质、易于实现的特点和多功能性使其成为各种应用的流行选择。 示例示例 1:C++ 中使用字符串数组的快速排序输出 Original Array: apple banana cherry date grape fig Sorted Array: apple banana cherry date fig grape 解释
partition 接受字符串向量 arr 和两个索引 low 和 high。 它将最后一个元素 (arr[high]) 选作枢轴。 它将范围 [low, high-1] 中的每个元素与枢轴进行比较。 如果元素小于或等于枢轴,则将其移到枢轴的左侧。 循环结束后,它将枢轴放置在其正确的位置,较小的元素在左侧,较大的元素在右侧,并返回枢轴索引。
quickSort 通过递归地选择枢轴和分区数组来对字符串向量进行排序。 它从 low 和 high 定义的范围开始。 如果 low 小于 high,则它会 调用 partition 来查找枢轴索引 (pi) 并对数组进行分区。 递归地将 quickSort 应用于枢轴的左侧和右侧子数组(枢轴之前和之后)。
初始化一个字符串向量 arr,其中包含未排序的元素。 打印原始数组。 调用 quickSort 对数组进行排序。
在此示例中,快速排序用于对字符串向量进行排序。分区和交换操作基于字符串比较进行。编译并运行此代码,以查看快速排序如何应用于不同的数据类型。 示例 2:对浮点数数组进行排序输出 Original Array: 9.1 7.2 5.3 11.4 2.5 1.6 Sorted Array: 1.6 2.5 5.3 7.2 9.1 11.4 解释
示例 3:排序自定义对象输出 Original Students: Alice - 85 Bob - 72 Charlie - 94 David - 68 Eve - 91 Sorted Students (by score): David - 68 Bob - 72 Alice - 85 Eve - 91 Charlie - 94 解释
定义了一个名为 Student 的自定义类来表示学生。 每个 Student 对象都有两个属性:name(字符串)和 score(整数)。 提供了一个构造函数,用于在创建 Student 对象时初始化这些属性。
定义了一个名为 compareStudents 的自定义比较函数。 此函数接受两个 Student 对象作为输入,并根据它们的 score 属性进行比较。 如果第一个学生的 score 小于第二个学生的 score,则返回 true,表示第一个学生在排序顺序中应排在第二个学生之前。
在 main 函数中,创建了一个名为 students 的向量。 该向量包含多个 Student 对象,每个对象代表一个具有姓名和分数的学生。
程序将原始学生列表打印到控制台。 对于 students 向量中的每个学生,它会显示学生的姓名和分数。
使用 std::sort 函数对 students 向量进行排序。 它根据学生的分数以升序重新排列学生。 compareStudents 函数用作比较标准,以确定排序过程中学生的顺序。
排序后,程序再次将学生列表打印到控制台。 这一次,它按分数的升序显示学生,显示他们的姓名和分数。 总之,此程序演示了如何定义自定义类(Student),创建该类的对象,根据特定属性(score)对其进行排序,并显示原始和排序后的学生列表。排序是使用带有自定义比较函数的 std::sort 函数执行的。 在此示例中,我们有一个自定义 Student 类,我们使用快速排序根据分数对 Student 对象向量进行排序。 这些示例演示了快速排序在对不同数据类型和自定义对象进行排序方面的灵活性。您可以通过为特定用例定义适当的比较函数或运算符重载来使快速排序与各种数据结构协同工作。 排序技术(从快到慢)排序算法根据输入数据和特定场景具有不同的时间复杂度和性能特征。以下是常见排序技术的排名(从快到慢),并附有简要说明: 快速排序 对于通用排序任务,快速排序通常是最快的。它具有 O(n * log(n)) 的平均时间复杂度,并以其出色的缓存行为和对不同数据类型的适应性而闻名。 合并排序 归并排序以其一致的性能而闻名。它具有 O(n * log(n)) 的平均和最坏情况时间复杂度,使其能够高效地对大型数据集进行排序。它也很稳定,这意味着它保留了相等元素的相对顺序。 堆排序 堆排序具有 O(n * log(n)) 的平均和最坏情况时间复杂度。它是一种就地排序算法,具有可预测的性能。它常用于需要可靠、内存高效的排序解决方案的场景。 Tim Sort Tim Sort 是一种混合排序算法,它结合了归并排序和插入排序的元素。它旨在高效处理真实世界的数据,并且是稳定的。其平均时间复杂度为 O(n * log(n))。 Intro Sort Intro Sort 是快速排序的一个变体,当递归深度超过特定阈值时会切换到堆排序。它结合了快速排序的速度优势和堆排序的最坏情况性能保证。 冒泡排序 冒泡排序在最坏和平均情况下的时间复杂度均为 O(n^2),因此效率低于之前的算法。由于其简单性,它主要用于教育目的。 选择排序 选择排序在最坏和平均情况下的时间复杂度也均为 O(n^2)。它很简单,但对于大型数据集来说效率不如其他算法。 插入排序 插入排序,在最坏和平均情况下的时间复杂度为 O(n^2),常用于小型数据集或作为 Tim Sort 等更复杂算法的一部分。 希尔排序 希尔排序通过在排序较远的元素之前对它们进行比较来改进插入排序。它的时间复杂度比基本的二次算法要好,但仍比前面提到的更高级的算法慢。 这些排序算法的实际性能可能因数据集大小、数据初始顺序以及硬件和软件环境等因素而异。排序算法的选择应基于您项目的特定要求和限制。 结论快速排序通常被认为是 C++ 中最快的排序算法,因为它具有 O(n * log(n)) 的平均时间复杂度和高效的就地排序。其分而治之策略、随机枢轴选择和缓存效率都对其在实践中的出色性能做出了贡献。 在 C++ 中选择排序算法时,请考虑您数据的特征和项目的具体要求。快速排序是通用排序任务的绝佳选择,尤其是在处理大型数据集和内存限制时。 然而,在使用随机枢轴选择时,重要的是要记住最坏情况的可能性和算法的不确定性。在某些情况下,其他排序算法(如归并排序或 Tim Sort)可能更合适。 通过了解快速排序的优点和注意事项,您可以明智地决定它是否是您编程需求的正确排序算法,最终优化您应用程序的效率。 下一主题C++ 中的二叉树边界遍历 |
我们请求您订阅我们的新闻通讯以获取最新更新。