一、排序简介
排序:将一组无序的记录序列按照某种逻辑顺序重新啊脾虚,调整为有序的记录序列的过程。
根据排序过程中中涉及存储器的不同,将排序算法分为两大类:
- 内部排序算法:参加排序的数据量不太大,在排序过程中将全部记录存放在内存处理中;
- 外部排序算法:参加排序的数据量较大时,以至于内存不足以一次存放全部记录,需要通过内存与外村之间的数据交换来达到排序目的。
根据排序算法中前后拥有相同值记录的相对位置是否相同,将排序算法分为两大类:
- 稳定性排序算法:对于值相同的两个元素,排序前后的先后次序不变;
- 非稳定性排序算法:对于值相同的两个元素,排序前后的先后次序改变。
按照算法时间复杂度划分常见的十种排序算法:
- O ( n 2 ) O(n^2) O(n2):冒泡排序、选择排序、插入排序
- O ( n ∗ l o g 2 n ) O(n*log_2n) O(n∗log2n):希尔排序、归并排序、快速排序、堆排序
- O ( n ) O(n) O(n):计数排序、桶排序、基数排序
二、常见排序算法
2.1冒泡排序
冒泡排序法通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。第 i ( i = 1 , 2 , . . . ) i(i=1,2,...) i(i=1,2,...)趟排序时从序列中前 n − i + 1 n-i+1 n−i+1个元素的第1个元素开始,相邻两个元素进行比较,若前者大于后者交换位置。
class Solution:
def bubbleSort(self,arr):
for i in range(len(arr)):
for j in range(len-i-1):
if arr[j]>arr[j+1]:
arr[j],arr[j+1]=arr[j+1],arr[j]
return arr
def sortArray(self,num:List[int])->List[int]:
return self.bubbleSort(nums)
- 冒泡排序最好的情况时初始序列升序排列,只需经过一趟 n − 1 n-1 n−1次元素之间的比较,并且不移动元素遍历完成。此时算法的时间复杂度为 O ( n ) O(n) O(n)。最差的情况位降序排列,此时需要经过 n − 1 n-1 n−1躺排序,总共进行 ∑ i = 1 n ( i − 1 ) = n ( n − 1 ) 2 \sum_{i=1}^n(i-1)=\frac{n(n-1)}{2} ∑i=1n(i−1)=2n(n−1)次元素之间的比较,此时算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 冒泡排序适用于数据量较小的情况。
- 不会改变值相同元素的相对位置,属于稳定性排序算法。
2.2选择排序
在每一趟排序中,从剩余未排序元素中选择一个最小的元素,与未排好序的元素最前面的那个元素交换位置。先保存下标遍历完第 i ( i = 1 , 2 , . . . ) i(i=1,2,...) i(i=1,2,...)趟后再交换。
class Solution:
def selectionSort(self,arr):
for i in range(len(arr)-1):
min_i=i
for j in range(i+1,len(arr)):
if arr[j]<arr[min_i]:
min_i=j
#找到最小值之后交换位置,若第i个已经是最小值则不需要交换
if i!=min_i:
arr[i],arr[min_i]=arr[min_i],arr[i]
return arr
def sortArray(self,nums:List[int])->List[int]:
return self.selectionSort(nums)
2.3 插入排序
将整个序列且氛围两部分,每一趟排序中将后面剩余未排序元素中的第一个元素插入到前面排序元素中的合适位置上。
class Solution:
def insertSort(self,arr):
for i in range(1,len(arr)):
temp=arr[i]
j=i
# 倒序遍历已排序数组,将比temp大的值后移
while j>0 and arr[j-1]>temp:
arr[j]=arr[j-1]
j-=1
arr[j]=temp
return arr
def sortArray(self,nums:List[int])->List[int]:
return self.insertionSort(nums)
- 插入排序最好的情况时初始序列升序排列,只需经过一趟 n − 1 n-1 n−1次元素之间的比较,并且不移动元素遍历完成。此时算法的时间复杂度为 O ( n ) O(n) O(n)。最差的情况位降序排列,此时需要经过 n − 1 n-1 n−1躺排序,总共进行 ∑ i = 2 n ( i ) = n ( n − 1 ) 2 \sum_{i=2}^n(i)=\frac{n(n-1)}{2} ∑i=2n(i)=2n(n−1)次元素之间的比较,此时算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 插入排序属于稳定性排序方法。
2.4 希尔排序
基本步骤如下:
- 首先确定一个元素间隔数 g a p gap gap,分别将所有位置相隔 g a p gap gap的元素视为一个子序列,在各个子序列中采用某种排序方法进行排序。
- 然后减少间隔数,并重新将整个序列按照新的间隔数分成若干子序列,再分别对各个子序列进行排序,直到间隔数 g a p = 1 gap=1 gap=1。
class Solution:
def shellSort(self,arr):
size=len(arr)
gap=size//2
while gap>0:
for i in range(gap,size):
temp=arr[i]
j=i
#对同一子序列的相邻元素进行排序
while j>=gap and arr[j-gap]>temp:
arr[j]=arr[j-gap]
j-=gap
arr[j]=temp
gap=gap//2
return arr
def sortArray(self,nums:List[int])->List[int]:
return self.shellSort(nums)
- 希尔排序的排序总趟数为 l o g 2 = n log_2=n log2=n
- 希尔排序的时间复杂度在 O ( l o g 2 n ) O(log_2n) O(log2n)和 O ( n 2 ) O(n^2) O(n2)之间
- 希尔排序是一种非稳定性排序
3.5 归并排序
采用经典分治策略,先地柜地将当前序列平均分为两半,然后将有序序列两两合并最终合并成为一个有序序列。
class Solution:
def merge(self,left_arr,right_arr):
arr=[]
while left_arr and right_arr:
if left_arr[0]<=right_arr[0]:
arr.append(left_arr.pop[0])
else:
arr.append(right_arr.pop[0])
# 两个数组中有一个已经遍历完了,将另一个数组的元素剩余的元素依次弹出
while left_arr:
arr.append(left_arr.pop[0])
while right_rr:
arr.append(right_arr.pop[0])
return arr
def mergeSort(self,arr):
size=len(arr)
if size<2:
return arr
mid=size//2
left_arr,right_arr=arr[0:mid],arr[mid:]
# 划分为两个数组,并分别对其归并排序后合并
return self.merge(self.mergeSort(left_arr),mergeSort(right_arr))
def sortArray(self,nums:List[int])->List[int]:
return self.mergeSort(nums)
- 时间复杂度为趟数*每一趟归并的时间复杂度,因此总时间复杂度为 O ( n ∗ l o g 2 n ) O(n*log_2n) O(n∗log2n)
- 归并排序的空间复杂度为 O ( n ) O(n) O(n),需要和原数组相同大小的辅助存储空间
mergeSort(left_arr,right_arr)
保证了归并排序是稳定性排序算法
2.6 快速排序
通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小。然后递归地排列两个子序列,以达到整个序列有序
import random
class Solution:
# 随机生成基准值,并进行前后元素调整
def randomPartition(self,arr:[int],low:int,high:int):
i=random.randint(low,high)
arr[i],arr[high]=arr[high],arr[i]
return self.partition(arr,low,high)
# 调整基准值位置,比其小移动到前面,比其大移动到后面
def partition(self,arr:[int],low:[int],high:[int]):
i=low-1
pivot=arr[high]
for j in range(low,high):
if arr[j]<=pivot
i+=1
arr[i],arr[j]=rr[j],arr[i]
arr[i+1],arr[high]=arr[high],arr[i+1]
return i+1
# 随机挑选基准值对前后两个子序列递归进行排序
def quickSort(self,arr,low,high):
if low<high:
pi=self.randomPartition(arr,low,high)
self.quickSort(arr,low,pi-1)
self.auickSort(arr,pi+1,high)
return arr
def sortArray(self,nums:List[int])->List[int]:
return self.quickSort(nums,0,len(nums)-1)
- 参加排序的元素初始时已经有序的情况下,时间复杂度为 O ( n 2 ) O(n^2) O(n2);若每趟排序后分解元素正好在序列中间,时间复杂度为 O ( n ∗ l o g 2 n ) O(n*log_2n) O(n∗log2n)
- 快速排序是一种非稳定性排序算法
2.6堆排序
借助堆结构所设计的排序算法:将数组转化为大顶堆,使得最大值处于序列的第1个位置,交换序列的最大值元素与最后一个元素的位置。重复从大顶堆中取出数值最大的节点,并让剩余的堆顶维持大顶堆性质。
构造新的堆积具体步骤为:从根节点开始自上而下调整节点的位置,使其称为堆积:把序号为 i i i的节点与其左子树节点(序号 2 i 2i 2i)、右子树节点(序号 2 i + 1 2i+1 2i+1)中值最大的节点交换位置。
class Solution:
#调整为大顶堆,遍历整个序列
def heapify(self,arr:[int],index:int,end:int):
left=index*2+1
right=left+1
while left<=end:
#当前为非叶子节点
max_index=left
if arr[left]>arr[max_index]:
max_index=left
if arr[right]>arr[max_index]:
max_index=right
if index==max_index:
break
arr[index],arr[max_index]=arr[max_index],arr[index]
#继续调整子树
index=max_index
left=index*2+1
right=left+1
#初始化大顶堆
def buildMaxHeap(self,arr[int]):
size=len(arr)
for i in range((size-2)//2,-1,-1):
self.heapify(arr,i,size-1)
return arr
# 升序堆排序:
# 1.先建立大顶堆
# 2.让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值
# 3.再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值
# 4.一次腿累,直到最后一个元素交换完毕
def maxHeapSort(self,arr[int]):
self.buldMaxHeap(arr)
size=len(arr)
for i in range(size):
arr[0],arr[size-i-1]=arr[size-i-1],arr[0]
self.heapify(arr,0,size-i-2)
return arr
def sortArray(self,nums:List[int])->List[int]:
return self.maxHeapSort(nums)
- 堆积排序主要花费在构造初始堆积和排序过程中调整大顶堆两个方面
- 构造初始顶堆时,每一层节点最多有 2 i − 1 2^{i-1} 2i−1个,adjust算法最大移动距离为 2 i − 1 ∗ ( d − i ) 2^{i-1}*(d-i) 2i−1∗(d−i),时间总和为 ∑ i = d − 1 1 2 i − 1 ( d − i ) < 2 n \sum_{i=d-1}^1 2^{i-1}(d-i)<2n ∑i=d−112i−1(d−i)<2n,时间复杂度为 O ( n ) O(n) O(n)
- 排序过程中,adjust算法中节点移动最大距离为完全二叉树的深度 l o g 2 n + 1 log_2 n+1 log2n+1
- 因此堆积排序的时间复杂度为 O ( n ∗ l o g 2 n ) O(n*log_2n) O(n∗log2n)
- 属于非稳定性排序算法,不适合在链表中是实现
3.8计数排序
使用一个额外的数组,其中 c o u n t s [ i ] counts[i] counts[i]是待排序数组 a r r arr arr中值等于 i i i的元素个数,然后根据数组 c o u n t s counts counts来将 a r r arr arr中的元素拍到正确的位置。
class Solution:
def countingSort(self,arr):
arr_min,arr_max=min(arr),max(arr)
size=arr_max-arr_min+1
counts=[0 for _ in range(size)]
for num in arr:
counts[num-arr_min]+=1
for j in range(1,size):
counts[j]+=counts[j-1]
res=[0 for _ in range(len(arr))]
for i in range(len(arr)-1,-1,-1):
res[counts[arr[i]-arr[min]-1]=arr[i]
counts[arr[i]-arr_min]-=1
return res
def sortArray(self.nums:List[int])->List[int]:
return self.countingSort(nums)
- 时间复杂度为 O ( n + k ) O(n+k) O(n+k)
- 计数的数组长度取决于待排序数组中数据的范围,对于数据范围很大的数组需要大量的时间和内存
- 计数排序是稳定排序算法
2.9 桶排序
将区间划分为 n n n个相同大小的子区间,每个区间称为一个桶,对每个桶内的元素单独排序(插入、归并、快排等算法),最后按照顺序将桶内元素合并起来。
class Solution:
def insertionSort(self,arr):
for i in range(1,len(arr)):
temp=arr[i]
j=i
while j>0 and arr[j-1]>temp:
arr[j]=arr[j-1]
j-=1
arr[j]=temp
return arr
def bucketSort(self,arr,bucket_size=5):
arr_min,arr_max=min(arr),max(arr)
bucket_count=(arr_max-arr_min)//bucket_size+1
bucket=[[] for _ in range(bucket_count)]
for num in arr:
buckets[(num-arr_min)//bucket_size].append(num)
res=[]
for bucket in buckets:
self.insertionSort(bucket)
res.extend(bucket)
return res
def sortArray(self,nums:List[int])->List[int]:
return self.bucketSort(nums)
- 桶排序的时间复杂度接近于 O ( n ) O(n) O(n)
- 空间复杂度是 o ( m + n ) o(m+n) o(m+n)
2.10 基数排序
基数排序算法最常采用的是最低位优先法:遍历数组元素获得数组最大元素及其位数。分别以个位元素、十位元素、百位元素为索引,进行排序并合并数组,最终完成排序。
class Solution:
def radixSort(self,arr):
size=len(str(max(arr)))
for i in range(size):
buckets=[[] for _ in range(10)]
for num in arr:
buckets[num//(10**i)%10].append(num)
arr.clear()
for bucket in buckets:
for num in bucket:
arr.append(num)
return arr
def sortArray(self,num:List[int])->List[int]:
return self.radixSort(nums)
- 空间复杂度是 0 ( n + k ) 0(n+k) 0(n+k)