关于每个排序算法的原理
强推:
https://www.runoob.com/w3cnote/ten-sorting-algorithm.html
里面不仅有时间和空间复杂度分析等,还有各种语言的实现方式等
本文作为它的搬运工,为了方便复习,搬运了里面的Python实现代码
Python内嵌的sorted函数的实现原理:
https://blog.csdn.net/qq_42533216/article/details/109519973
还有一些其他的优秀博客:
https://blog.csdn.net/liang_gu/article/details/80627548
https://blog.csdn.net/zsd0819qwq/article/details/105464137
https://guisu.blog.csdn.net/article/details/7776068?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_default&utm_relevant_index=6
图片摘抄于菜鸟教程
平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择、冒泡排序。
线性对数阶 (O(nlogn)) 排序:快速排序、堆排序、归并排序。
O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数:希尔排序。
线性阶 (O(n)) 排序:基数排序,此外还有桶、箱排序。
关于稳定性
稳定的排序算法:冒泡排序、插入排序、归并排序、基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
n:数据规模
k:"桶"的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
def bubbleSort(arr):
'''
https://www.runoob.com/w3cnote/bubble-sort.html
1) 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3) 针对所有的元素重复以上的步骤,除了最后一个。
4) 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
'''
N = len(arr)
for i in range(1, N):
for j in range(N-i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
选择排序
def selectionSort(arr):
'''
https://www.runoob.com/w3cnote/selection-sort.html
1) 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2) 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3) 重复第二步,直到所有元素均排序完毕。
'''
N = len(arr)
for i in range(N-1):
# 记录最小数的索引
minIndex = i
# 寻找未排序元素中的最小数
# 将最小数的索引放到minIndex
for j in range(i+1, N):
if arr[j] < arr[minIndex]:
minIndex = j
# 如果i不是最小数,则将i和最小数进行交换
if i != minIndex:
arr[i], arr[minIndex] = arr[minIndex], arr[i]
return arr
插入排序
def insertionSort(arr):
'''
https://www.runoob.com/w3cnote/insertion-sort.html
1) 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2) 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
'''
N = len(arr)
# for i in range(1, N):
# for j in range(i-1, -1, -1):
# if arr[j] > arr[j+1]:
# arr[j], arr[j+1] = arr[j+1], arr[j]
# else:
# # 因为前面已经排好序,所以不用再判断了
# break
for i in range(1, N):
preIndex = i - 1
current = arr[i]
# 找到current应该插入的位置,即preIndex+1
# 对于已经排好序中大于current的元素,则进行后移的 *** 作
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+1] = arr[preIndex]
preIndex -= 1
arr[preIndex+1] = current
return arr
希尔排序
def shellSort(arr):
'''
https://www.runoob.com/w3cnote/shell-sort.html
基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,
待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
1) 选择一个增量序列 t1, t2,……, tk, 其中 ti > tj, tk = 1;
2) 按增量序列个数 k, 对序列进行 k 趟排序;
3) 每趟排序,根据对应的增量 ti, 将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。
仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
'''
N = len(arr)
# 选择一个合适的group,即将数组分为几个部分
# 注意group必须大于1,不然是一个死循环
group = 3
# 根据group生成一个间隔gap,这里gap = 4
gap = 1
while gap < N / group:
gap = gap * group + 1
while gap > 0:
for i in range(gap, N):
temp = arr[i]
j = i - gap
while j >= 0 and arr[j] > temp:
arr[j+gap] = arr[j]
j -= gap
arr[j+gap] = temp
gap = int(gap / group)
return arr
归并排序
def merge(left, right):
# 开辟新空间
result = []
# 遍历左右两个序列,直到一个空为止
while left and right:
# 将左右两部分的第一个元素进行比较
# 将较小的那个加入到result中去
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
# 将另外一个不为空的序列依次加入到result中去
while left:
result.append(left.pop(0))
while right:
result.append(right.pop(0))
return result
def mergeSort(arr):
'''
https://www.runoob.com/w3cnote/merge-sort.html
1) 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2) 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4) 重复步骤 3 直到某一指针达到序列尾;
5) 将另一序列剩下的所有元素直接复制到合并序列尾。
'''
N = len(arr)
if N < 2: return arr
middle = N // 2
# 将数组均等分为两个部分
left, right = arr[:middle], arr[middle:]
# 对每个部分分别进行递归 *** 作
# 然后根据元素大小进行融合
return merge(mergeSort(left), mergeSort(right))
快速排序
好文:https://blog.csdn.net/qq_40941722/article/details/94396010
def partition(arr, left, right):
'''
https://www.runoob.com/w3cnote/quick-sort-2.html
根据上述链接的思想实现
'''
# 选择第一个元素为基准
pivot = left
# 这里使用的是快慢双指针的思想
# i为快指针,index为慢指针
# 快指针遍历整个数组找到那些小于基准值的元素,
# 而慢指针就是指向第一个大于基准值的元素
index = pivot + 1
i = index
while i <= right:
# 当快指针指向的元素小于基准值时
# 交换快指针和慢指针的值
# 并将慢指针向前移一位
if arr[i] < arr[pivot]:
arr[i], arr[index] = arr[index], arr[i]
index += 1
i += 1
# 将基准交换到慢指针的位置
arr[pivot], arr[index-1] = arr[index-1], arr[pivot]
# 返回基准位置
return index-1
def partition2(arr, left, right):
'''
https://blog.csdn.net/qq_40941722/article/details/94396010
根据上述链接的思想实现
'''
# 选择第一个元素为基准
pivot = left
# 这里使用方向相反的双指针思想
# left为左指针,从坐向右移动
# right为右指针,从右向左移动
# 直到left>right时,移动停止
while left <= right: # 注意这里一定要有等号
# 当left指向的元素大于基准值同时right指向的元素小于基准值时,交换left和right指向的元素
if arr[left] > arr[pivot] and arr[right] < arr[pivot]:
arr[left], arr[right] = arr[right], arr[left]
# 当left指向的元素小于等于基准值时,继续向右移动
if arr[left] <= arr[pivot]: left += 1
# 当right指向的元素大于等于基准值时,继续向左移动
if arr[right] >= arr[pivot]: right -= 1
# # 将基准交换到left指针的位置
arr[pivot], arr[left-1] = arr[left-1], arr[pivot]
# 返回基准位置
return left-1
def quickSort(arr, left=None, right=None):
'''
https://www.runoob.com/w3cnote/quick-sort-2.html
1) 从数列中挑出一个元素,称为 "基准" (pivot);
2) 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
在这个分区退出之后, 该基准就处于数列的中间位置。这个称为分区(partition) *** 作;
3) 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
'''
N = len(arr)
if left is None: left = 0
if right is None: right = N - 1
if left < right:
# partitionIndex = partition(arr, left, right)
partitionIndex = partition2(arr, left, right)
# 对基准前后两个部分再进行快排
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
堆排序
有关堆排序的介绍,可以参考我的文章:一文了解堆的定义、优先队列、堆排序以及Python实现
def buildMaxHeap_Down(arr):
N = len(arr)
# 为什么从(N-1-1)//2开始?
# 我们的目标是找到最后一个元素对应的父节点
# 最后一个元素的下标是N-1
# 节点i的父节点是(i-1) // 2
# 所以最后一个元素对应的父节点为: (N-1-1) // 2
for i in range((N-1-1)//2, -1, -1):
heapDown(arr, i)
# print(arr)
def heapDown(arr, index, arrLen=None):
'''
arr: 一维数组
index: 一个父节点
'''
left = 2 * index + 1
right = 2 * index + 2
if arrLen is None: arrLen = len(arr)
# 找到父节点、左节点和右节点中最大节点的下标largest
largest = index
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
# 如果最大节点的下标不是父节点
# 则将父节点与最大节点进行交换
if largest != index:
arr[index], arr[largest] = arr[largest], arr[index]
# 交换完后,该父节点变为子节点
# 它作为下面树的父节点继续下滤
heapDown(arr, largest, arrLen)
def heapSort(arr):
'''
https://www.runoob.com/w3cnote/heap-sort.html
1) 创建一个堆 H[0……n-1];
2) 把堆首(最大值)和堆尾互换;
3) 把堆的尺寸缩小 1, 并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
4) 重复步骤 2, 直到堆的尺寸为 1。
'''
# 构建一个大根堆
buildMaxHeap_Down(arr)
# 从最后一个元素遍历到第2个元素(排除根节点)
for i in range(len(arr)-1, 0, -1):
# 交换根节点和未排序数组的最后一个元素
arr[i], arr[0] = arr[0], arr[i]
# 将交换过后的堆进行下滤 *** 作重新构建为大根堆
# 注意:这里需要限制数组的长度为0到i,即只考虑未排序的数组
# 而后面已经排好序的序列不再考虑
heapDown(arr, 0, i)
return arr
计数排序
桶排序
基数排序
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)