目录
这里用Python实现了简单的7种排序,还有三种可以参考下面文章
https://www.cnblogs.com/onepixel/p/7674659.html
https://www.jianshu.com/p/7d037c332a9d
https://blog.youkuaiyun.com/shan9liang/article/details/7555811
冒泡排序
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。(n-1)
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(list):
for i in range(0, len(list)-1):
for j in range(0, len(list)-1-i):
if list[j] > list[j+1]:
list[j], list[j+1] = list[j+1], list[j]
if __name__ == "__main__":
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
bubble_sort(li)
print(li)
li2 = [54, 226, 93, 17, 77, 31, 44, 55, 20]
bubble_sort(li2)
print(li2)
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 26, 31, 44, 54, 55, 77, 93]
# [17, 20, 31, 44, 54, 55, 77, 93, 226]
#
# Process finished with exit code 0
时间复杂度
- 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
def selection_sort(list):
n = len(list)
for i in range(0,n-1):
min_index = i
for j in range(i+1,n):
if list[j] < list[min_index]:
min_index = j
if i != min_index:
list[i], list[min_index] = list[min_index], list[i]
if __name__ == "__main__":
li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
selection_sort(li)
print(li)
li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
selection_sort(li2)
print(li2)
#
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
#
# Process finished with exit code 0
红色表示当前最小值,黄色表示已排序序列,蓝色表示当前位置。
外层遍历n-1,每次都选择list头部位子默认是最小值,然后内存循环从外层的i+1开始比较,通过临时变量记录最小值小标,然后和每一次的list头元素比较,index不同,就交换,排序从左到右,从小到大,左边不变,右边进行排序比较
时间复杂度
- 最优时间复杂度:O(n2)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定(考虑升序每次选择最大的情况)
插入排序
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
插入排序和上面的选择排序是差不多的,插入操作的是左边的有序队列,而选择操作的是右边的无序队列
def insert_sort(list):
n = len(list)
for i in range(1,n):
for j in range(i,0,-1):
if list[j] < list[j-1]:
list[j], list[j-1] = list[j-1], list[j]
if __name__ == "__main__":
li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
insert_sort(li)
print(li)
li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
insert_sort(li2)
print(li2)
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
#
# Process finished with exit code 0
时间复杂度
- 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
快速排序
快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
步骤为:
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
def quick_sort(list, start, end):
if start >= end:
return
low = start
high = end
mid = list[start]
while low < high:
while low < high and list[high] >= mid:
high -= 1
list[low] = list[high]
while low < high and list[low] < mid:
low += 1
list[high] = list[low]
list[low] = mid
quick_sort(list, start, low-1)
quick_sort(list, low+1, end)
if __name__ == "__main__":
li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
quick_sort(li,0,len(li)-1)
print(li)
li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
quick_sort(li2,0,len(li)-1)
print(li2)
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
#
# Process finished with exit code 0
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定
从一开始快速排序平均需要花费O(n log n)时间的描述并不明显。但是不难观察到的是分区运算,数组的元素都会在每次循环中走访过一次,使用O(n)的时间。在使用结合(concatenation)的版本中,这项运算也是O(n)。
在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。
希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序过程
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然后我们对每列进行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之后变为:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最后以1步长进行排序(此时就是简单的插入排序了)
def shell_sort(list):
n = len(list)
gap = n // 2
while gap > 0:
for i in range(gap, n):
for j in range(i, 0, -gap):
if list[j] < list[j - gap]:
list[j], list[j - gap] = list[j - gap], list[j]
gap //= 2
if __name__ == "__main__":
li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
shell_sort(li)
print(li)
li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
shell_sort(li2)
print(li2)
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
#
# Process finished with exit code 0
时间复杂度
- 最优时间复杂度:根据步长序列的不同而不同
- 最坏时间复杂度:O(n2)
- 稳定想:不稳定
归并排序
归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
def merge_sort(list):
if len(list) <= 1:
return list
# 二分分解
num = len(list)//2
left = merge_sort(list[:num])
right = merge_sort(list[num:])
# 合并
return merge(left, right)
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 += left[l:]
result += right[r:]
return result
if __name__ == "__main__":
li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
print(merge_sort(li))
print("原始数组"+str(li))
li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
print(merge_sort(li2))
print("原始数据"+str(li2))
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# 原始数组[54, 26, 93, 17, 22, 31, 44, 55, 20]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 原始数据[54, 226, 93, 17, 77, 31, 100, 55, 20]
#
# Process finished with exit code 0
时间复杂度
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(nlogn)
- 稳定性:稳定
堆排序
堆的概念
堆:本质是一种数组对象。特别重要的一点性质:<b>任意的叶子节点小于(或大于)它所有的父节点</b>。对此,又分为大顶堆(这里来做排序)和小顶堆(计算top k算法问题),大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。
利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。
步骤1:建立大顶堆 参数1 list 参数2 建堆list长度 建堆需要调整的节点
如下序列 1 3 4 2 7 9 8 10 16 14 建立大顶堆后,这里可以看下面的代码理解,无非就是通过循环把让根节点一定是当先序列的最大值
步骤2:取出大顶堆根节点和序列最后的元素进行交换
交换之后,序列最后的值肯定是最大值,但是此时这个堆就不再是我们的大顶堆了
步骤3:交换一次,序列长度动态调整为n-1(操作一次就-1)
继续执行建堆代码,把n-1的序列调整为大顶堆
步骤4:循环步骤2和步骤3即可
def heap_sort(list):
# 构建大顶堆,保证最大值位于叶节点,并且父节点的值大于子节点
build_max_heap(list)
# 当前堆中序列长度 初始化为序列的长度
i = len(list)
# 每次取出堆顶元素置于序列的最后
# 然后修改大顶堆的长度,重新调整堆,这里length持续减少,i由于是头部换到尾部,我们传0进去就可以重新建堆
while i > 0:
list[i - 1], list[0] = list[0], list[i - 1]
i -= 1
# 这里由于已经在开始的时候建了大顶堆,每次拿出根节点最大值后不需要循环建堆,只要调整根节点0下标下修改的节点
adjust_max_heap(list, i, 0)
# 构建大顶堆
def build_max_heap(list):
length = len(list)
# 第一次根据无序序列,建立大顶堆需要循环进行多次建堆 多个节点需要调整,就要循环全部
for num in range((length-1) // 2,-1,-1):
adjust_max_heap(list,length,num)
# 调整大顶堆
#list:待调整序列 length: 序列长度 i:需要调整的结点
def adjust_max_heap(list, length, i):
# 定义一个int值保存当前最大值的下标
largest = i
# 执行循环操作 1、寻找最大值下标 2、最大值与父节点交换
while 1:
# 获取左右子节点的下标
left, right = LEFT(i), RIGHT(i)
# 当前左子节点的下标小于序列长度,并且左叶子节点值大于父节点,把最大值下标赋值
if left < length and list[left] > list[i]:
largest = left
else:
largest = i
if right < length and list[right] > list[largest]:
largest = right
# 如果值不等,说明当前节点的父节点不是最大值,需要交换值
if largest != i:
list[i], list[largest] = list[largest], list[i]
# 这里交换之后,需要把交换的子节点重新作为调整节点进行堆调整
i = largest
continue
else:
break
# 左节点下标
def LEFT(i):
return 2*i + 1
# 右节点下标
def RIGHT(i):
return 2*i + 2