本篇文章为大家带来基本排序方法的python实现
插入排序
分析:维护一个已排序好的数组,每次像这个数组中插入一个元素,使插入后的数组仍然有序,直到排序结束。是就地排序,不需要占用额外的空间。时间复杂度,随机度越高,插入排序的表现越好,在接近逆序的情况下表现较差。
def insertion(seq):
length = len(seq)
i = 1
while i < length:
data, k = seq[i], i
while seq[k - 1] > data and k > 0:
seq[k] = seq[k - 1]
k -= 1
seq[k] = data
i += 1
选择排序
分析:在n个元素的数组中,在前n项选择最大值,与第n项交换,然后在前n-1项选择最大值,与第n-1项交换,以此类推,知道排序结束。时间复杂度,在随机度较高是表现不如插入排序,在接近逆序时则明显好于插入排序。
def selection(seq):
length = len(seq)
i = 0
while i < length:
maxIndex, j = 0, 0
while j < length - i:
if seq[maxIndex] < seq[j]:
maxIndex = j
j += 1
seq[length - 1 - i], seq[maxIndex] = seq[maxIndex], seq[length - i - 1]
i += 1
冒泡排序
分析:从第一项开始,如果某一项比它的下一项大,则两者交换,然后进行下一项的比较,直至排序结束。时间复杂度。每次遍历可以设置一个exchange变量,如果某次遍历没有交换,则排序完成。所以,数据如果已经接近有序,则冒泡排序可能在较短的时间内排序完成,但在大多数情况下,冒泡排序的效率不如其他排序算法。
def bubble(seq):
length = len(seq)
i = 0
while i < length:
j = length - 1
exchange = False
while j > i:
if seq[j] < seq[j - 1]:
seq[j], seq[j - 1] = seq[j - 1], seq[j]
exchange = True
j -= 1
if not exchange:
break
i += 1
希尔排序
希尔排序可看作插入排序的改良,在插入排序时,插入操作每次与相邻的项进行比较,希尔排序则设置一个间隔数组,间隔数组依次减小直至1。在希尔排序中, 插入操作每次待插入的项与间隔x的数依次比较(x属于间隔数组,是当前的间隔)。希尔排序的性能要优于插入排序和选择排序,时间复杂度大致为
def shell(seq):
length = len(seq)
gap = length // 2
while gap > 0:
i = gap
while i < length:
data, k = seq[i], i
while seq[k - gap] > data and k > 0:
seq[k] = seq[k - gap]
k -= gap
seq[k] = data
i += gap
gap //= 2
本篇选择的间隔数组是[length//2,length//4,……,1]。
快速排序
快速排序体现了分治的思想,先在数组中选择一个介质,然后将比介质小的元素放在介质左,将比介质大的元素放在介质右,然后对介质左和介质右的数组递归调用快速排序,直至排序完成。快速排序的效率取决于介质的选取,如果介质总是能选中中位数,则效率很高,反之如果介质总是选择了最大值或者最小值,则效率很低。快速排序的效率要好于希尔排序。平均时间复杂度为。本篇总是选择数组的第一个数作为介质,因此这个快速排序在随机度较高时表现很好,在接近逆序时表现则很差(甚至会爆栈)。
def quick(seq):
def partition(seq, low, high):
pivot = seq[low] # 选择第一个元素作为基准值
while low < high:
while low < high and seq[high] >= pivot:
high -= 1
seq[low], seq[high] = seq[high], seq[low]
while low < high and seq[low] <= pivot:
low += 1
seq[low], seq[high] = seq[high], seq[low]
return low
def _quick_sort(seq, low, high):
if low < high:
pivot_index = partition(seq, low, high)
_quick_sort(seq, low, pivot_index - 1)
_quick_sort(seq, pivot_index + 1, high)
_quick_sort(seq, 0, len(seq) - 1)
为了防止爆栈情况的出现,在代码最前面设置一下最大递归调用次数
import sys
sys.setrecursionlimit(3000)
归并排序
归并排序的效率与快速排序相当,快速排序可以看作是用元素大小来划分数组,则归并排序可以看作是用索引值来划分数组,因此,归并排序总是从中间将数组分为两部分,然后将两部分排序后合并。对于每一部分,递归调用归并排序。归并排序的效率比较稳定,在随机度较高或者接近逆序的 时候都有比较好的表现,时间复杂度是
def merge(seq):
def _merge(m, n, p, q):
i, j, res = 0, 0, []
while i < p and j < q:
if m[i] < n[j]:
res.append(m[i])
i += 1
else:
res.append(n[j])
j += 1
res.extend(m[i:])
res.extend(n[j:])
return res
堆排序
堆排序使用了堆的数据结构,因为python的标准库heapq中有堆排序相关的算法,所以实现堆排序的代码可以很简洁。堆排序维护一个二叉堆(最小堆和最大堆都可以),然后每次弹出最小的元素。堆排序很稳定,在随机度高和接近逆序时表现都很好,时间复杂度是。
事先引入该模块
from heapq import *
def Pyheap(seq):
heapify(seq)
return [heappop(seq) for i in range(len(seq))]
二叉堆就是一颗完全二叉树,且保证根节点的元素总是小于子节点的元素(最小堆),根据数组建堆是线性时间复杂度,而弹出元素的时间复杂度则是。因为是完全二叉树,所以可以使用一个数组存储
class BinHeap:
def __init__(self):
self.data = [0]
self.length = 0
def percUp(self, index):
while index // 2 > 0:
if self.data[index] < self.data[index // 2]:
self.data[index], self.data[index // 2] = self.data[index // 2], self.data[index]
index //= 2
def getMin(self, index):
if index * 2 > self.length:
return None
elif index * 2 + 1 > self.length:
return index * 2
else:
if self.data[index * 2] <= self.data[index * 2 + 1]:
return index * 2
else:
return index * 2 + 1
def percDown(self, index):
while True:
minimum = self.getMin(index)
if not minimum:
break
if self.data[index] > self.data[minimum]:
self.data[index], self.data[minimum] = self.data[minimun], self.data[index]
index = minimum
def insert(self, data):
self.data.append(data)
self.length += 1
self.percUp(self.length)
def pop(self):
if self.length == 0:
raise IndexError('The heap is empty')
data = self.data[1]
self.delete(data)
return data
def delete(self, data):
index = self.data.index(data)
self.data[index] = self.data[self.length]
self.data.pop()
self.length -= 1
self.percDown(index)
def heapify(self, seq):
self.data = [0] + seq[:]
self.length = len(seq)
i = self.length // 2
while i > 0:
self.percDown(i)
i -= 1
def output(self):
print(self.data[1:])
def __bool__(self):
return bool(self.length)
def heap(seq):
heap, res = BinHeap(), []
heap.heapify(seq)
while heap:
res.append(heap.pop())
return res
以上是常见的排序算法,最后我们来测试一下它们的性能。
import random
num = 20000
l = [random.random() for i in range(num)]
random.shuffle(l)
funcs = [list.sort, Sort.Pyheap, Sort.quick, Sort.merge, Sort.heap, Sort.shell, Sort.insertion, Sort.selection,
Sort.bubble]
for func in funcs:
a = time()
func(l.copy())
b = time()
print(func.__name__,":",b-a)
在高随机度的情况下,结果如下
Pyheap : 0.01575779914855957
quick : 0.024766206741333008
merge : 0.02457451820373535
heap : 0.06661224365234375
shell : 6.078854084014893
insertion : 14.36155891418457
selection : 33.40423130989075
bubble : 48.74769401550293
在逆序的情况下
num = 20000
l = [random.random() for i in range(num)]
l.sort(reverse=True)
funcs = [list.sort, Sort.Pyheap, Sort.quick, Sort.merge, Sort.heap, Sort.shell, Sort.insertion, Sort.selection,
Sort.bubble]
for func in funcs:
a = time()
func(l.copy())
b = time()
print(func.__name__,":",b-a)
Pyheap : 0.003007173538208008
quick : 5.107363939285278
merge : 0.014906883239746094
heap : 0.05798840522766113
shell : 9.991580724716187
insertion : 17.121063709259033
selection : 12.812097787857056
bubble : 25.45111322402954
回顾往期: