已整理:快速排序、归并排序、冒泡排序、选择排序、插入排序、希尔排序、堆排序。
桶排序、计数等待更新。
1、快速排序,时间O(nlogn),空间O(1)。不稳定。
特点分析:是一种排序速度非常快的排序方法,该算法之所以非常快,是因为高度优化的内部循环,该算法在实际应用中非常广泛。
时间复杂度分析:当数组原本有序时是最差的情况,O(n^2),因此排序前先将数组随机打乱就是防止输入为有序数组而导致排序效率低下;最好的情况是选的中轴数恰好将数组平分,此时是O(nlogn)。参考快速排序(基础版)
快排之所以快,就是因为它高度优化的内部循环(分割),它既不像归并那样需要辅助数组来回复制元素,也不像堆排序无法利用缓存并且有许多无用的比较,并且最坏情况可以采用一些方法避免。
快速排序使用“分而治之”的方法,有点二分法的味道。尤其注意检查边界!以下是步骤:首先任意选择数组中的一个元素作为中轴元素;然后将大于或者等于中轴元素的元素放在右边,小于中轴元素的元素放在左边。这两个过程(选元素和调整位置)称为Partition(分割)。实现时可以设置双边哨兵从数组两边向内走,也可以设置单边哨兵。
假设选则最后一个数做为中轴元素,设置单边哨兵。首先设置一个small指针指向所有小于中轴的数,初始化为-1。从头开始遍历数组,每找到一个比中轴小的数,small++同时把这个小数放到small位置,即交换该小数和small位置原来的数。遍历完后,small位置(包含)之前的所有数[0,small]都是小于中轴的数,small之后的所有数都是≥中轴的数,把中轴元素跟大数的第一个数(small++索引处)交换位置,即完成一次分割。递归:调用partition函数得到中轴位置,对中轴两边的数组继续调用自身。终止条件为数组长度≤1。——quichsort函数。
# 实现1:剑指优化版,原地改变nums。
class Solution:
def sortArray(self, nums):
# 左边哨兵
def partition(nums, start, end):
small = start-1
for j in range(start, end):
if nums[j] < nums[end]:
small += 1
nums[small], nums[j] = nums[j], nums[small]
small += 1
nums[small], nums[end] = nums[end], nums[small]
return small
def quicksort(nums, start, end):
if start
index = partition(nums, start, end)
quicksort(nums, start, index-1)
quicksort(nums, index+1, end)
quicksort(nums, 0, len(nums)-1)
return nums
# 双边哨兵,只是partition内部实现不同。
# def partition(nums, start, end):
# base = nums[start]
# l, r = start+1, end
# while l<=r:
# while r>start and nums[r]>= base:
# r-=1
# while l<=end and nums[l] < base:
# l += 1
# if l>=r:
# break
# nums[l], nums[r] = nums[r], nums[l]
# nums[start], nums[r] = nums[r], nums[start]
# return r
#实现2:原地改变原数组nums,只需写一个内部函数,递归地对base元素的左右两边数组进行排序。
# 单边哨兵
import random
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
# 原地打乱
random.shuffle(nums)
def fastSort(nums, l, r):
if r-l <= 0:
return
small = l-1
for j in range(l, r):
if nums[j] < nums[r]:
small += 1
nums[small], nums[j] = nums[j], nums[small]
small += 1
nums[small], nums[r] = nums[r], nums[small]
fastSort(nums, l, small-1)
fastSort(nums, small+1, r)
fastSort(nums, 0, len(nums)-1)
return nums
#实现3:不改变原数组nums,每次递归需要开辟left,right空间,最后返回排序好的新数组。
import random
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
# 原地打乱
random.shuffle(nums)
def fastSort(nums):
if len(nums)<=1:
return nums
# left = right = [] # 错误写法:二者将同时改变
left, right = [], []
for num in nums[1:]:
if num
left.append(num)
else:
right.append(num)
return fastSort(left) + [nums[0]] + fastSort(right)
return fastSort(nums)
2、归并排序,时间O(nlogn),空间O(n)。稳定。
归并排序是建立在归并操作的一种高效的排序方法,该方法采用了分治的思想,比较适用于处理较大规模的数据,但比较耗内存。复杂度计算过程和下图来自归并排序治:治理,这里就是将数组排序。不断地分直到只有一个元素的时候,这个时候就不治而治了。
归并过程中可同时计算逆序对。先把数组二分,直到每个组只剩下一个元素(递归终止)。然后开始合并两个组。
合并过程中,首先新建一个辅助数组new,左组和右组分别从最小的元素a、b开始比较,每次将较小的元素pop出并添加到new中。合并后返回新数组。
递归的合并数组。
#分成两个函数,思路更清晰。
class Solution:
def sortArray(self, nums):
# 递归(‘分’)的终止条件:只剩下一个元素,无须再分直接返回。
if len(nums)<=1:
return nums
else:
mid = len(nums)>>1
left = self.sortArray(nums[:mid])
right = self.sortArray(nums[mid:])
return self.merge(left, right)
# return self.merge(self.sortArray(nums[:mid]), self.sortArray(nums[mid:]))
def merge(self, left, right):
new = []
i,j = 0, 0
while i
if left[i]<= right[j]:
new.append(left[i])
i += 1
else:
new.append(right[j])
# c += len(left) # 如果要计算逆序对,只需定义一个c在此处更新。
j += 1
new += left[i:] + right[j:]
return new
# 另一种合并方法
class Solution:
def sortArray(self, arr):
if len(arr) == 1: return arr
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
return self.merge(self.sortArray(left), self.sortArray(right))
def merge(self, left, right):
res = []
while len(left) > 0 and len(right) > 0:
# 左右两个数列第一个最小放前面
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
# 只有一个数列中还有值,直接添加
res += left
res += right
return res
3、冒泡排序,时间O(n^2),空间O(1)。稳定。
遍历n次数组。每次从索引1开始,判断是否比前一个元素小,是则交换位置,否则继续向后遍历判断;等同于每趟遍历选出当前无序数组的最大值,放在后面已排序值的最前面。
# 超时,通过80%
class Solution:
def sortArray(self, nums):
# 能够访问到的最大索引
i = len(nums)-1
while i>=1:
for j in range(1, i+1):
if nums[j] < nums[j-1]:
nums[j], nums[j-1] = nums[j-1], nums[j]
i -= 1
return nums
# 超时,通过90%。改进版,如果有一次遍历没有交换元素,说明已经排好序了,直接返回即可。
class Solution:
def sortArray(self, nums):
# 能够访问到的最大索引
i = len(nums)-1
while i>=1:
flag = False
for j in range(1, i+1):
if nums[j] < nums[j-1]:
nums[j], nums[j-1] = nums[j-1], nums[j]
flag = True
if not flag:
return nums
i -= 1
return nums
4、选择排序,时间O(n^2),空间O(1)。不稳定。最直观、简单、粗暴的排序方法。遍历n-1次,每次把最小值放在前面已排序数的最末尾。
由于选择元素之后会发生交换操作,所以有可能把前面的元素交换到后面,所以不稳定。
实现过程:先默认i处是最小值,向后遍历,遇到更小的数就更新最小索引,最后交换位置。
# 超时,通过90%。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
for i in range(len(nums)-1):
# 先默认最小位置索引是i
minidx = i
for j in range(i+1, l):
if nums[j]< nums[minidx]:
minidx = j
nums[i], nums[minidx] = nums[minidx], nums[i]
return nums
5、直接插入排序,时间O(n^2),空间O(1)。稳定。
适用处理数据量比较少或者部分有序的数据,是稳定的。所谓直接插入排序,就是把未排序的元素一个一个地插入到有序的集合中,插入时把有序集合从后向前扫一遍,找到合适的位置插入。
代码实现:视第一个元素为有序数组,从索引1开始遍历,首先保存待插入元素值tmp,从有序数组的末尾开始比较,当有序数组内的元素比tmp大时,则将该元素向后移一步(腾出位置给tmp),否则跳出循环并插入tmp。
# 超时,通过90%
class Solution:
def sortArray(self, nums):
n = len(nums)
for i in range(1, n):
temp = nums[i]
j = i - 1
while j >= 0 and nums[j] > temp:
nums[j+1] = nums[j]
j -= 1
nums[j+1] = temp
return nums
# 实现2:超时,通过90%。有点冒泡的感觉。
# 将待插入元素从有序数组的末尾开始比较,两两交换位置直到正确插入到有序数组中。
class Solution:
def sortArray(self, nums):
for i in range(1, len(nums)):
for j in range(i, 0, -1):
if nums[j] >= nums[j-1]:
break
# 待插入是小数,则从有序数组末尾进入,寻找正确位置。
nums[j-1], nums[j] = nums[j], nums[j-1]
return nums
6、希尔排序,时间最坏O(n^2),空间O(1)。不稳定。
是对直接插入排序的一种改进,对于中等规模、基本无序的数据的性能表现还不错。
虽然插入排序是稳定的,但是希尔排序在插入的时候是跳跃性插入的,有可能破坏稳定性。首先它把较大的数据集合分割成若干个小组(逻辑上分组),然后对每一个小组分别进行插入排序,此时,插入排序所作用的数据量比较小(每一个小组),插入的效率比较高。希尔排序的复杂度和增量序列{1, 2, 4, ...}是相关的,分析起来很复杂。参考希尔排序mp.weixin.qq.com
具体过程:每次将数组分成gap(增量)组,在每个组内使用直接插入排序。直接插入排序可以看成是gap=1时的希尔排序。
# 勉强通过!
class Solution:
def sortArray(self, nums):
n = len(nums)
gap = n//2
while gap>0:
# 对各个组进行插入的时候并不是先对一个组进行排序完再对另一个组进行排序,
# 而是轮流对每个组进行插入排序
for i in range(gap, n):
self.shell_sort(nums, gap, i)
gap //= 2
return nums
def shell_sort(self, arr, gap, i):
tmp = arr[i]
j = i - gap
while j>=0 and arr[j]>tmp:
arr[j+gap] = arr[j]
j -= gap
arr[j+gap] = tmp
7、堆排序,时间O(nlogn),空间O(n)。不稳定。
二叉堆的引入参考可以管理时间的二叉堆,写得非常好。
适合需要频繁插入数据、取出最大/小值的场景。
①从零开始实现堆类:主要是插入和删除两个操作。每次插入时,先将元素添加到数组末尾,然后不断调用swim(上浮)函数,如果插入元素小于父节点则交换;每次删除时,先将根节点元素和末尾元素交换,再弹出末尾元素,然后不断调用sink(下沉)函数,如果根节点元素大于父节点则交换。令索引0处为根节点,[0,1,2,3,4,...]令索引1处为根节点,[*,1,2,3,4,...]
②排序:参考堆排序
方法1:建一个空堆,遍历无序数组的元素,一一入堆;然后一一出堆得到排序数组。不修改原数组,空间O(n)。
方法2:将无序数组视为完全二叉树,用下沉(sink)操作对其自底向上构造一个堆。每个叶子节点可以视为一个大小为一的堆,我们可以自底向上从非叶子节点开始每层从右至左给每个节点都调用下沉(sink)方法,这样以当前节点为根节点的树就变为堆了。(思考:最后一层非叶子节点索引如何得到?即最后一个叶子结点的父节点,(len(nums)-1-1)>>1)。注意:在下沉(sink)某个节点的时候,这个节点的两个孩子必须是堆。修改原数组,空间O(n)。
Tips:在深度为n的满二叉树中,叶子节点数为2^(n-1),内部节点数为(2^(n-1))-1。
*方法3:使用库函数分别实现上述两种方法。调用python最小堆库heapq。
# 从零开始实现最小堆类 + 方法1排序
class min_heap:
def __init__(self):
self.heap = []
def __len__(self):
return len(self.heap)
def insert_heap(self, n):
self.heap.append(n)
self.swim(len(self.heap)-1)
def swim(self, child):
parent = (child-1)>>1
if parent>=0 and self.heap[parent] > self.heap[child]:
self.heap[parent], self.heap[child] = self.heap[child], self.heap[parent]
self.swim(parent)
def del_top(self):
if not self.heap:
return None
self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0]
out = self.heap.pop()
self.sink(0)
# self.print_heap()
return out
def sink(self, parent):
heap_size = len(self.heap)
mini_idx = self.min_index(parent, heap_size)
# print('\t', self.print_heap(),mini_idx)
if mini_idx != parent:
self.heap[parent], self.heap[mini_idx] = self.heap[mini_idx], self.heap[parent]
self.sink(mini_idx)
def min_index(self, parent, heap_size):
left_child = (parent<<1) + 1
right_child = left_child + 1
mini_idx = parent
if left_child< heap_size and self.heap[mini_idx]>self.heap[left_child]:
mini_idx = left_child
if right_child self.heap[right_child]:
mini_idx = right_child
return mini_idx
def print_heap(self):
print(self.heap)
class Solution:
def sortArray(self, nums):
heap = min_heap()
out = []
for n in nums:
heap.insert_heap(n)
# heap.print_heap()
while len(heap)>0:
out.append(heap.del_top())
return out
# 方法1调用库函数:新建堆+出堆
import heapq
class Solution:
def sortArray(self, nums):
h = []
for n in nums:
heapq.heappush(h, n)
out = []
while len(h)>0:
out.append(heapq.heappop(h))
return out
# 方法2调用库函数:原地建堆+出堆
import heapq
class Solution:
def sortArray(self, nums):
heapq.heapify(nums)
out = []
while len(nums)>0:
out.append(heapq.heappop(nums))
return out