机试中,排序算法是主要面临的一类算法,很久都没有接触机试的题了,解决的时候感觉有点思路不是很清楚了,因此写了这一片博客,来整理下常见的排序算法以及各种常见算法的效率稳定性等特点。
在机试中常用的排序算法主要包含下面几种:
1.插入排序
2.选择排序
3.快速排序(最常用的排序)
4.冒泡排序
5.归并排序
6.桶排序
下面我将具体介绍各种排序算法的一些特点:
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 |
---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(1) | 不稳定 |
直接插入排序 | O(n2) | O(n2) | O(1) | 稳定 |
希尔排序 | O(n2) | O(O3/2) | ||
归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(n2) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
时间复杂度辅助记忆:
- 冒泡、选择、直接 排序需要两个for循环,每次只关注一个元素,平均时间复杂度为O(n2)(一遍找元素O(n),一遍找位置O(n))
- 快速、归并、希尔、堆基于二分思想,log以2为底,平均时间复杂度为O(nlogn)(一遍找元素O(n),一遍找位置O(logn))
1.插入排序
每次从头到尾选择一个元素,并且将这个元素和整个数组中的所有已经排序的元素进行比较,然后插入到合适的位置。
注意:插入排序的核心点就是两两比较时从后向前进行比较,如果比插入值大,那么将其向后移动,直到找到比插入值小的。
1 | def insertion_sort(arr): |
稳定性:稳定
时间复杂度:O(n^2)
空间复杂度:O(1)
为什么插入排序是稳定的排序算法?
当前从头到尾选择元素进行排序时,当选择到第i个元素时,前i-1个元素已经排好了续,取出第i个元素,从i-1开始向前开始比较,如果小于,则将该位置元素向后移动,继续先前的比较,如果不小于,那么将第i个元素放在当前比较的元素之后。
2.选择排序
选择排序主要采用了从头到尾依次确定各个位置的方式来进行排序,首先遍历一次整个数组,如果遇到比第一个元素小的元素那么交换位置,一次遍历完成那么第一个位置就已经是整个数组中最小的元素了,经过n次遍历,确定全部位置的元素。
1 | def selection_sort(arr): |
稳定性:不稳定
时间复杂度:O(n^2)
空间复杂度:O(1)
3.冒泡排序
冒泡排序额是实现是不停地进行两两比较,将较大的元素换到右侧,然后继续进行两两比较,直到比较完全全部元素,每进行完一轮两两比较,确定一个元素的位置。例如:第一轮两两比较确定最大的值,第二轮比较确定次大元素。
1 | def bubble_sort(arr): |
稳定性:稳定
时间复杂度:O(n^2)
空间复杂度:O(1)
冒泡排序在原始冒泡排序算法的基础上还能做哪些优化?
1.设置是否已经排好序的flag。如果在某一轮的便利中没有出现任何的交换发生,这说明已经都排好序,那么直接将flag置True,每轮结束时检测flag,如果为True则直接返回
2.某一轮的结束为止为j,但这一轮最后一次交换发生在lastSwap位置,那么说明lastSwap到j之间已经排好序,下次遍历的结束点就不需要再到j—而是直接到lastSwap即可。
4.希尔排序
希尔排序是一种插入排序的改良算法,简单的插入排序不管元素怎么样,都从头到尾一步一步的进行元素比较,如果遇到逆序序列如:[5,4,3,2,1,0]数组末端的0要回到原始位置需要n-1次的比较和移动。而希尔排序使用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后在各个组内进行插入排序,随后逐步缩小增量,继续按照组进行排序,直至增量为1。
希尔排序通过这种策略使的整个数组在初始阶段宏观上基本有序,小的基本在前,大的基本在后,然后缩小增量相当于进行微调,不会过多的设计元素移动。
基本思想:把记录按照下标的一定增量进行分组,对每组使用直接插入排序算法进行排序;随着增量逐渐减少,魅族包含的元素个数越来越多,当增量减至1时,整个文件被分成一组,算法终止。
稳定性:不稳定
平均时间复杂度:O()
最坏时间复杂度 : O()
空间复杂度:O( )
5.快速排序
快速排序的的主要思想是先找到一个任意一个元素作为基准元素pivot(一般都采用第一个元素作为基准),然后从右向左搜索,如果发现比pivot小,那么和pivot交换,然后从右向左进行搜索,如果发现比pviot大,那么进行交换,遍历一轮后pivot左边的元素都比它小,右边的元素都比他大,此时pivot的位置就是排好序后他也应该在的位置。然后继续用递归算法分别处理pivot左边的元素和右边的元素。
对于大的乱序数据快速排序被认为是最快速的排序方式
1 | #方式一:递归 |
1 | #方式二:非递归,利用栈 |
稳定性:不稳定(排序过程中不停地交换元素位置造成了排序算法不稳定)
时间复杂度:
平均时间O(nlogn)
最坏情况:O(n^2)
空间复杂度:O(nlogn)
6.归并排序
该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
1 | def MergeSort(lists): |
7.堆排序
见堆排序