目录
[冒泡排序(Bubble Sort)](#冒泡排序(Bubble Sort))
[插入排序(Insertion Sort)](#插入排序(Insertion Sort))
[选择排序(Selection Sort)](#选择排序(Selection Sort))
[归并排序(Merge Sort)](#归并排序(Merge Sort))
[堆排序(Heap Sort)](#堆排序(Heap Sort))
[1. 构建最大堆:](#1. 构建最大堆:)
[2. 排序:](#2. 排序:)
[快速排序(Quick Sort)](#快速排序(Quick Sort))
[1. 选择基准元素:](#1. 选择基准元素:)
[2. 分割操作:](#2. 分割操作:)
[3. 递归排序:](#3. 递归排序:)
[希尔排序(Shell Sort)](#希尔排序(Shell Sort))
本文将详细介绍常见的几种排序算法,主要有以下几个算法:
冒泡排序(Bubble Sort)、插入排序(Insertion Sort)、选择排序(Selection Sort)、快速排序Quick Sort)、归并排序(Merge Sort)、堆排序(Heap Sort)、希尔排序(Shell Sort)。
冒泡排序(Bubble Sort)
一种简单的排序算法,它重复地遍历要排序的列表,比较相邻的两个元素,并按照大小顺序交换它们,直到整个列表排序完成。冒泡排序的名称来自于元素像气泡一样从列表的一端漂浮到另一端的过程。
下面是冒泡排序的详细步骤:
- 从列表的第一个元素开始,比较它与下一个元素的大小。
- 如果当前元素大于下一个元素,则交换它们的位置。
- 继续比较下一个相邻的元素,重复步骤2,直到到达列表的最后一个元素。此时,最大的元素会被交换到列表的最后位置。
- 重复步骤1-3,但是这次不包括已经排序好的最后一个元素。每次遍历都会将最大的元素放到正确的位置。
- 重复执行步骤1-4,直到整个列表排序完成。
下面是一个使用冒泡排序算法对列表进行排序的示例:
假设我们要对以下列表进行排序:[5, 2, 9, 1, 3]
第一次遍历:
比较 5 和 2,发现 5 大于 2,交换它们的位置:[2, 5, 9, 1, 3]
比较 5 和 9,发现 5 小于 9,位置不变:[2, 5, 9, 1, 3]
比较 9 和 1,发现 9 大于 1,交换它们的位置:[2, 5, 1, 9, 3]
比较 9 和 3,发现 9 大于 3,交换它们的位置:[2, 5, 1, 3, 9]
第一次遍历结束,最大的元素 9 已经被交换到了最后的位置。
第二次遍历:
比较 2 和 5,发现 2 小于 5,位置不变:[2, 5, 1, 3, 9]
比较 5 和 1,发现 5 大于 1,交换它们的位置:[2, 1, 5, 3, 9]
比较 5 和 3,发现 5 大于 3,交换它们的位置:[2, 1, 3, 5, 9]
第二次遍历结束,第二大的元素 5 已经被交换到了倒数第二的位置。
第三次遍历:
比较 2 和 1,发现 2 大于 1,交换它们的位置:[1, 2, 3, 5, 9]
比较 2 和 3,发现 2 小于 3,位置不变:[1, 2, 3, 5, 9]
第三次遍历结束,第三大的元素 3 已经被交换到了倒数第三的位置。
第四次遍历:
比较 1 和 2,发现 1 小于 2,位置不变:[1, 2, 3, 5, 9]
第四次遍历结束,第四大的元素 2 已经被交换到了倒数第四的位置。
第五次遍历:
没有发生交换,排序完成。
最终排序结果为:[1, 2, 3, 5, 9]
插入排序(Insertion Sort)
一种简单直观的排序算法,它将列表分为已排序和未排序两部分,逐步将未排序的元素插入到已排序的部分中,直到整个列表排序完成。插入排序的思想类似于我们打扑克牌时按照大小顺序将新抽到的牌插入到手中已排序的牌中。
下面是插入排序的详细步骤:
- 假设第一个元素是已排序的部分,将列表视为已排序和未排序两部分。
- 从未排序部分取出第一个元素,将其插入到已排序部分的正确位置。
- 比较新插入的元素与已排序部分的元素,找到合适的位置插入。
- 将插入位置后面的元素向右移动一个位置,为新元素腾出空间。
- 重复步骤2-4,直到未排序部分的所有元素都被插入到已排序部分。
下面是一个使用插入排序算法对列表进行排序的示例:
假设我们要对以下列表进行排序:[5, 2, 9, 1, 3]
第一次插入:
将第一个元素 5 视为已排序部分,未排序部分为 [2, 9, 1, 3]。
取出下一个元素 2,与已排序部分的元素 5 比较,2 小于 5,将 2 插入到 5 的前面,得到 [2, 5, 9, 1, 3]。
第二次插入:
已排序部分为 [2, 5],未排序部分为 [9, 1, 3]。
取出下一个元素 9,与已排序部分的元素 5 和 2 比较,9 大于 5 和 2,将 9 插入到 5 的后面,得到 [2, 5, 9, 1, 3]。
第三次插入:
已排序部分为 [2, 5, 9],未排序部分为 [1, 3]。
取出下一个元素 1,与已排序部分的元素 9、5 和 2 比较,1 小于 9、5 和 2,将 1 插入到 2 的前面,得到 [1, 2, 5, 9, 3]。
继续比较 1 和 5,1 小于 5,位置不变。
最终得到 [1, 2, 5, 9, 3]。
第四次插入:
已排序部分为 [1, 2, 5, 9],未排序部分为 [3]。
取出下一个元素 3,与已排序部分的元素 9、5 和 2 比较,3 小于 9、5 和 2,将 3 插入到 2 的前面,得到 [1, 2, 3, 5, 9]。
最终排序结果为:[1, 2, 3, 5, 9]。
插入排序的时间复杂度为O(n^2),其中n是列表的长度。在最坏的情况下,当列表是逆序排列时,插入排序的性能最差。然而,对于小型列表或部分有序的列表,插入排序通常是一种高效的排序算法。
选择排序(Selection Sort)
一种简单直观的排序算法,它将列表分为已排序和未排序两部分,每次从未排序部分选择最小(或最大)的元素,将其与未排序部分的第一个元素交换位置,逐步将最小(或最大)的元素放到已排序部分的末尾。选择排序的思想类似于我们在一组数中选择最小(或最大)的数。
下面是选择排序的详细步骤:
- 假设第一个元素是已排序的部分,将列表视为已排序和未排序两部分。
- 从未排序部分中找到最小(或最大)的元素,将其与未排序部分的第一个元素交换位置。
- 已排序部分的元素增加一个,未排序部分的元素减少一个。
- 重复步骤2-3,直到未排序部分的所有元素都被放到已排序部分。
下面是一个使用选择排序算法对列表进行排序的示例:
假设我们要对以下列表进行排序:[5, 2, 9, 1, 3]
第一次选择:
已排序部分为空,未排序部分为 [5, 2, 9, 1, 3]。
从未排序部分中找到最小的元素 1,将其与未排序部分的第一个元素 5 交换位置,得到 [1, 2, 9, 5, 3]。
第二次选择:
已排序部分为 [1],未排序部分为 [2, 9, 5, 3]。
从未排序部分中找到最小的元素 2,将其与未排序部分的第一个元素 2 交换位置,得到 [1, 2, 9, 5, 3]。
第三次选择:
已排序部分为 [1, 2],未排序部分为 [9, 5, 3]。
从未排序部分中找到最小的元素 3,将其与未排序部分的第一个元素 9 交换位置,得到 [1, 2, 3, 5, 9]。
第四次选择:
已排序部分为 [1, 2, 3],未排序部分为 [5, 9]。
从未排序部分中找到最小的元素 5,将其与未排序部分的第一个元素 5 交换位置,得到 [1, 2, 3, 5, 9]。
最终排序结果为:[1, 2, 3, 5, 9]。
选择排序的时间复杂度为O(n^2),其中n是列表的长度。无论列表的初始顺序如何,选择排序的性能都是相同的,因为每次都需要在未排序部分中找到最小(或最大)的元素。选择排序不是一种高效的排序算法,但对于小型列表来说,它的实现简单且容易理解。
归并排序(Merge Sort)
一种基于分治法的排序算法,它将列表递归地分成较小的部分,然后将这些部分按照顺序合并,最终得到一个有序的列表。归并排序的核心思想是将列表不断地分割成更小的子列表,直到每个子列表只有一个元素,然后再将这些子列表两两合并,直到最终得到完全有序的列表。
下面是归并排序的详细步骤:
- 将列表不断地分割成较小的子列表,直到每个子列表只有一个元素。
- 逐个将相邻的子列表进行合并,合并过程中按照顺序将元素放入新的临时列表中。
- 重复步骤2,直到所有的子列表都被合并成一个完全有序的列表。
下面是一个使用归并排序算法对列表进行排序的示例:
假设我们要对以下列表进行排序:[5, 2, 9, 1, 3]
第一次分割:
将列表分割成两个子列表:[5, 2] 和 [9, 1, 3]。
第二次分割:
将第一个子列表 [5, 2] 分割成两个子列表:[5] 和 [2]。
将第二个子列表 [9, 1, 3] 分割成两个子列表:[9] 和 [1, 3]。
第三次分割:
将第二个子列表 [1, 3] 分割成两个子列表:[1] 和 [3]。
开始合并:
将两个子列表 [5] 和 [2] 合并成有序的子列表 [2, 5]。
将两个子列表 [9] 和 [1, 3] 合并成有序的子列表 [1, 3, 9]。
继续合并:
将两个子列表 [2, 5] 和 [1, 3, 9] 合并成完全有序的列表 [1, 2, 3, 5, 9]。
最终排序结果为:[1, 2, 3, 5, 9]。
归并排序的时间复杂度为O(nlogn),其中n是列表的长度。归并排序是一种稳定的排序算法,它的主要优点是能够处理大规模的数据集,并且对于任何输入数据都能保证最坏情况下的时间复杂度。然而,归并排序需要额外的存储空间来存储临时列表,因此在空间复杂度方面略高于其他排序算法。
堆排序(Heap Sort)
一种基于二叉堆数据结构的排序算法。它利用了堆的特性来进行排序,具体来说,堆排序首先将待排序的列表构建成一个最大堆(或最小堆),然后依次将堆顶元素与最后一个元素交换,再调整堆使其满足堆的性质,重复这个过程直到整个列表有序。
下面是堆排序的详细步骤:
- 构建最大堆(或最小堆):将待排序的列表看作是一个完全二叉树,从最后一个非叶子节点开始,依次向前进行以下操作:
- 将当前节点与其子节点进行比较,将较大(或较小)的子节点与当前节点交换位置。
- 重复上述操作直到当前节点满足堆的性质(即父节点的值大于(或小于)其子节点的值)。
- 继续向前处理下一个非叶子节点,直到根节点。
- 这样就能够构建出一个最大堆(或最小堆)。
- 排序:构建完最大堆(或最小堆)后,将堆顶元素(即根节点)与最后一个元素交换位置,然后将堆的大小减1。交换后,最后一个元素就是当前堆的最大(或最小)值。然后对剩余的元素进行堆调整,使其满足堆的性质。重复这个过程,直到堆的大小为1,排序完成。
下面是一个使用堆排序算法对列表进行升序排序的示例:
假设待排序的列表为:[8, 5, 2, 9, 1, 7, 6, 3]
1. 构建最大堆:
从最后一个非叶子节点开始,依次向前进行堆调整操作:
节点3与其子节点9进行比较,交换位置,得到[8, 5, 9, 3, 1, 7, 6, 2]
节点2与其子节点7进行比较,交换位置,得到[8, 5, 9, 7, 1, 3, 6, 2]
节点1与其子节点3进行比较,交换位置,得到[8, 5, 9, 7, 3, 1, 6, 2]
继续向前处理根节点,节点8与其子节点9进行比较,无需交换位置,得到最大堆:[9, 8, 5, 7, 3, 1, 6, 2]
2. 排序:
将堆顶元素9与最后一个元素2交换位置,得到[2, 8, 5, 7, 3, 1, 6, 9]
将堆的大小减1,对剩余的元素进行堆调整,得到最大堆:[8, 7, 5, 6, 3, 1, 2]
再次将堆顶元素8与最后一个元素2交换位置,得到[2, 7, 5, 6, 3, 1, 8]
将堆的大小减1,对剩余的元素进行堆调整,得到最大堆:[7, 6, 5, 2, 3, 1]
重复上述步骤,直到堆的大小为1,得到最终的排序结果:[1, 2, 3, 5, 6, 7, 8, 9]
堆排序的时间复杂度为O(nlogn),其中n是待排序列表的长度。堆排序是一种原地排序算法,不需要额外的空间,但由于其对内存的访问方式不连续,可能对缓存产生较大的压力。
快速排序(Quick Sort)
一种常用的高效排序算法,基于分治思想。它通过选择一个基准元素,将列表分割成两个子列表,其中一个子列表的元素都小于基准元素,另一个子列表的元素都大于基准元素。然后递归地对这两个子列表进行排序,最终得到有序的列表。
下面是快速排序的详细步骤:
- 选择基准元素:
从待排序的列表中选择一个元素作为基准元素(通常选择第一个或最后一个元素)。
- 分割操作:
通过一趟排序将列表分割成两个子列表,使得左子列表的元素都小于基准元素,右子列表的元素都大于基准元素。具体操作如下:
设置两个指针,一个指向列表的起始位置(通常称为"low"),另一个指向列表的末尾位置(通常称为"high")。
将基准元素与列表的起始位置元素交换位置,即将基准元素放在列表的起始位置。
从"high"指针开始向前搜索,找到第一个小于基准元素的元素,将其与"low"指针指向的元素交换位置。
从"low"指针开始向后搜索,找到第一个大于基准元素的元素,将其与"high"指针指向的元素交换位置。
重复以上两步,直到"low"和"high"指针相遇。
将基准元素与"low"指针指向的元素交换位置,此时基准元素左边的元素都小于它,右边的元素都大于它。
- 递归排序:
对基准元素左边的子列表和右边的子列表分别进行递归排序,直到子列表的长度为1或0,即递归终止条件。
下面是一个使用快速排序算法对列表进行排序的示例(以升序排序为例):
假设待排序的列表是 [7, 2, 1, 6, 8, 5, 3, 4]。
1. 选择基准元素:
选择第一个元素7作为基准元素。
2. 分割操作:
列表变为 [4, 2, 1, 6, 8, 5, 3, 7]。
"low"指针指向4,"high"指针指向7。
从"high"指针开始向前搜索,找到3,将3与4交换位置,列表变为 [3, 2, 1, 6, 8, 5, 4, 7]。
从"low"指针开始向后搜索,找到6,将6与7交换位置,列表变为 [3, 2, 1, 6, 8, 5, 4, 6]。
重复以上两步,直到"low"和"high"指针相遇,将基准元素7与"low"指针指向的元素6交换位置,列表变为 [3, 2, 1, 6, 4, 5, 7, 8]。
此时基准元素左边的元素都小于它,右边的元素都大于它。
3. 递归排序:
对基准元素左边的子列表 [3, 2, 1, 6, 4, 5] 进行递归排序。
对基准元素右边的子列表 [7, 8] 进行递归排序。
继续递归排序,直到子列表的长度为1或0。
最终,经过递归排序,列表变为 [1, 2, 3, 4, 5, 6, 7, 8],完成了排序。
希尔排序(Shell Sort)
一种改进的插入排序算法,也被称为缩小增量排序。它通过将列表分割成多个子列表来进行排序,然后逐步减小子列表的长度,最终完成整个列表的排序。希尔排序的核心思想是通过较大的步长进行初步排序,然后逐渐缩小步长进行更细致的排序。
下面是希尔排序的详细步骤:
- 选择一个合适的步长序列:希尔排序的关键在于选择合适的步长序列。常用的步长序列有希尔增量序列(n/2,n/4,n/8...)和Hibbard增量序列(2^k-1,2^(k-1)-1...),其中n为列表长度。步长序列的选择会影响希尔排序的性能。
- 根据步长进行分组:根据选定的步长,将列表分割成多个子列表,每个子列表包含距离为步长的元素。对每个子列表进行插入排序。
- 逐步缩小步长:重复上述步骤,每次缩小步长,直到步长为1。
- 最后一次插入排序:当步长为1时,进行最后一次插入排序,将列表中的元素放置在正确的位置上。
下面是一个希尔排序的例子,以步长序列为希尔增量序列(n/2)进行说明:
假设有一个待排序列表:[9, 5, 7, 3, 2, 8, 1, 6, 4]
首先
根据步长2将列表分割成两个子列表:[9, 7, 2, 1, 4]和[5, 3, 8, 6]
对每个子列表进行插入排序:
对子列表[9, 7, 2, 1, 4]进行插入排序,得到[1, 2, 4, 7, 9]
对子列表[5, 3, 8, 6]进行插入排序,得到[3, 5, 6, 8]
此时,列表变为:[1, 2, 4, 7, 9, 3, 5, 6, 8]
接下来
根据步长1进行最后一次插入排序:
对整个列表[1, 2, 4, 7, 9, 3, 5, 6, 8]进行插入排序,得到最终的有序列表:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
通过希尔排序,列表成功地被排序成升序。需要注意的是,希尔排序的性能与步长序列的选择有关,不同的步长序列可能导致不同的性能表现。