在这篇介绍排序算法的文章中,我们将涵盖所有重要和常用的排序算法。我们将简要地解释每一种算法,它们的最坏和最好的情况,大O符号,然后最后比较它们的性能。
并非所有的算法都能直接相互竞争,有些算法在某些情况下会更好,或者有某些利基用途。不过一般来说,有些排序算法就是比其他算法好。尽管它们通常比简单的算法更难理解。然而,用复杂的东西换取更快的速度是一个足够公平的交易,特别是在性能关键的任务中。
这只是一篇介绍性文章,旨在简要地触及许多主题。对于带有工作实例的详细解释,请参考每个算法的单独教程,并附上链接。
你也可以查看我们在YouTube上的排序算法系列。
分拣算法术语
大O记号。一种特殊的符号,用于表示算法的时间复杂性和增长模式。大O符号的常见例子是O(n2)和O(n.logn)。O "是大O记数法格式的一部分。
原地排序。在不分配任何额外内存的情况下进行排序,通常在同一个数组/列表中进行。通常是通过交换值来完成。这种算法的例子是Quicksort和Insertion Sort。
位外排序。排序算法,在排序时需要额外的内存。它们通常会创建一个新的数组/列表,输出被排序到其中。
递归。这是指一个函数在它的主体中调用自己。通常情况下,递归需要额外的内存,因为递归调用会在堆栈内存中堆叠。然而,现代编译器也有自动优化功能,可以在编译时将递归代码转换为迭代代码,从而消除了额外的内存问题。递归解决方案由于其简短而有效的排序方式而受到欢迎。
**迭代式。**使用常规循环来进行排序。相当标准的排序方式。
你可以在这篇文章中了解更多关于排序算法和它的各种类型。
泡沫排序
**方法学。**最简单的一种排序,经常被教授给初学者。它涉及到在一个列表中交换两个值,如果前一个值大于后一个值。设置两个(嵌套的)for循环,遍历整个列表中的值,在两个值之间进行上述比较,如果需要的话,将它们交换。经过n - 1 ,(其中n是数组的长度)数组/列表成为完全排序的。
(平均) **大O符号:**n2
**排序的类型。**就地排序
了解更多关于泡沫排序算法的信息!
快速排序
方法。顾名思义,它是一种对数值列表进行快速排序的方法。快速排序通常采用递归解决方案,它使用了一个枢轴的概念,数值围绕这个枢轴进行排序。支点是从数值列表中挑选出的任何任意数值(通常是第一个数值)。然后,我们 "形象地 "将列表分成两部分,分别围绕支点。
经过一系列复杂的比较和交换,我们在列表的每一半上递归调用QuickSort函数(以支点为中间值)。
Quicksort可能相当不稳定(最坏的情况),但这在很大程度上可以通过使用随机Quicksort来避免,它可以将遇到最坏情况的变化降到最低。随机化Quicksort涉及洗牌或挑选一个Ranom枢轴,试图找到一个接近中值的值。一个数组的中值是快速排序算法最理想的支点。
**排序的类型。**就地排序
(平均) **大O记号:**n * log(n)
了解更多关于QuickSort算法+代码。
插入式排序
插入式排序算法的工作原理是将数组划分为两部分。第一部分是被排序的,而第二部分则不是。任何插入式排序算法的第一步都是将数组中的第一个元素作为第一部分。基本上你可以设想在第一个元素和其他的值之间有一条分隔线。
然后,我们开始将未排序部分的值插入到已排序部分。我们这样做的方法是挑选未排序部分的第一个元素,并将其推回到末端(在排序部分的最后一个元素之后)。如果新的元素小于排序部分的最后一个值,我们再把它移回去一次。
我们一直这样移动它,直到它后面的值大于新插入的值。我们重复这个过程,直到未排序部分没有任何元素。
**排序的类型。**就地排序
(平均) **大O记号:**n2
了解更多关于泡沫排序算法+代码!
选择排序
选择排序算法是一种比较简单的算法,你可以用它来进行排序。它使用一个非常简单的逻辑模式,包括找到被排序的项目的最小值,然后将其移到列表的开始位置。在下一次迭代中,我们缩小范围,排除第一个(现在已经排序的)最小值,然后继续定位下一个最小值。然后我们把这个最小值放在搜索范围的开始,这意味着它在我们找到的第一个最小值之后。这个过程一直持续到所有值都被排序完毕。
**排序的类型。**就地排序
(平均) 大O记号。 O(n2)
了解更多关于选择排序算法+代码!
合并排序
**方法学。**一种相当独特的排序方式,它需要两个列表(如果你有一个,那么只需围绕中心分割),然后同时以排序的方式合并它们。列表基本上是不断地重复减半,直到只剩下一个元素。然后,这些个体元素被相互比较,然后放在一个新的列表中,从最小的元素开始。
尽管比quicksort这样的排序更稳定(在最坏的情况下更好),但合并排序需要为最终的数值创建一个新的数组/列表,因此会消耗更多的内存。(它不在原地排序)。
**排序的类型。**不在原位
(平均)大O符号=n * log(n)
了解更多关于合并排序算法+代码!
桶式排序
Bucket Sort有几个独特的特点。首先,它需要使用另一种排序算法来完全排序数据。其次,它也不是一个基于比较的算法。第三,它与Radix Sort非常相似,最初的步骤实际上是一样的。
Bucket Sort的基本前提,是创建一定数量的桶,然后将所有的值(根据一个小公式),分配到桶里。然后将另一种排序算法应用于这些桶中的每一个,实际上,这些桶只是一维列表。这个排序过程是相当快的,因为对许多小列表进行排序要比对一个大列表进行排序快得多。(这是因为排序算法有像n2这样的时间复杂性,它随着n ,而急剧增加)
**排序的类型。**位外排序
(平均) **大O的记法。**O(n + k)
了解更多关于BucketSort算法+代码!
弧形排序
**方法论。**关于Radix排序的一个有趣的事情是,与大多数排序算法不同,它不是基于比较的。这意味着,在被排序的数值之间没有直接的比较。它使用 "通过 "的概念,基于最大数字的位数和Buckets的创建。Buckets的创建数量取决于被排序的数据类型。数字有10个Buckets,而字符有26个。
在Radix排序中,我们逐个数字进行排序,而不是数字。在第一遍中,单位数字(从右边开始的第一个)被找到,数字被放在正确的桶中(0-9)。第二遍,根据十位数(从右数起第二位)将其放入桶中,以此类推。
**排序的类型。**就地排序
(平均) **大O记号。**O(n*k)
了解更多关于Radix排序算法+代码!
堆排序
Max Heap是一种数据结构,我们创建一个类似二进制树的模式,由每个节点有两个孩子组成。规则是每个节点必须比它的子节点大。位于顶部的节点被称为根节点,因为它是堆中所有东西的根(父)。在开始实际排序之前,我们交换列表中的值,直到我们得到一个完美的最大堆。
堆排序算法使用Max Heap的概念对数组进行排序。这是可能的,因为我们知道数组中的第一个元素,也被称为根节点是最大的。通过不断地将根节点移动到数组的末端,并重新创建堆(同时忽略这个值)。
**排序的类型。**就地排序
(平均) **大O记号:**n * logn
了解更多关于堆排序算法+代码!
计数排序算法
另一种基于非比较的排序算法。
它在排序过程中会对数字列表进行多次传递。第一遍需要找到列表中的最大数字。然后我们用这个数字生成一个大小为最大数字+1的列表。然后我们计算每个数字在数组中出现的实例的次数,并将这个计数值存储在我们刚刚创建的列表中。在完成计数数组后,我们对整个数组进行迭代,累积计算里面所有的计数值。
最后,通过使用一个小公式,我们能够正确地确定每个数字在一个新创建的大小为n的数组中的索引,其中n是原数组中的数字数量。这个新的数组是一个完全排序的未排序的原始数组的版本。
**排序的类型。**就地排序
(平均) **大O记号。**O(n+k)
了解更多关于计数排序算法+代码!
排序比较(表)
一个显示一些最常用的排序算法的时间复杂度的表格。在比较两种排序算法时,时间复杂度是你需要检查的第一件事。时间复杂度越低越好。
| 排序算法 | 平均情况 | 最佳案例 | 最差情况 |
|---|---|---|---|
| 泡沫排序 | O(n2) | O(n) | O(n2) |
| 插入式排序 | O(n2) | O(n) | O(n2) |
| 选择排序 | O(n2) | O(n2) | O(n2) |
| 快速排序 | O(n.log(n)) | O(n.log(n)) | O(n2) |
| 合并排序 | O(n.log(n)) | O(n.log(n)) | O(n.log(n)) |
| 堆排序 | O(n.log(n)) | O(n.log(n)) | O(n.log(n)) |
| 计数排序 | O(n+k) | O(n+k) | O(n+k) |
| 径向排序 | O(n*k) | O(n*k) | O(n*k) |
| 桶式排序 | O(n+k) | O(n+k) | O(n2) |
还有很多其他的因素,比如空间复杂度、稳定或不稳定、原地或异地等,都是比较排序算法的依据。如需更详细的比较,请查看我们单独的专门文章《排序算法的比较》,其中还包括实际的现实世界测试,以比较速度。
你也可以使用这个网站,直观地看到本文中提到的所有类型的排序的计算时间差异和排序过程。看一看吧,这将有助于提高你的概念。
这标志着《排序算法介绍》一文的结束。我们非常欢迎对CodersLegacy的任何建议或贡献。关于教程内容的问题可以在下面的评论区提出。
The postIntroduction to Sorting Algorithmsappeared first onCodersLegacy.