文字转载于https://blog.csdn.net/weixin_41571493/article/details/81875088
代码转载于https://blog.csdn.net/sensev/article/details/80908776
参考https://blog.csdn.net/qq_39207948/article/details/80006224
参考https://www.cnblogs.com/xiugeng/p/9645972.html
参考https://www.jianshu.com/p/86c2375246d7
十大排序算法
十种常见排序算法可以分为两大类:
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序;
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
相关概念:
稳定:如果a原本在b的前面,而a=b,排序之后a仍然在b的前面
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面
时间复杂度:对排序数据的总的操作次数,反映当n变化时,操作次数呈现什么规律
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数
1、冒泡排序(Bubble Sort)
相邻的两个元素进行比较,然后把较大的元素放到后面(正向排序),
在一轮比较后,最大的元素就放在了最后一个位置,
像鱼在水中吐的气泡在上升过程中不断变大)。
每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)
额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)
def bubble_sort(alist):
length = len(alist)
for i in range(length-1):
# i 表示比较多少轮
for j in range(length-i-1):
# j表示每轮比较的元素范围,因为每比较一轮就会排序好一个元素的位置
# 所以在下一轮比较的时候就少比较了一个元素,所以要减去i
if alist[j] > alist[j+1]:
alist[j], alist[j+1]= alist[j+1], alist[j]
return alist
2、快速排序(Quick Sort)
1、从数列中挑出一个元素,称为“基准”
2、重新排列数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边),这个称为分区操作(partition)。
3、递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
快速排序基于选择划分,是简单选择排序的优化。
每次划分将数据选到基准值两边,循环对两边的数据进行划分,类似于二分法。
def quick_sort(alist, start, end):
if start >= end:
return
low = start
high = end
mid = alist[low]
while low < high:
while low < high and mid < alist[high]:
# 从右边开始找,如果元素小于基准,则把这个元素放到左边
high -= 1
alist[low] = alist[high]
while low < high and mid > alist[low]:
# 从左边开始找,如果元素大于基准,则把元素放到右边
low += 1
alist[high] = alist[low]
# 循环退出,low==high,把基准元素放到这个位置
alist[low] = mid
# 递归调用,重新排列左边的和右边的序列
quick_sort(alist, start, low-1)
quick_sort(alist, low+1, end)
因为序列被切分为两部分,如果切分了n次把元素切割完,序列的长度为m,则2^n=m,所以切割完的时间复杂度为log(n),在每次切割都会比较所有的元素,需要n次,所以整体的时间复杂度O(nlogn)。
额外空间开销出在暂存基准值,O(logn)次划分需要O(logn)个,空间复杂度O(logn)。
3、插入排序(Insert Sort)
1、从第一个元素开始,该元素可以认为已经被排序;
2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
3、如果该元素(已排序)大于新元素,就将该元素移到下一位置;
4、重复步骤3,直至找到已排序的元素小于或者等于新元素的位置;
5、将新元素插入到该位置后;
6、重复步骤2~5。
简单插入排序同样操作n-1轮,每轮将一个未排序数插入已排序序列。
每轮操作O(n),共O(n)轮,时间复杂度O(n^2)
额外空间开销出在数据移位时那一个过渡空间,空间复杂度O(1)。
def insert_sort(alist):
for i in range(1, len(alist)):
# 从第二个元素开始,每次取出一个元素,插入前面的序列使其有序
for j in range(i, 0, -1):
if alist[j] < alist[j - 1]:
alist[j], alist[j - 1] = alist[j - 1], alist[j]
return alist
4、希尔排序(Shell Sort)
希尔排序是插入排序的一种。也称缩小增量排序。
1、先将整个待排序序列按照增量分割成若干子序列,分别进行直接插入排序
(每个分组分别进行插入排序,每个分组就变成有序(整体不一定))
2、然后缩小增量为上个增量的一半,对每个分组进行插入排序
def shell_sort(alist):
n = len(alist)
# 初始步长
gap = n / 2
while gap > 0:
# 按步长进行插入排序
for i in range(gap, n):
j = i
# 插入排序
while j >= gap and alist[j - gap] > alist[j]:
alist[j - gap], alist[j] = alist[j], alist[j - gap]
j -= gap
# 得到新的步长
gap = gap / 2
5、选择排序(Select Sort)
第一轮,所有的元素都和第一个元素进行比较,如果比第一个元素大,就和第一个元素进行交换,在这轮比较完成后,就找到了最小的元素;
第二轮,所有的元素都和第二个元素进行比较,找出第二个位置的元素,以此类推
每遍历一次排好一个元素,而每一次都会比较所有的元素,所以时间复杂度为O(n^2)
def selection_sort(alist):
length = len(alist)
for i in range(length - 1, 0, -1):
for j in range(i):
if alist[j] > alist[i]:
alist[j], alist[i] = alist[i], alist[j]
6、 堆排序(Heap Sort)
堆,是一种特殊的完全二叉树。
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大;
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小。
1、首先,将待排序的数组构造出一个大根堆
2、取出这个大根堆的堆顶节点(最大值),与堆的最下最右的元素进行交换,然后把剩下的元素再构造出一个大根堆
3、重复第2步,直到这个大根堆的长度为1,此时完成排序
堆常应用于topk问题:
现有n个数,设计算法得到前k大的数,(k<n)。常用于实现网站热搜榜等。
思路:
取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数(最小的数)。
依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行依次调整。
7、 归并排序(Merge Sort)
分治法(divide and conquer)
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序
3.将两个排序好的子序列合并成一个最终的排序序列
def merge(left, right):
l, r = 0, 0
result = []
while l < len(left) and r < len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result.append(left[l:])
result.append(right[r:])
return result
def merge_sort(alist):
if len(alist) <= 1:
return
num = len(alist) / 2
# 划分
left = merge_sort(alist[:num])
right = merge_sort(alist[num:])
# 合并
return merge(left, right)
8、 桶排序(Bucket Sort)
1、假设数据在[min,max]之间均匀分布,其中min/max分别指数据中的最小值和最大值。那么将区间[min,max]等分成n份,这n个区间便称为n个桶
2、将数据加入对应的桶中,然后每个桶内单独排序
3、由于桶之间有大小关系,因此可以从大到小(或从小到大)将桶中元素放入到数组中
9、 计数排序(Counting Sort)
1、根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
2、遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
3、对额外空间内数据进行计算,得出每一个元素的正确位置;
4、将待排序集合每一个元素移动到计算得出的正确位置上
10、 基数排序(Radix Sort)
基数排序是桶排序的扩展。
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串和特定格式的浮点数,所以基数排序也不是只能使用于整数。
1、取得数组中的最大数,并取得位数;
2、arr为原始数组,从最低位开始取每个位组成radix数组;
3、对radix数组进行计数排序(利用计数排序适用于小范围数的特点)
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;