概述
排序算法是计算机专业考研必考的内容,也是很多大厂面试的常考题目。排序算法包括比较类和非比较类,比较类通过元素的之间的大小比较决定相对位置,时间复杂度最快只能达到O(nlogn),所以被称为非线性时间比较类排序。非比较类不通过比较进行排序,能够以线性时间运行,也被称为线性时间非比较类排序。排序算法的性能包括时间复杂度、空间复杂度和稳定性。后面给出每种算法的思想、python3实现和性能分析。本文包括如下内容:
-
比较类排序
-
非比较类排序
-
leetcode相关题解
比较类排序
在比较类排序方法中,又根据算法的不同分为交换排序、插入排序、选择排序和归并排序,每种排序方法又可以分成两种,他们组成常见的八大排序算法。
- 交换排序:冒泡排序、快速排序
- 插入排序:简单插入排序、希尔排序
- 选择排序:简单选择排序、堆排序
- 归并排序:二路归并排序、多路归并排序
交换排序
交换排序包括冒泡排序和快速排序。
1. 冒泡排序
对数组的每一个数,依次与其后面的数组进行比较,如果比后面的数小,则交换两个数,两层循环,对于外层循环,每遍历一遍数据,最小的数就会被移到最前面。
def bubbleSort(arr):
for i in range(len(arr)):
for j in range(i+1,len(arr)):
if arr[i]>arr[j]:
tmp=arr[i]
arr[i]=arr[j]
arr[j]=tmp
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n 2 n^2 n2) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O(1) | 稳定 |
2. 快速排序
快速排序不同冒泡排序的逐一比较交换,它每次交换整块的内容。选择一个基准,将小于该数的所有数交换到它的左边,将大于该数的所有数交换到该数的右边。然后再对左右两边通过递归分别运行该策略。
def quickSort(arr,start,end):
if start>end:
return
base=start
low=start
high=end
while low<high:
while low<high and arr[high]>=arr[base]:
high-=1
while low<high and arr[low]<=arr[base]:
low+=1
if low<high: #每次交换比base位置大小不一致的元素
tmp=arr[low]
arr[low]=arr[high]
arr[high]=tmp
tmp=arr[low] #最后将base位置与当前low==high的位置交换
arr[low]=arr[base]
arr[base]=tmp
base=low
quickSort(arr,base+1,end)
quickSort(arr,start,base-1)
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n l o g 2 n nlog_2n nlog2n) | O( n 2 n^2 n2) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | 不稳定 |
插入排序
插入排序包括简单插入排序和希尔排序。
- 简单插入排序
简单插入排序每次将一个数目插入一个排好序的序列。启始的序列包含一个元素。然后逐步插入其中,插入时将待插入的元素依次从后到前比较,直到找到比它小的元素为止。
def insertSort(arr):
for i in range(1,len(arr)):
j=i
value=arr[i]
while j>0 and arr[j-1]>value:
arr[j]=arr[j-1]
j-=1
arr[j]=value
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n n n) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O( 1 1 1) | 稳定 |
- 希尔排序
希尔排序是对简单插入排序的优化。不同与简单选择排序,希尔排序分成不断的段进行选择排序段间隔的大小通常使用长度的1/2,并且依次递减。简单插入排序相当于gap=1的希尔排序。
def shellSort(arr):
gap=len(arr)//2
while gap>0:
for i in range(gap):
gapInsert(arr,i,gap)
gap=gap//2
def gapInsert(arr,start,gap):
for i in range(start+gap,len(arr),gap):
j=i
value=arr[i]
while arr[j-1]>value and j>start:
arr[j]=arr[j-gap]
j=j-gap
arr[j]=value
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n n n) | O( n 2 n^2 n2) | O( n 1.3 n^{1.3} n1.3) | O( 1 1 1) | 不稳定 |
选择排序
选择排序包括直接选择排序和堆排序。
- 直接选择排序
直接选择排序的思想很简单:每次从待排序的序列中选择最小元素进行排序。
def selectSort(arr):
for i in range(len(arr)):
minIndex=i
for j in range(i+1,len(arr)):
if arr[j]<arr[minIndex]:
minIndex=j
tmp=arr[minIndex]
arr[minIndex]=arr[i]
arr[i]=tmp
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n 2 n^2 n2) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O( 1 1 1) | 不稳定 |
- 堆排序
堆排序利用二叉树的数据结构通过不断构建大顶堆获选择当前最大元素放到序列的末尾进行排序。构建堆的过程就是不断选择的过程。
def heapBuild(arr,n,i):
largest=i
l=2*i+1
r=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]
heapBuild(arr,n,largest)
def heapSort(arr):
n=len(arr)
for i in range(n-1,-1,-1): #第一次建堆
heapBuild(arr,n,i)
for i in range(n-1,0,-1): #每次将堆顶元素与当前最后元素互换
arr[i],arr[0]=arr[0],arr[i]
heapBuild(arr,i,0)
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( 1 1 1) | 不稳定 |
归并排序
- 二路归并排序
归并排序是分治算法的应用。将有序子序列不断合并,得到完全有序序列。二个有序的合并被称为2路归并。
def mergeSort(alist):
if len(alist)<=1:
return alist
mid=len(alist)//2
left=mergeSort(alist[:mid])
right=mergeSort(alist[mid:])
merge_result=merge(left,right)
return merge_result
def merge(alist1,alist2): #合并两个有序队列
result=[]
p1,p2=0,0
while p1<len(alist1) and p2<len(alist2):
if alist1[p1]<=alist2[p2]:
result.append(alist1[p1])
p1+=1
elif alist1[p1]>alist2[p2]:
result.append(alist2[p2])
p2+=1
result.extend(alist1[p1:])
result.extend(alist2[p2:])
return result
|O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n n n) | 稳定|
非比较类排序
非比较类排序包括计数排序、桶排序和基数排序。
- 计数排序
计数排序通过空间换时间,待排序的序列最大值有多大,就申请最大值大小加1(为了存储最大值)空间的序列存储每个元素出现的次数,存储序列的下标就是待排序数组中的数。计数排序要求输入的数据必须是有确定范围的整数。
def countingSort(arr):
max_num=max(arr)
bucket=[0]*(max_num+1)
for i in arr:
bucket[i]+=1
sort_num=[]
for i,j in enumerate(bucket):
for k in range(j):
sort_num.append(i)
return sort_num
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n + k n+k n+k) | O( n + k n+k n+k) | O( n + k n+k n+k) | O( n + k n+k n+k) | 稳定 |
2. 桶排序
桶排序是计数排序的高阶版本,计数排序可以看成桶大小为1的特殊桶排序。通过将数组分配到不同的桶中,对每个桶进行排序后再归一到一起。值得注意的是下表的计算:
桶的个数为:最大值-最小值//桶大小+1;对于某个具体的数x,它属于的桶的下标为
x-最小值//桶大小+1-1=x-最小值//桶大小 (减1因为下表从0开始)
def insertBuckets(arr,value):
arr.append(value)
currentIndex=len(arr)-1
while currentIndex>0 and arr[currentIndex-1]>value:
arr[currentIndex]=arr[currentIndex-1]
currentIndex-=1
arr[currentIndex]=value
def bucketSort(arr,bucket_size):
min_value,max_value=min(arr),max(arr)
bucket_num=(max_value-min_value)//bucket_size+1
buckets=[[] for i in range(bucket_num)]
for num in arr:
insertBuckets(buckets[(num-min_value)//bucket_size],num)
result=[]
for bucket in buckets:
result.extend(bucket)
return result
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n n n) | O( n 2 n^2 n2) | O( n + k n+k n+k) | O( n + k n+k n+k) | 稳定 |
- 基数排序
基数排序按照排序元素的位数分位排序,每次排序又用到了基数排序方法。
def bucketSort(arr,bucket_size):
min_value,max_value=min(arr),max(arr)
bucket_num=(max_value-min_value)//bucket_size+1
buckets=[[] for i in range(bucket_num)]
for num in arr:
insertBuckets(buckets[(num-min_value)//bucket_size],num)
result=[]
for bucket in buckets:
result.extend(bucket)
return result
def radixSort(arr):
i=0
max_num=max(arr)
j=len(str(max_num))
while i<j:
buckets=[[] for _ in range(10)]
for ele in arr:
buckets[ele//(10**i)%10].append(ele)
arr.clear()
for b in buckets:
arr.extend(b)
i+=1
最优时间复杂度 | 最差时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
O( n × k n×k n×k) | O( n × k n×k n×k) | O( n × k n×k n×k) | O( n + k n+k n+k) | 稳定 |
leetcode 题解
此题是排序题的变体,只需要获得第k个数即可。通过堆排序每次获取一个最大值,获取到第k次即可。
class Solution:
def heapbuild(self,arr,n,i):
largest=i
left=2*i+1
right=2*i+2
if left<n and arr[left]>arr[largest]:
largest=left
if right<n and arr[right]>arr[largest]:
largest=right
if largest!=i:
arr[largest],arr[i]=arr[i],arr[largest]
self.heapbuild(arr,n,largest)
def findKthLargest(self, nums: List[int], k: int) -> int:
n=len(nums)
for i in range(n-1,-1,-1):
self.heapbuild(nums,n,i)
for i in range(n-1,n-k,-1):
nums[i],nums[0]=nums[0],nums[i]
self.heapbuild(nums,i,0)
return nums[0]
此题应用计数排序的思想,通过记录每个数字出现的次数,然后对次数进行排序,最后取次数排序前k个最大的结果。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
numDict={}
for n in nums:
if n in numDict:
numDict[n]+=1
else:
numDict[n]=1
sort_nums=sorted(numDict.items(),key=lambda x:x[1],reverse=True)
return [v[0] for v in sort_nums[:k]]
这题仍然可以使用计数排序的方法。
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
arr=[0]*3
for n in nums:
arr[n]+=1
result=[]
for i in range(len(arr)):
if arr[i]!=0:
for j in range(arr[i]):
result.append(i)
for i in range(len(result)):
nums[i]=result[i]
参考:
https://www.cnblogs.com/onepixel/articles/7674659.html