数据的排序是在解决实际问题时经常用到的步骤,也是数据结构的考点之一,下面介绍10种经典的排序方法。

  首先,排序方法可以大体分为插入排序、选择排序、交换排序、归并排序和桶排序四大类,其中,插入排序又分为直接插入排序、二分插入排序和希尔排序,选择排序分为直接选择排序和堆排序,交换排序分为冒泡排序和快速排序,桶排序以基数排序和计数排序为代表。这些排序方法的时间复杂度和空间复杂度分别如下表所示。



排序方法的稳定性是这样定义的:在待排序序列中如果存在a[i]和a[j],a[i]=a[j]&&i<j,如果排序后仍然符合a[i]=a[j]&&i<j,即它们的前后相对位置关系没有改变,该排序算法就是稳定的。

(1)直接插入排序

插入排序的基本思想是将数据插入合适的位置。如下所示序列[6,8,1,4,3,9,5,0],以升序排列为例。

a.6<8,符合升序

b. 8>1,不符合要求,改变1的位置。首先比较1和8,1更小,交换1和8的位置,序列成为[6,1,8,4,3,9,5,0],然后继续比较1和6,1更小,交换1和6的位置,序列成为[1,6,8,4,3,9,5,0],注意此时前三个值已经符合升序的要求。

c. 8>4,不符合要求,改变4的位置,按照上面的方法,依次与前面的值比较,分别与8和6交换位置,当比较到1时,1<4,不用交换位置。此时序列成为[1,4,6,8,3,9,5,0]

d.重复上满的操作,直到整个序列遍历完成。



因此,插入排序实际上是保证遍历过的序列是有序的,然后将下一个访问到的值,通过比较和交换位置,插入到这个有序序列中,当所有的值都被访问过后,整个序列就是有序的了。所以这种方法是稳定,空间复杂度为O(1),最好的时间复杂度为O(n),代码如下

def insert_sort(arr):
for i in range(len(arr)-1):
if arr[i+1]<arr[i]:#如果arr[i+1]较小,将其插入到前面升序序列中
for j in range(i+1,0,-1):
if arr[j]<arr[j-1]:#依次将大于arr[i+1]的值向后移动,直到找到不大于arr[i+1]的值
arr[j-1],arr[j]=arr[j],arr[j-1]
else:
break
return arr

(2) 二分插入排序

二分插入排序的思想与直接插入排序相同,只是在将数据插入有序序列时,采用了二分查找的思想,即先于中间位置的值进行比较,以缩短查找的时间。代码如下

def BinaryInsert_sort(arr):
for i in range(1,len(arr)):
if arr[i]<arr[i-1]:
left,right=0,i-1
while left<right:#最终right位置的值是有序序列中第一个不大于arr[i]的值
mid=left+(right-left)//2
if arr[i]<arr[mid]:#
right=mid
else:
left=mid+1
for j in range(i,right,-1):
arr[j],arr[j-1]=arr[j-1],arr[j]
return arr

(3) 希尔排序

希尔排序建立在直接插入排序的基础上,假设序列长度为n,先取一个小于n的整数d1,序列中所有距离为d1的数据为一组,如下图中,以2为i增量,1,4,5为一组,8,3,0为一组,1,9为1组,然后在组内进行直接插入排序,然后取整数d2,d2<d1,重复上面的步骤,直到增量减少为1。一般的初次取序列的一半为增量,以后每次减半。这样通过相隔增量的数,使得数移动时能跨过多个元素,加快速度。代码如下

def Hill_sort(arr):
d=len(arr)//2
while(d>=1):
for i in range(len(arr)//d):
for j in range(i,len(arr)-d,d):
if arr[j+d]<arr[j]:
for k in range(j+d,i,-d):
if arr[k]<arr[k-d]:
arr[k],arr[k-d]=arr[k-d],arr[k]
d=d//2
return arr

因为希尔排序跨距离访问元素,因此不稳定。空间复杂度为O(1),最好的时间复杂度为O(n)。

(4) 直接选择排序

选择排序的思想是每次从无序的序列中选择最大或最小的值,并与第一个位置的值交换。如序列l,如果是升序排列,第一次找出l的最小值与l[0]交换,第二次找出l[1:]最小的值,与l[2]交换,以此类推,代码如下。

def select_sort(arr):
for i in range(len(arr)-1):
minnum=arr[i]
m=i
for j in range(i+1,len(arr)):
if arr[j]<minnum:
minnum=arr[j]
m=j
arr[i],arr[m]=arr[m],arr[i]
return arr

直接选择排序是稳定的,空间复杂度为O(1),时间复杂度恒定为O(n2),因为原序列是否有序不会影响选择排序的步骤。

(5) 堆排序

堆排序用到了大顶堆和小顶堆的概念。大顶堆是父节点最大的二叉树,即arr[i]>=arr[2i+1]&&arr[i]>=arr[2i-1];小顶堆是父节点的值最小的二叉树,即arr[i]<=arr[2i+1]&&arr[i]<=arr[2i-1]。

以升序排序为例,首先建立初始最大堆,以某个节点为入口,向下调整其子节点,使其符合大顶堆的特点。建立初始大顶堆的过程会重复调整一些节点,是因为父节点和子节点的交换,会影响以子节点为根节点的子树,如图中,[9,1,7]子树原符合要求,6和9交换后,[6,1,7]不符合要求,就要再次调整该子树。

初始大顶堆建立之后,将根节点的值置换到序列末尾,然后重新调整前面的序列,这时只从根节点向下调整即可,因为初始大顶堆已经形成,如图中,即便交换了0和4的位置,父节点8也仍旧是最大的。

def heap_sort(arr):
def adjust(arr,node,maxid):
root=node
while True:
child=2*root+1
if child>maxid:
return
child=child+1 if arr[child+1]>arr[child] and child+1<maxid else child#child是两个孩子中值较大的一个
if arr[root]<arr[child]:
arr[root],arr[child]=arr[child],arr[root]
root=child
else:
return
first=len(arr)//2-1
for node in range(first,-1,-1):
adjust(arr,node,len(arr)-1)
for maxid in range(len(arr)-1,0,-1):
arr[0],arr[maxid]=arr[maxid],arr[0]
adjust(arr,0,maxid-1)

堆排序不稳定,空间复杂度为O(1),但时间复杂度恒定为O(nlogn)。

(6) 冒泡排序

交换排序即通过交换元素位置,使序列有序。冒泡排序是最简单的交换排序,每次将最大值/最小值依次交换到序列末尾。冒泡排序是稳定的,空间复杂度为O(1),时间复杂度最好为O(n)。

def bubble_sort(arr):
for i in range(len(arr)-1): # 这个循环负责设置冒泡排序进行的次数
for j in range(len(arr)-i-1): # j为列表下标
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr

(7) 快速排序

快速排序可以说是使用频率和考察频率最高的排序方法。快速排序的基本思想是以序列中的某个值为标准,大于该值的被放在右边,小于该值放在左边,然后再继续对该值左右两两边的序列重复此步骤,思想很简单,代码如下。

import random
def quick_sort(arr,start,end):
if end-start<=0: return
index=random.randrange(start,end+1)
print(index)
arr[end],arr[index]=arr[index],arr[end]
small=start-1
for i in range(start,end):
if arr[i]<arr[end]:
small+=1
if small!=i:#如果small与i不相同,说明有大于arr[end]的值出现,small记录的是分界线的位置
arr[i],arr[small]=arr[small],arr[i]
small+=1
arr[end],arr[small]=arr[small],arr[end]
print(arr)
quick_sort(arr,start,small-1)
quick_sort(arr,small+1,end)

对于python,有一种更加简单明了的实现方式

def qsort(arr):
if len(arr) <= 1: return arr
return qsort([lt for lt in arr[1:] if lt < arr[0]]) + arr[0:1]+ qsort([ge for ge in arr[1:] if ge >= arr[0]])

快速排序并没有创建新的数组,但由于实现过程中利用了函数的递归调用,需要堆栈空间,所以空间复杂度为O(nlogn),时间复杂度最好为O(nlogn)。

(8) 归并排序

归并排序采用分而治之的思想,将序列分为多个小的序列,对小的序列排序之后,将小序列合并为大的序列,过程如下图所示。



代码如下

def merge_sort(arr,start,end):
def merging(l1,l2,lnew=[]):
i,j=0,0
while(i<len(l1) and j<len(l2)):
if l1[i]<l2[j]:
lnew.append(l1[i])
i+=1
else:
lnew.append(l2[j])
j+=1
if i<len(l1):
for num in l1[i:]:
lnew.append(num)
if j<len(l2):
for num in l2[j:]:
lnew.append(num)
return lnew
if end-start>1:#分
mid=start+(end-start)//2
merge_sort(arr,start,mid)
merge_sort(arr,mid,end)
arr[start:end]=merging(arr[start:mid],arr[mid:end],[])#治
return arr

归并排序需要新的数组存放合并后的序列,空间复杂度为O(n),时间复杂度恒定为O(nlogn),是稳定的。

(9) 计数排序

桶排序实际上是一类排序方法的总称。桶排序的思想是将所有数据按照一定的映射关系放到一定数量的桶中,将每个桶里的数据排序,再将所有的桶组合起来,所以不同的映射关系下可以产生不同的桶排序,并且桶排序是基于其他排序算法的,桶内数据的排序需要使用其他排序算法。计数排序和基数排序是桶排序的两种代表。

计数排序是采用的映射关系是最直接的f[i]=i,即一个桶里的数字都是相同的,因此没有桶内排序的步骤。具体过程为,

1.找出待排序的数组中最大和最小的元素

2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项,这时映射的过程

3.对所有的计数累加,C[i]的含义即是在数组中小于i的元素个数,即为排序后i应放的位置

4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个 元素就将C(i)减去1。

代码如下

def count_sort(arr):
crr=[]
m=max(arr)+1
ll=len(arr)
ans=[]
for i in range(ll):#用于存放排序后的数组
ans.append(None)
for i in range(m):#桶
crr.append(0)
for num in arr:#映射的过程,计数
crr[num]+=1
for i in range(1,m):#计数累加
crr[i]=crr[i-1]+crr[i]
for num in arr:#反向填充数组
ans[crr[num]-1]=num
crr[num]-=1
return ans

(10) 基数排序

基数排序的映射方法是按照数据特定位上数字放入不同的桶中, 将序列中的数组按照个位放到不同的桶中,然后按照个位0~9的顺序依次取出,再按照十位放到不同的桶中,…….直到达到数据的最高位,最后一次将数据取出时,排序完成,例如:



实现代码

def radix_sort(arr):
def compare(basis,arr):#以数组basis为依据的插入排序
for i in range(1,len(basis)):
if basis[i]<basis[i-1]:
for j in range(i,0,-1):
if basis[j-1]>basis[j]:
basis[j],basis[j-1]=basis[j-1],basis[j]
arr[j],arr[j-1]=arr[j-1],arr[j]
else:
break
upnum=max(arr)
tmp=1
while(tmp<upnum):
basis=[num//tmp%10 for num in arr]
compare(basis,arr)
tmp*=10
return arr

注意基数排序在桶内排序时,一定要采用稳定的排序方法,这样才可以利用之前的排序结果,否则之前的排序是无意义的。

Python中经典排序方法的更多相关文章

  1. Python中的排序方法sort(),sorted(),argsort()等

    python 列表排序方法sort.sorted技巧篇 转自https://www.cnblogs.com/whaben/p/6495702.html,学习参考. Python list内置sort( ...

  2. Python中的排序方法

    1 list.sort list.sort(key=None, reverse=False) 该方法只能用于list.就地排序,原来的list被修改.key的用法见下文.reverse控制降序还是生序 ...

  3. python不使用系统库中的排序方法判断一个数组是否是有序数组

    2. 给定一组整数, 已知其每两个数都互不相同,判断这些数字是否能排成一个有序的数组? 例:li = [1,3,4,2] 是有续的 可以排序为li =[1,2,3,4] li = [2,4,6,8] ...

  4. Python中的__new__()方法与实例化

    @Python中的__new__()方法与实例化   __new__()是在新式类中新出现的方法,它作用在构造方法建造实例之前,可以这么理解,在Python 中 存在于类里面的构造方法__init__ ...

  5. python中的sort方法

    Python中的sort()方法用于数组排序,本文以实例形式对此加以详细说明: 一.基本形式 列表有自己的sort方法,其对列表进行原址排序,既然是原址排序,那显然元组不可能拥有这种方法,因为元组是不 ...

  6. python中的sort方法使用详解

    Python中的sort()方法用于数组排序,本文以实例形式对此加以详细说明: 一.基本形式 列表有自己的sort方法,其对列表进行原址排序,既然是原址排序,那显然元组不可能拥有这种方法,因为元组是不 ...

  7. python中字典排序,列表中的字典排序

    python中字典排序,列表中的字典排序 一.使用python模块:operator import operator #首先要导入模块operator x = {1:2, 3:4, 4:3, 2:1, ...

  8. python中的replace()方法的使用

    python中的replace()方法的使用 需求是这样的:需要将字符串的某些字符替换成其他字符 str.replace(old,new,max) 第一个参数是要进行更换的旧字符,第二个参数是新的子串 ...

  9. Python中的字符串方法

    Python中的字符串方法 字符串类即str提供了许多有用的方法来操纵字符串.具体来说,我们将讨论如下的方法. 搜索字符串内的子字符串. 测试字符串. 格式字符串. 转换字符串. 回顾前面的章节,方法 ...

随机推荐

  1. .Net Core2.2升级到3.1小记

    .NET Core 3.1 作为LTS长期支持版本,会提供3年的支持(明年就出.net5),值得升级(吗). 目前主流的第三方包大多都已经提供了支持,2.x => 3.1还是变化不是特别多,EF ...

  2. iOS开发 - 超级签名实现之描述文件

    简介 因为最近企业签掉得太严重了,上头要求实现超级签进行游戏下载.故有了此文章,记录一下过程. 签名原理其实很简单,超级签名的技术就是使用个人开发者账号,将用户的设备当作开发设备进行应用分发.这也导致 ...

  3. spring boot 2 + shiro 实现权限管理

    Shiro是一个功能强大且易于使用的Java安全框架,主要功能有身份验证.授权.加密和会话管理.看了网上一些文章,下面2篇文章写得不错.Springboot2.0 集成shiro权限管理 Spring ...

  4. Java面试题_第三阶段(Spring、MVC、IOC、AOP、DI、MyBatis、SSM、struts2)

    1.1 何为Spring Bean容器?Spring Bean容器与Spring IOC 容器有什么不同吗? 答:1)用于创建bean对象,管理bean对象的那个容器. 2)Spring IOC 容器 ...

  5. [20191206]隐含参数_db_always_check_system_ts.txt

    [20191206]隐含参数_db_always_check_system_ts.txt --//今年年头我做tab$删除恢复时,遇到的问题,就是遇到延迟块清除的问题.参考链接:http://blog ...

  6. 剑指offer-39:平衡二叉树

    题目描述 输入一棵二叉树,判断该二叉树是否是平衡二叉树. 解题思路 在做这题是,我第一反应就是遍历两次二叉树.第一遍记录每个节点的深度,并将信息存入HashMap中,key = node,value ...

  7. 每天进步一点点----JS之比较运算符易错点

    1.字符串的比较 字符串也是可以比较的,字符串比较的asc码顺序:asc有128位,由7位二进制数表示,每个数对应的是一个字符.ASC码有ASC码1,由7位二进制1数表示:ASC2码又8位二进制数表示 ...

  8. badboy脚本录制工具的安装

    一.获取软件包 百度搜索badboy,或者直接访问官网:https://badboy.en.softonic.com/ 点击Download,下载安装包 或者从我的网盘提取: 链接:https://p ...

  9. PlayJava Day026

    1.泛型:指代任意对象类型 public class CC<T> {} C<Integer> c = new C<Integer>(1) ; 2.限制泛型:用于继承 ...

  10. http请求报400错误的原因分析

     在ajax请求后台数据时有时会报 HTTP 400 错误 - 请求无效 (Bad request);出现这个请求无效报错说明请求没有进入到后台服务里: 原因:1)前端提交数据的字段名称或者是字段类型 ...