十大排序算法
**$O(n^2)
平
方
阶
排
序
:
∗
∗
冒
泡
排
序
、
选
择
排
序
、
插
入
排
序
∗
∗
平方阶排序 :**冒泡排序、选择排序、插入排序 **
平方阶排序:∗∗冒泡排序、选择排序、插入排序∗∗O(nlog2n)
线
性
对
数
阶
排
序
:
∗
∗
快
速
排
序
、
归
并
排
序
、
堆
排
序
∗
∗
线性对数阶排序:** 快速排序、归并排序、堆排序 **
线性对数阶排序:∗∗快速排序、归并排序、堆排序∗∗O(n1+§)
排
序
,
排序,
排序,§$ 是介于 0 和 1 之间的常数:**希尔排序
线性阶
O
(
n
)
O(n)
O(n)排序: 基数排序,此外还有桶、箱排序
**稳定性:**排序后 2 个相等键值的顺序和排序前顺序相同
**稳定的排序算法:**冒泡排序、插入排序、归并排序和基数排序
**不是稳定的排序算法:**选择排序、快速排序、堆排序、希尔排序
**占用额外内存:**快速排序、归并排序、桶排序、基数排序、计数排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhyDp0ip-1631554731712)(F:\0. Note\Typora-image\latex\view)]
学习来源:菜鸟教程
冒泡排序
(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
def bubbleSort(arr):
n = len(arr)
# 遍历所有数组元素
for i in range(n):
# Last i elements are already in place
for j in range(0, n-i-1):
if arr[j] > arr[j+1] :
arr[j], arr[j+1] = arr[j+1], arr[j]
arr = [64, 34, 25, 12, 22, 11, 90]
bubbleSort(arr)
print ("排序后的数组:")
for i in range(len(arr)):
print ("%d" %arr[i]),
插入排序
(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
def insertionSort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
arr = [12, 11, 13, 5, 6]
insertionSort(arr)
print ("排序后的数组:")
for i in range(len(arr)):
print ("%d" %arr[i])
选择排序
(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
def selectionSort(arr):
for i in range(len(arr)):
min_idx = i
for j in range(i+1, len(arr)):
if arr[min_idx] > arr[j]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
快速排序
**思想:**使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。【东尼·霍尔】
分而治之,=冒泡+递归分治
复杂度:
时间复杂度:平均
O
(
n
l
o
g
n
)
Ο(nlogn)
O(nlogn);最坏
O
(
n
2
)
Ο(n^2)
O(n2),不常见
为什么是这种复杂度:
**最好情况——**足够理想,那我们期望每次都把数组都分成平均的两个部分,如果按照这样的理想情况分下去,我们最终能得到一个完全二叉树
**最坏情况——**个树是一个完全的斜树,只有左半边或者右半边[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdiCPHkL-1631554731713)(F:\0. Note\Typora-image\latex\image-20210914010941507-1631552983073.png)]
大多数情况下都比平均时间复杂度为
O
(
n
l
o
g
n
)
Ο(nlogn)
O(nlogn)的排序算法表现要更好
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
空间复杂度: O ( l o g n ) O(logn) O(logn)
- 原地排序
原地快排的空间占用是递归造成的栈空间的使用,最好情况下是递归次,所以空间复杂度为 O ( l o g n ) O(logn) O(logn),最坏情况下是递归 n-1 次,所以空间复杂度是 O ( n ) O(n) O(n)。 - 非原地排序
对于非原地排序,每次递归都要声明一个总数为n的额外空间,所以空间复杂度变为原地排序的n倍,即最好情况下 O ( n l o g n ) O(nlogn) O(nlogn),最差情况下 O ( n 2 ) O(n^2) O(n2)。
步骤为:
- 挑选基准值:从数列中挑出一个元素,称为"基准"(pivot)【首元素】;
- 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。
选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。
图解算法:
left
为区间的开始地址,right
为区间的结束地址,Key
基准值【通常第一个】。
key
首先与arr[right]
进行比较- 如果
arr[right]<key
,则arr[left]=arr[right]
将这个比key小的数放到左边去 - 如果
arr[right]>key
则我们只需要将right--
,right--
之后,再拿arr[right]
与key
进行比较,直到arr[right]<key
交换元素为止。
- 如果
- 存在
arr[right]<key
的情况,将arr[left]=arr[right]
,接下来,将转向left端- 拿
arr[left ]
与key
进行比较,如果arr[left]>key
,则将arr[right]=arr[left]
- 如果
arr[left]<key
,则只需要将left++
,直到arr[left]>key
交换元素为止。
- 拿
- 然后再移动right重复上述步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hYysfjyE-1631554731714)(F:\0. Note\Typora-image\latex\20200209195641634.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ct0SeBwH-1631554731715)(F:\0. Note\Typora-image\latex\20200209195708716.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCq72LwQ-1631554731715)(F:\0. Note\Typora-image\latex\20200209195744548.png)]
最后得到 {23 58 13 10 57 62} 65 {106 78 95 85},再对左子数列与右子数列进行同样的操作。最终得到一个有序的数列。
{23 58 13 10 57 62} 65 {106 78 95 85}
{10 13} 23 {58 57 62} 65 {85 78 95} 106
10 13 23 57 58 62 65 78 85 95 106
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nd3KHAl-1631554731716)(F:\0. Note\Typora-image\latex\quickSort.gif)]
def quickSort(arr, left=None, right=None):
left = 0 if not isinstance(left,(int, float)) else left
right = len(arr)-1 if not isinstance(right,(int, float)) else right
if left < right:
partitionIndex = partition(arr, left, right)
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
def partition(arr, left, right):
pivot = left
index = pivot+1
i = index
while i <= right:
if arr[i] < arr[pivot]:
swap(arr, i, index)
index += 1
i += 1
swap(arr, pivot, index-1)
return index-1
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
arr = [10, 17, 8, 9, 1, 5]
print("排序前的数组:")
print(arr)
quickSort(arr, 0, len(arr)-1)
print("排序后的数组:")
print(arr)
def partition(arr,low,high):
i = ( low-1 ) # 最小元素索引
pivot = arr[high]
for j in range(low , high):
# 当前元素小于或等于 pivot
if arr[j] <= pivot:
i = i+1
arr[i],arr[j] = arr[j],arr[i]
arr[i+1],arr[high] = arr[high],arr[i+1]
return ( i+1 )
# arr[] --> 排序数组
# low --> 起始索引
# high --> 结束索引
# 快速排序函数
def quickSort(arr,low,high):
if low < high:
pi = partition(arr,low,high)
quickSort(arr, low, pi-1)
quickSort(arr, pi+1, high)
arr = [10, 17, 8, 9, 1, 5]
print("排序前的数组:")
print(arr)
quickSort(arr, 0, len(arr)-1)
print("排序后的数组:")
print(arr)
归并排序
(Merge sort,或mergeSort),是创建在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
分治法:
- 分割:递归地把当前序列平均分割成两半。
- 集成:在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O9vbAzzr-1631554731717)(F:\0. Note\Typora-image\latex\mergeSort.gif)]
def merge(arr, l, m, r):
n1 = m - l + 1 # m在左边区间里
n2 = r- m
# 创建临时数组
L = [0] * (n1)
R = [0] * (n2)
# 拷贝数据到临时数组 arrays L[] 和 R[]
for i in range(0 , n1):
L[i] = arr[l + i]
for j in range(0 , n2):
R[j] = arr[m + 1 + j]
# 归并临时数组到 arr[l..r]
i = 0 # 初始化第一个子数组的索引
j = 0 # 初始化第二个子数组的索引
k = l # 初始归并子数组的索引 ? 为什么等于1
while i < n1 and j < n2 :
if L[i] <= R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
# 拷贝 L[] 的保留元素
while i < n1:
arr[k] = L[i]
i += 1
k += 1
# 拷贝 R[] 的保留元素
while j < n2:
arr[k] = R[j]
j += 1
k += 1
def mergeSort(arr,l,r):
if l < r:
m = int((l+(r-1))/2)
mergeSort(arr, l, m)
mergeSort(arr, m+1, r)
merge(arr, l, m, r)
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
print ("给定的数组")
for i in range(n):
print ("%d" %arr[i]),
mergeSort(arr,0,n-1)
print ("\n\n排序后的数组")
for i in range(n):
print ("%d" %arr[i]),
堆排序
(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvLd2VbI-1631554731717)(F:\0. Note\Typora-image\latex\heapSort.gif)]
def heapify(arr, n, i):
largest = i
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 2*i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i] # 交换
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
# Build a maxheap.
for i in range(n, -1, -1):
heapify(arr, n, i)
# 一个个交换元素
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # 交换
heapify(arr, i, 0)
arr = [ 12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print ("排序后")
for i in range(n):
print ("%d" %arr[i]),
计数排序
核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWzSx97A-1631554731718)(F:\0. Note\Typora-image\latex\countingSort.gif)]
希尔排序
也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aX6UKJI8-1631554731718)(F:\0. Note\Typora-image\latex\Sorting_shellsort_anim.gif)]
def shellSort(arr):
n = len(arr)
gap = int(n/2)
while gap > 0:
for i in range(gap,n):
temp = arr[i]
j = i
while j >= gap and arr[j-gap] >temp:
arr[j] = arr[j-gap]
j -= gap
arr[j] = temp
gap = int(gap/2)
arr = [ 12, 34, 54, 2, 3]
n = len(arr)
print ("排序前:")
for i in range(n):
print(arr[i]),
shellSort(arr)
print ("\n排序后:")
for i in range(n):
print(arr[i]),
拓扑排序
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting):
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJYrtHiB-1631554731718)(F:\0. Note\Typora-image\latex\graph.png)]
from collections import defaultdict class Graph: def __init__(self,vertices): self.graph = defaultdict(list) self.V = vertices def addEdge(self,u,v): self.graph[u].append(v) def topologicalSortUtil(self,v,visited,stack): visited[v] = True for i in self.graph[v]: if visited[i] == False: self.topologicalSortUtil(i,visited,stack) stack.insert(0,v) def topologicalSort(self): visited = [False]*self.V stack =[] for i in range(self.V): if visited[i] == False: self.topologicalSortUtil(i,visited,stack) print (stack) g= Graph(6) g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); print ("拓扑排序结果:")g.topologicalSort()