[总结]排序算法分析与实现
这里主要总结了常用的排序算法以及python实现。
1.冒泡排序
依次比较相邻两元素,若前一元素大于后一元素则交换之,直至最后一个元素即为最大;然后重新从首元素开始重复同样的操作,直至倒数第二个元素即为次大元素;依次类推。如同水中的气泡,依次将最大或最小元素气泡浮出水面。
时间复杂度:\(O(N^2)\) 稳定性:稳定
class Bubble(object):
def sort(self,nums):
for i in range(len(nums)-1): # 这个循环负责设置冒泡排序进行的次数
for j in range(len(nums)-i-1): # j为列表下标
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
return nums
2.选择排序
首先初始化最小元素索引值为首元素,依次遍历待排序数列,若遇到小于该最小索引位置处的元素则刷新最小索引为该较小元素的位置,直至遇到尾元素,结束一次遍历,并将最小索引处元素与首元素交换;然后,初始化最小索引值为第二个待排序数列元素位置,同样的操作,可得到数列第二个元素即为次小元素;以此类推。
时间复杂度:\(O(N^2)\) 稳定性:不稳定
class Selector(object):
def sort(self,nums):
for i in range(len(nums)):
min = i
for j in range(i+1,len(nums)):
if nums[j]<nums[min]:
min = j
nums[i],nums[min] = nums[min],nums[i]
return nums
3.插入排序法
数列前面部分看为有序,依次将后面的无序数列元素插入到前面的有序数列中,初始状态有序数列仅有一个元素,即首元素。在将无序数列元素插入有序数列的过程中,采用了逆序遍历有序数列,相较于顺序遍历会稍显繁琐,但当数列本身已近排序状态效率会更高。
时间复杂度:\(O(N^2)\) 稳定性:稳定
class Insert(object):
def sort(self,nums):
for i in range(len(nums)):
for j in reversed(range(1,i+1)):
if nums[j]>nums[j-1]:
break
nums[j],nums[j-1] = nums[j-1],nums[j]
return nums
4.希尔排序
插入排序的改进版。为了减少数据的移动次数,在初始序列较大时取较大的步长,通常取序列长度的一半,此时只有两个元素比较,交换一次;之后步长依次减半直至步长为1,即为插入排序,由于此时序列已接近有序,故插入元素时数据移动的次数会相对较少,效率得到了提高。
时间复杂度:通常认为是 \(O(N^{3/2})\),未验证 稳定性:不稳定
class Shell(object):
def sort(self,nums):
N = len(nums)
h = 1
while h<N//3:
h = 3*h+1 #1,4,13,40,121,364,1093
while h>=1:
for i in range(h,N):
j = i
while j>=h and nums[j] < nums[j-h]:
nums[j],nums[j-h] = nums[j-h],nums[j]
j -= h
h //= 3
return nums
5.归并排序
采用了分治和递归的思想,递归&分治-排序整个数列如同排序两个有序数列,依次执行这个过程直至排序末端的两个元素,再依次向上层输送排序好的两个子列进行排序直至整个数列有序(类比二叉树的思想,from down to up)。
时间复杂度:\(O(NlogN)\) 稳定性:稳定
import copy
class Merge(object):
def sort(self,nums):
self.sortw(nums,0,len(nums)-1)
return nums
def sortw(self,a,lo,hi):
if hi <= lo:return
mid = lo+(hi-lo)//2
self.sortw(a,lo,mid)
self.sortw(a,mid+1,hi)
self.merge(a,lo,mid,hi)
def merge(self,a,lo,mid,hi):
i,j = lo,mid+1
aux = copy.copy(a)
for k in range(lo,hi+1):
if i>mid:
a[k] = aux[j]
j+=1
elif j>hi:
a[k] = aux[i]
i+=1
elif aux[j] < aux[i]:
a[k] = aux[j]
j+=1
else:
a[k] = aux[i]
i+=1
6.快速排序
(类似于选择排序的定位思想)选一基准元素,依次将剩余元素中小于该基准元素的值放置其左侧,大于等于该基准元素的值放置其右侧;然后,取基准元素的前半部分和后半部分分别进行同样的处理。快速排序是排序算法运用最广的算法。
时间复杂度:\(O(NlogN)\) 稳定性:不稳定
# 递归实现
class Quick(object):
def sort(self,nums):
if not nums:return []
return self.sortquick(nums,0,len(nums)-1)
def sortquick(self,nums,lo,hi):
if(hi<=lo):return
j = self.partition(nums,lo,hi)
self.sortquick(nums,lo,j-1)
self.sortquick(nums,j+1,hi)
return nums
def partition(self,a,lo,hi):
i=lo+1;j=hi
v = a[lo]
while(True):
while a[i]<=v and i<hi:
i+=1
while v<=a[j] and j>lo:
j-=1
if (i>=j):break
a[i],a[j] = a[j],a[i]
a[lo],a[j] = a[j],a[lo]
return j
快速排序同样也可以使用非递归实现。
# 非递归实现
class Quick(object):
def quick_sort(self,nums):
'''
模拟栈操作实现非递归的快速排序
'''
if len(nums) < 2:
return nums
stack = []
stack.append(len(nums)-1)
stack.append(0)
while stack:
lo = stack.pop()
hi = stack.pop()
j = self.partition(nums, lo, hi)
if lo < j - 1:
stack.append(j - 1)
stack.append(lo)
if hi > j + 1:
stack.append(hi)
stack.append(j + 1)
return nums
def partition(self,a,lo,hi):
i=lo+1
j=hi
v = a[lo]
while(True):
while a[i]<=v and i<hi:
i+=1
while v<=a[j] and j>lo:
j-=1
if (i>=j):break
a[i],a[j] = a[j],a[i]
a[lo],a[j] = a[j],a[lo]
return j
对于处理重复值的情况,快速排序可以改写成三向切分,维持复杂度。三向切分即将数组切分为三部分,分别对应小于、等于和大于切分元素。
class QuickthereSort(object):
def sort(self,nums):
self.sortw(nums,0,len(nums)-1)
return nums
def sortw(self,a,lo,hi):
if hi<=lo:return
lt=lo;i=lo+1;gt=hi
v=a[lo]
while(i<=gt):
if a[i]<v:
a[lt],a[i] = a[i],a[lt]
lt+=1
i+=1
elif a[i]>v:
a[i],a[gt] = a[gt],a[i]
gt-=1
else:i+=1
self.sortw(a,lo,lt-1)
self.sortw(a,gt+1,hi)
7.堆排序
堆排序的思想借助于二叉堆中的最大堆得以实现。首先,将待排序数列抽象为二叉树,并构造出最大堆;然后,依次将最大元素(即根节点元素)与待排序数列的最后一个元素交换(即二叉树最深层最右边的叶子结点元素);每次遍历,刷新最后一个元素的位置(自减1),直至其与首元素相交,即完成排序。
时间复杂度:\(O(NlogN)\) 稳定性:不稳定
class HeapSort(object):
def sort(self, nums):
N = len(nums)
nums.insert(0,0)
for k in reversed(range(1,N//2+1)):
self.sink(nums,k,N)
while N>1:
nums[1],nums[N] = nums[N],nums[1]
N-=1
self.sink(nums,1,N)
del nums[0]
print('nums',nums)
def sink(self,a,k,N):
while 2*k<=N:
j = 2*k
if j<N and a[j]<a[j+1]:
j+=1
if a[k] >= a[j]:
break
a[k],a[j] = a[j],a[k]
k = j
注意,这里用的是大顶堆实现的;python中heapq库是用小顶堆实现的,恰好相反。
8.计数排序
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
时间复杂度:\(O(N)\) 稳定性:稳定
class CountSort():
def sort(self,nums):
max_value = max(nums)
result = []
container = [0 for _ in range(0, max_value + 1)]
for num in nums:
container[num] += 1
for index in range(0, len(container)):
while container[index] > 0:
result.append(index)
container[index] -= 1
return result
总结
这里主要列举了八类排序算法,另外,基数排序和桶排序实际应用中并不多,但其思想很重要,在刷题的过程中遇到了很多。
扩展
单链表快排
由数组推广到链表是常见的做法了,单链表排序使用归并是简单有效的做法,但如何使用快排来完成呢?这里使用了交换链表元素值的做法。如下:
class ListNode():
def __init__(self, val):
self.val = val
self.next = None
class LinkList():
def __init__(self,nums):
if not nums:
return
self.nums = nums
self.head = node = ListNode(nums[0])
for num in nums[1:]:
node.next = ListNode(num)
node = node.next
def getHead(self):
return self.head
class QuickList():
def quickSort(self,head):
if not head:return
self.sortw(head,None)
def sortw(self,head,tail):
if head != tail:
node = self.partition(head,tail)
self.sortw(head,node)
self.sortw(node.next,tail)
def partition(self,head,tail):
v = head.val
p,q = head,head.next
while q!=tail:
if q.val < v:
p = p.next
p.val,q.val = q.val,p.val
q = q.next
p.val,head.val = head.val,p.val
return p
参考:
九种排序算法分析与实现