1、稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
2、非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
3、原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
4、非原地排序:需要利用额外的数组来辅助排序
排序算法的时间复杂度
常用排序
1、快速排序
我们从数组中选择一个元素,我们把这个元素称之为中轴元素吧,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们无需再移动中轴元素的位置。
从中轴元素那里开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的大小为1,此时每个元素都处于有序的位置。
2、插入排序
当我们给无序数组做排序的时候,为了要插入元素,我们需要腾出空间,将其余所有元素在插入之前都向右移动一位,这种算法我们称之为插入排序。
过程简单描述:
1、从数组第2个元素开始抽取元素。
2、把它与左边第一个元素比较,如果左边第一个元素比它大,则继续与左边第二个元素比较下去,直到遇到不比它大的元素,然后插到这个元素的右边。
3、继续选取第3,4,….n个元素,重复步骤 2 ,选择适当的位置插入。
3、归并排序
将一个大的无序数组有序,我们可以把大的数组分成两个,然后对这两个数组分别进行排序,之后在把这两个数组合并成一个有序的数组。由于两个小的数组都是有序的,所以在合并的时候是很快的。
通过递归的方式将大的数组一直分割,直到数组的大小为 1,此时只有一个元素,那么该数组就是有序的了,之后再把两个数组大小为1的合并成一个大小为2的,再把两个大小为2的合并成4的 …… 直到全部小的数组合并起来。
4、堆排序
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
- 堆属性
在最大堆中,父节点的值比每一个子节点的值都要大。
在最小堆中,父节点的值比每一个子节点的值都要小。
这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
最大堆总是将其中的最大值存放在树的根节点
最小堆根节点中的元素总是树中的最小值。
- 堆和普通树的区别:
二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。
堆中,在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。
用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间上都是很高效的。
数组[ 10, 7, 2, 5, 1 ],可以构造最大堆。(数组不是完全有序)
数组[ 10, 14, 25, 33, 81, 82, 99 ],可以构造最小堆。(数组完全有序)
一个从高到低有序排列的数组是一个有效的最大堆。
注意:并不是每一个最小堆都是一个有序数组!要将堆转换成有序数组,需要使用堆排序。
有两个原始操作用于保证插入或删除节点以后堆是一个有效的最大堆或者最小堆:
- shiftUp(): 如果一个节点比它的父节点大(最大堆)或者小(最小堆),那么需要将它同父节点交换位置。这样是这个节点在数组的位置上升。
- shiftDown(): 如果一个节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动。这个操作也称作“堆化(heapify)”。
5、二分查找
def binary_search(nums, target):
low = 0
high = len(nums)
while low <= high:
mid = (low+high)//2
if nums[mid] == target:
return 'target in nums'
elif nums[mid] < target:
low = mid + 1
else:
high = mid - 1
return 'target not in nums'
nums = [2, 4, 7, 8, 9, 10, 13, 15, 19]
print(binary_search(nums, 15))
print(binary_search(nums, 14))