
本文共 8935 字,大约阅读时间需要 29 分钟。
目录
这里用Python实现了简单的7种排序,还有三种可以参考下面文章
冒泡排序
冒泡排序(英语: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 8225 59 94 65 2345 27 73 25 3910
然后我们对每列进行排序:
10 14 73 25 2313 27 94 33 3925 59 94 65 8245
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
10 14 7325 23 1327 94 3339 25 5994 65 8245
排序之后变为:
10 14 1325 23 3327 25 5939 65 7345 94 8294
最后以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 //= 2if __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 resultif __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
发表评论
最新留言
关于作者
