十大经典排序算法
插入排序
代码实现
1 | void InsertionSort(vector<int> &arr, int len){ |
分析
- 时间复杂度:最好情况下数组已经有序,此时复杂度为O(n);最坏情况下数组为倒序,此时复杂度为O(n^2)。
- 空间复杂度:原地排序算法,没有额外内存消耗。
- 稳定性:只有遇到比当前元素大的才需要把它们移动到后面,所以是稳定排序。(注:我们分析稳定性有一定的实际意义。如果对有多个属性的复杂元素排序,使用稳定排序可以使我们对其中一个属性排序时不影响其它属性。)
选择排序
代码实现
1 | void SelectionSort(vector<int> &arr, int len){ |
分析
- 时间复杂度:最好和最坏情况下都需要遍历未排序区间,所以时间复杂度都是O(n^2)。
- 空间复杂度:没有额外的内存消耗。
- 稳定性:不稳定,因为每次都要在未排序区间找到最小值和前面的值交换,这样前面的值可能会被换到和它相等的值的后面(比如,232154 -> 132254)。
冒泡排序
代码实现
1 | void BubbleSort(vector<int> &arr, int len){ |
分析
- 时间复杂度:最好情况下,内层循环过一遍发现没有元素发生交换,直接结束,复杂度为O(n)。最坏情况下复杂度为O(n^2),平均复杂度也是O(n^2)。
- 空间复杂度:没有用到额外空间。
- 稳定性:在元素相等的情况下,我们不予交换,因此冒泡排序是稳定排序。
归并排序
代码实现
1 | void Merge(vector<int> &arr, int left, int mid, int right){ |
分析
- 时间复杂度:O(nlogn),需要数学推导。
- 空间复杂度:需要分配临时数组tmp,空间复杂度为O(n)。
- 稳定性:当需要归并的左右数组中有元素相同时,我们可以保证优先放入左边数组的元素,所以是稳定排序。
快速排序
代码实现
1 | int Partition(vector<int> &arr, int left, int right){ |
分析
- 时间复杂度:如果每次分区都能把数组分成大小一样的区间,那么时间复杂度是O(nlogn),但是最坏情况下可能退化成为O(n^2)。
- 空间复杂度:没有额外内存消耗。
- 稳定性:不是稳定排序。
计数排序
代码实现
1 | vector<int> CountSort(vector<int> &arr){ |
分析
- 时间复杂度:O(n+k),k就是maxValue-minValue+1。
- 空间复杂度:O(n+k),与时间复杂度相同。
- 稳定性:稳定,见代码。
桶排序
代码实现
1 | void BucketSort(vector<int>& arr) { |
分析
- 时间复杂度:如果要排序的数据有n个,我们把它们分在m个桶中,这样每个桶里的数据就是k = n/m。每个桶内排序(假设用快排)的时间复杂度就为O(k*logk)。m个桶就是
m*O(klogk)=m*O((n/m)*log(n/m))=O(n*log(n/m))。当桶的个数m接近数据个数n时,log(n/m)就是一个较小的常数,所以时间复杂度接近O(n)。但是如果运气不好,所有的元素都到了一个桶了,那么它的时间复杂度就退化成 O(nlogn) 了。 - 空间复杂度:O(n+m),其中n为待排序元素的个数,m为桶的数量。
- 稳定性:取决于桶内排序使用的算法。
基数排序
代码实现
1 | void RadixSort(vector<int>& arr) |
分析
- 时间复杂度:基数排序的时间复杂度是O(k*n),其中n是排序的元素个数,k是元素中最大元素的位数。
- 空间复杂度:O(n+m),n为tmp数组的长度,m为count数组的长度。
- 稳定性:是稳定的,原因同计数排序。
希尔排序
代码实现
1 | void ShellSort(vector<int>& arr) { |
分析
- 时间复杂度:和步长序列有关,以上代码展示序列的时间复杂度为O(n^(1.3-2))。
- 空间复杂度:原地排序,没有额外空间消耗。
- 稳定性:将数据分组并存在元素的交换,所以不稳定。
堆排序
代码实现
1 | void Heapify(vector<int> &arr, int n, int i){ |
分析
- 时间复杂度:在所有情况下均为O(nlogn)。
- 空间复杂度:没有用到额外空间。
- 稳定性:堆排序是不稳定的,因为堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。