排序算法
快速排序
tips:为什么快速排序算法把基准元素名称定义为pivot
在快速排序算法中,“pivot
”(基准元素)是一个用来将数据集分割成两部分的元素。所有比基准元素小的元素都被放到它的左边,而所有比它大的元素都被放到它的右边。这就是为什么它被称为
“pivot
”(枢轴),因为它在排序过程中起到了中心轴的作用,就像一个旋转门或者天平的支点那样。
在一次快速排序的分割操作中,我们从数组的一端开始,将所有比pivot
小的元素放到左边,比pivot
大的元素放到右边。这个过程称为分区操作(partitioning
)。经过分区操作后,pivot
元素会位于数组的某个位置,它左边的所有元素都不大于它,它右边的所有元素都不小于它,所以它就到了排序后应该在的位置。
然后我们可以递归地对pivot
左边的元素和右边的元素分别进行快速排序,这样整个数组就会变得有序。
所以,pivot
元素在快速排序中起到了关键的作用,它是算法的核心部分。
代码实现
以下是一个C++的快速排序实现示例:
1 |
|
在这个程序中,partition
函数是快速排序的核心,它实现了一次划分操作。我们首先选择一个基准元素(这里选择的是数组最右侧的元素),然后将所有比基准元素小的元素移动到数组的左侧,比基准元素大的元素移动到数组的右侧。
quickSort
函数是一个递归函数,它首先调用partition
函数进行一次划分操作,然后对基准元素左侧和右侧的子数组分别递归调用quickSort
函数进行排序。这个过程会一直递归下去,直到子数组的大小为1或0,此时子数组已经是有序的,递归结束。
在实现代码时犯了一个小错误
在partition
函数实现时,最后一步的交换过程中,应该是swap(arr[i+1], arr[high])
,而我第一次实现时写成了swap(arr[i+1], pivot)
。为什么这样不行呢?貌似看起来是合理的,因为我们在一开始就有语句:int pivot = arr[high];
但是恰巧问题就是出现在了这里,因为我们要交换的是vector
容器里的元素,我们在实现int pivot = arr[high];
这个语句时pivot
只是拿到了arr[high]
的值,因此我们并没有实际交换arr[i+1]
和arr[high]
,所以排序后的结果肯定是错的。(因此,定义成这样int& pivot = arr[high]
,就可以这样写:swap(arr[i+1], pivot)
)
堆排序
堆排序是一种基于堆的排序算法。它使用了一种叫做堆的数据结构。堆有两种类型:最大堆和最小堆。最大堆的特性是父节点的值大于或等于其所有子节点的值,最小堆的特性是父节点的值小于或等于其所有子节点的值。
堆排序的基本步骤如下:
- 建立最大堆:将待排序序列构造成一个最大堆,这样就能保证整个序列的最大值就是堆顶的根节点。
- 交换数据:将根节点与最后一个元素交换位置,然后断开(排除)最后一个元素。
- 重建最大堆:通过调整使剩余元素重新构成最大堆。
- 重复步骤2~3,直到整个序列有序。
这里挂一个讲堆排序很好的博客,一定要耐心阅读!引用链接
堆排序c++实现
1 |
|
自己初步实现时没有彻底弄明白堆排序,所以有如下这个问题
为什么第一次需要
1 | for (int i = n / 2 - 1; i >= 0; --i) { |
而从i=n-1
开始,就直接
heapify(arr, i, 0);
就没有i=n/2-1
的这个过程
这是因为我们在开始的时候是在创建最大堆。在创建最大堆的过程中,我们需要从最后一个非叶子节点(即
n / 2 - 1
)开始,然后向前遍历到根节点(即
0
),对每个节点进行下沉操作,确保其满足最大堆的性质。这是创建最大堆的过程。
然后,我们进入排序的步骤,每次将当前最大的元素(即堆顶元素)与当前堆的最后一个元素交换,然后断开最后一个元素(即排除最后一个元素,使堆的大小减小1),然后再将新的堆顶元素进行下沉操作,确保剩余元素还是一个最大堆。这个过程一直持续到整个堆的元素都被排除,即整个数组都被排序。
因此,在排序的过程中,我们不需要再从 n / 2 - 1
开始了,因为除了堆顶元素以外,其他元素都满足最大堆的性质,所以我们只需要将新的堆顶元素进行下沉操作即可,也就是
heapify(arr, i, 0);
。