十大排序算法思想与 Python 实现

简介: 现在越来越卷的互联网面试,排序算法也是常考的方向之一,而在这些算法当中。一般排序算法最常考的:快速排序和归并排序。

排序算法


现在越来越卷的互联网面试,排序算法也是常考的方向之一,而在这些算法当中。一般排序算法最常考的:快速排序归并排序

因为这两个算法体现了分治算法思想的核心观点,而且还有很多出题的可能。本篇文章就从算法思想和Python 代码实现上带读者快速过完整个经典排序算法。

更多细节请参考刘宇波老师的:不能白板编程红黑树就是基础差?别扯了。

1. 常见的排序算法

排序算法很多,除了能写出常见排序算法的代码,还需要了解各种排序的时空复杂度、稳定性、使用场景、区别等。

1.1 选择排序

1.1.1 思想

对于给定的一组序列,第一轮比较选择最小(或最大)的值,然后将该值与索引第一个进行交换;接着对不包括第一个确定的值进行第二次比较,选择第二个记录与索引第二个位置进行交换,重复到只剩最后一个记录位置。

案例:幼儿园排队,老师先让站成一队,带第一个小朋友依此跟其他小朋友逐个比较,选出个子最矮的,然后依此进行

1.1.2 实现
defselection_sort(gList):
"""选择排序    :param gList: 给定的一组序列    :return: 返回排好序的序列    """length=len(gList)
foriinrange(length-1):
flag=iforjinrange(i+1, length):
ifgList[flag] >gList[j]:
flag=j# 如果最小值的索引与最小值相对应,则无需再次交换ifflag!=i:
gList[flag], gList[i] =gList[i], gList[flag]
returngList


1.1.3 选择排序分析
  • 时间复杂度: 最好、最坏、平均的时间复杂度都为 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性: 不稳定

1.2 冒泡排序

1.2.1 思想

对于给定的一组序列含n个元素,从第一个开始对相邻的两个记录进行比较,当前面的记录大于后面的记录,交换其位置,进行一轮比较和换位之后,最大记录在第n个位置;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个时为止。

案例:冒泡,像气泡一样往上升

1.2.2 实现
defbubble_sort(gList):
"""冒泡排序"""length=len(gList)
foriinrange(length):
forjinrange(i+1, length):
ifgList[i] >gList[j]:
gList[i], gList[j] =gList[j], gList[i]
returngList


1.2.3 冒泡排序分析
  • 时间复杂度:
  • 最好时间复杂度:O(n)
  • 最坏时间复杂度: O(n^2)
  • 平均时间复杂度: O(n^2)
  • 空间复杂度: O(1)
  • 稳定性: 稳定的排序

1.3 插入排序

1.3.1 思想

对于给定的一组记录,初始时假设第一个记录自成一个有序序列,其余的记录为无序序列;接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直至最后一个记录插入到有序序列中为止。

案例:抓扑克牌

1.3.2 实现
definsertion_sort(gList):
"""插入排序"""length=len(gList)
foriinrange(1, length):
temp=gList[i]  # 当前的待插入的值j=i-1# 前一个值whilej>=0:
ifgList[j] >temp:
gList[j+1] =gList[j]  # 插入的动作gList[j] =temp# 插入完毕j-=1returngList


1.3.3 插入排序分析
  • 时间复杂度
  • 最好时间复杂度:O(n)
  • 最坏时间复杂度: O(n^2)
  • 平均时间复杂度: O(n^2)
  • 空间复杂度:O(1)
  • 稳定性: 稳定的排序

1.4 归并排序 ☆☆★

1.4.1 思想

利用递归与分治技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列。其中“归”代表的是递归的意思,即递归地将数组折半地分离为单个数组。

给定一组序列含n个元素,首先将每两个相邻的长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列为止。

1.4.2 实现
defmerge_sort(gList: list) ->list:
"""归并排序    :param gList: 给定序列    :return: 升序排列后的集合    """defmerge(left: list, right: list) ->list:
"""merge left and right        :param left: left list        :param right: right list        :return: merge reslut        """i, j=0, 0result= []
whilei<len(left) andj<len(right):
ifleft[i] <=right[j]:
result.append(left[i])
i+=1else:
result.append(right[j])
j+=1result+=left[i:]
result+=right[j:]
returnresultiflen(gList) <=1:
returngListnum=len(gList) //2left=merge_sort(gList[:num])
right=merge_sort(gList[num:])
returnmerge(left, right)
if__name__=='__main__':
gList= [3, 5, 2, 4, 1]
print("----排序前:", gList)
print("----归并排序后: ", merge_sort(gList))



1.4.3 归并排序分析
  • 时间复杂度: 最好、最坏和平均情况O(nlogn)
  • 空间复杂度:O(n)
  • 定性: 稳定

题目:100个有序数列如何合成一个大数组?

1.5 快速排序☆★★

1.5.1 思想

高效的排序算法,它采用“分而治之”的思想,把大的拆分为小的,小的再拆分为更小的。其原理是:对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前部分的所有记录均比后部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。

1.5.2 实现
# -*- coding: utf-8 -*-defquick_sort(gList, left=0, right=None) ->list:
"""快速排序    :param gList: 给定一组序列    :param left:    :param right:    :return: 升序排序后的序列    """ifrightisNone:
right=len(gList)-1ifleft>right:
returngListkey=gList[left]
low=lefthigh=rightwhileleft<right:
whileleft<rightandgList[right] >=key:
right-=1gList[left] =gList[right]
whileleft<rightandgList[left] <=key:
left+=1gList[right] =gList[left]
gList[right] =keyquick_sort(gList, low, left-1)
quick_sort(gList, left+1, high)
returngListif__name__=='__main__':
gList= [3, 5, 2, 4, 1, 6, 7]
print("----排序前:", gList)
print("----快速排序后: ", quick_sort(gList))




1.5.3 快速排序分析
  • 时间复杂度:
  • 最坏时间复杂度:O(n2)
  • 最好时间复杂度:O(nlogn)
  • 平均时间复杂度: O(nlogn)
  • 空间复杂度:
  • 稳定性:不稳定

扩展:随机快排

1.6 希尔排序

1.6.1 思想

希尔排序也称为“缩小增量排序”。它的基本原理是:首先将待排序的元素分成多个子序列,使得每个子序列的元素个数相对较少,对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序后”,再对所有元素进行一次直接插入排序。

1.6.2 实现
# -*- coding: utf-8 -*-defshell_sort(gList) ->list:
"""希尔排序"""length=len(gList)
step=2group=length//stepwhilegroup>0:
forstartPosinrange(group):
gap_insertion_sort(gList, startPos, group)
group=group//2returngListdefgap_insertion_sort(gList, start, gap):
foriinrange(start+gap, len(gList), gap):
curr_value=gList[i]
pos=iwhilepos>=gapandgList[pos-gap] >curr_value:
gList[pos] =gList[pos-gap]
pos=pos-gapgList[pos] =curr_valueif__name__=='__main__':
gList= [5, 4, 2, 1, 7, 3, 6]
print("-----yuzhou1su-----", gList)
print("-----希尔排序后:", shell_sort(gList))




1.6.3 希尔排序分析
  • 时间复杂度:
  • 最好时间复杂度:O(n)
  • 最坏时间复杂度:O(ns)(1<s<2)
  • 平均时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 稳定性: 不稳定

1.7 堆排序

堆是一种特殊的树形数据结构,其每个结点都有一个值,通常提到的堆都是指一棵完全二叉树,根结点的值小于(或大于)两个子结点的值,同时根结点的两个子树也分别是一个堆。

1.7.1 算法思想:

对于给定的序列,初始把这些记录看成一刻顺序存储的二叉树,然后将其调整为一个大顶堆,然后将堆的最后一个元素与堆顶元素进行交换后,堆的最后一个元素即为最大记录;接着将前(n-1)个元素重新调整为一个大顶堆,在将堆顶元素与当前堆的最后一个元素进行交换后得到次大的记录,重复该过程直到调整的堆中只剩一个元素为止,该记录即为最小记录,此时可得到一个有序序列。

过程:1. 构建堆;2. 交换堆顶元素与最后一个元素的位置

1.7.2 实现
defheapify(unsorted, index, heap_size):
largest=indexleft_index=2*index+1right_index=2*index+2ifleft_index<heap_sizeandunsorted[left_index] >unsorted[largest]:
largest=left_indexifright_index<heap_sizeandunsorted[right_index] >unsorted[largest]:
largest=right_indexiflargest!=index:
unsorted[largest], unsorted[index] =unsorted[index], unsorted[largest]
heapify(unsorted, largest, heap_size)
defheap_sort(unsorted):
"""堆排序"""length=len(unsorted)
foriinrange(length//2-1, -1, -1):
heapify(unsorted, i, length)
foriinrange(length-1, 0, -1):
unsorted[0], unsorted[i] =unsorted[i], unsorted[0]
heapify(unsorted, 0, i)
returnunsortedif__name__=='__main__':
gList= [5, 4, 2, 1, 7, 3, 6]
print("-----yuzhou1su-----", gList)
print("-----堆排序后:", heap_sort(gList))


1.7.3 堆排序分析

时间复杂度: 主要耗费在创建堆和反复调整堆上,最坏情况下,时间复杂度也为O(nlogn)

空间复杂度O(1)

稳定性: 不稳定

1.8 计数排序

推荐文章: https://www.cnblogs.com/xiaochuan94/p/11198610.html

1.8.1 算法思想

对于某种整数K,计数排序假定每个元素都是1到K范围内的整数。 计数排序的基本思想是为每个输入元素x确定小于x的元素数量, 此信息可用于直接将其放置在正确的位置。 例如,如果10个元素小于x,则x属于输出中的位置11。

1.8.2 实现
# -*- coding: utf-8 -*-# @Author    : 宇宙之一粟defcounting_sort(unsorted):
"""计数排序    :param unsorted:给定一组序列    :return: 升序序列    """ifunsortedis []:
return []
# 根据给定序列求信息coll_len=len(unsorted)
coll_max=max(unsorted)
coll_min=min(unsorted)
# 创建计数数组counting_arr_length=coll_max+1-coll_mincounting_arr= [0] *counting_arr_length# 计数操作fornumberinunsorted:
counting_arr[number-coll_min] +=1# 将每个位置与它的前一个相加。counting_arr[i]统计出多少个# element <= i的元素foriinrange(1, counting_arr_length):
counting_arr[i] =counting_arr[i] +counting_arr[i-1]
# 创建保存升序结果的数组ordered= [0] *coll_lenforiinreversed(range(0, coll_len)):
ordered[counting_arr[unsorted[i] -coll_min] -1] =unsorted[i]
counting_arr[unsorted[i] -coll_min] -=1returnorderedif__name__=='__main__':
gList= [5, 4, 2, 1, 3, 6]
print("-----yuzhou1su:", gList)
print("-----计数排序后:", counting_sort(gList))



1.8.3 计数排序分析

时间复杂度: O(n) ifK=O(n)

空间复杂度:O(n) ifK=O(n)

Ps: 如果K特别大,时间复杂度会很高;如果面试官让你设计数据规模小的线性排序算法,可能就是考察计数排序

1.9 桶排序

1.9.1 算法思想

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

菜鸟教程:桶排序

1.9.2 实现
# -*- coding: utf-8 -*-# @Author    : 宇宙之一粟importmathdefinsertion_sort(collection):
foriinrange(1, len(collection)):
temp=collection[i]
index=iwhileindex>0andtemp<collection[index-1]:
collection[index] =collection[index-1]
index-=1collection[index] =tempdefbucket_sort(collection):
code=hashing(collection)
buckets= [list() for_inrange(code[1])]
foriincollection:
x=rehashing(i, code)
buck=buckets[x]
buck.append(i)
forbucketinbuckets:
insertion_sort(bucket)
ndx=0forbucinrange(len(buckets)):
forvalinbuckets[buc]:
collection[ndx] =valndx+=1returncollectiondefhashing(collection):
m=collection[0]
foriinrange(1, len(collection)):
ifm<collection[i]:
m=collection[i]
result= [m, int(math.sqrt(len(collection)))]
returnresultdefrehashing(i, code):
returnint(i/code[0] * (code[1] -1))
if__name__=='__main__':
gList= [5, 4, 2, 1, 3, 6]
print("-----yuzhou1su:", gList)
print("-----桶排序后:", bucket_sort(gList))


1.9.3 桶排序分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

1.10 基数排序

1.10.1 算法思想

与计数排序/桶排序类似,基数排序跟输入元素相关。比如:根据基数d对给定序列进行排序,这意味着所有的数字都是d位数。过程:

  1. 取每个元素的最低有效位
  2. 根据该数字对元素列表进行排序,但保持相同数字的元素顺序
  3. 用更高有效位重复排序,直到最高位
1.10.2 实现
defradix_sort(unsorted):
radix=10max_len=Falsetmp, placement=-1, 1whilenotmax_len:
max_len=Truebuckets= [list() for_inrange(radix)]
foriinunsorted:
tmp=int(i/placement)
buckets[tmp%radix].append(i)
ifmax_lenandtmp>0:
max_len=Falsea=0forbinrange(radix):
buck=buckets[b]
foriinbuck:
unsorted[a] =ia+=1# move to next digitplacement*=radixreturnunsortedif__name__=='__main__':
gList= [5, 4, 2, 1, 3, 6]
print("-----yuzhou1su:", gList)
print("-----基数排序后:", radix_sort(gList))




1.10.3 基数排序分析

基数排序适用于位数小的数字序列。

  • 时间复杂度: O(nlog(r) m),其中 r 为所采取的基数,而 m 为堆数
  • 稳定性:稳定

1.11 其他排序

  • 拓扑排序:在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。
  • 外部排序:大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。
  • 位图排序:当待排序数据规模较大,而堆内存大小又没有限制时,位图排序则最高效。
  • Tim-sort:Python的 list  标准排序算法,由Tim Peters设计。本质上是一种自下而上的归并排序,利用一些数据的初始运行,之后进行额外的插入排序。Tim-sort也成为Java7中数组排序的默认算法。

2. 各种排序算法比较?

5b0672d7482a8588211c7383a9964266.png

根据上图总结:

  • 不稳定算法有:选择、快速、希尔、堆  ( 记忆口诀:"快选七(希)堆不稳定的排序")
  • 时间复杂度O(n^2):选择、冒泡、插入
  • 时间复杂度O(nlogn):快速、归并、堆、希尔
  • 时间复杂度O(n):计数、桶
  • 空间复杂度O(1):选择、插入、冒泡、希尔、堆
  • 空间复杂度O(n):归并、计数、桶
  • 空间复杂度O(logn):快速排序

3.总结

一定要根据数据的规模、规律来给出合适的算法,不能觉得快速排序名字就以为是快速的,切记不能什么排序问题都回答快排。

  1. 虽然插入排序和冒泡排序平均速度较慢,但当初始序列整体或局部有序时,这两者效率较高
  2. 排序数据较小,且不要求稳定的情况下,选择排序效率较高;要求稳定,选择冒泡排序。
  3. 堆排序在更大的序列上往往优于快速排序和归并排序。
  4. 针对小数据追求线性时间复杂度,考虑计数排序和桶排序
  5. 除了上面几种常见的排序算法,还有众多其他排序算法,每种排序算法都有其最佳适用场合。具体情况具体分析。

最后,感谢大家阅读。我是宇宙之一粟,一个头发比想法多的研究僧。

如果觉得文章还不错,请一定帮忙点个赞。谢谢🤝

参考资料:

相关文章
|
25天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
87 4
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
1月前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
310 55
|
1月前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
127 66
|
7天前
|
存储 算法 安全
控制局域网上网软件之 Python 字典树算法解析
控制局域网上网软件在现代网络管理中至关重要,用于控制设备的上网行为和访问权限。本文聚焦于字典树(Trie Tree)算法的应用,详细阐述其原理、优势及实现。通过字典树,软件能高效进行关键词匹配和过滤,提升系统性能。文中还提供了Python代码示例,展示了字典树在网址过滤和关键词屏蔽中的具体应用,为局域网的安全和管理提供有力支持。
34 17
|
2月前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
152 67
|
2月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
144 61
|
2月前
|
算法 数据安全/隐私保护 开发者
马特赛特旋转算法:Python的随机模块背后的力量
马特赛特旋转算法是Python `random`模块的核心,由松本真和西村拓士于1997年提出。它基于线性反馈移位寄存器,具有超长周期和高维均匀性,适用于模拟、密码学等领域。Python中通过设置种子值初始化状态数组,经状态更新和输出提取生成随机数,代码简单高效。
131 63
|
1月前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
203 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
1月前
|
算法 网络协议 Python
探秘Win11共享文件夹之Python网络通信算法实现
本文探讨了Win11共享文件夹背后的网络通信算法,重点介绍基于TCP的文件传输机制,并提供Python代码示例。Win11共享文件夹利用SMB协议实现局域网内的文件共享,通过TCP协议确保文件传输的完整性和可靠性。服务器端监听客户端连接请求,接收文件请求并分块发送文件内容;客户端则连接服务器、接收数据并保存为本地文件。文中通过Python代码详细展示了这一过程,帮助读者理解并优化文件共享系统。
|
1月前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
58 5