宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取
快速排序 Quick sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行递归排序,以达到整个序列有序。
1.算法描述:
另一个分而治之
将数组划分为两个部分,然后独立地对部分进行排序:
首先选择一个数据透视,并从列表中删除隐藏在最后)
然后这些元素被分成两部分.一个小于枢轴,另一个大于枢轴. 这种分区是通过交换价值来实现的
然后在中间恢复枢轴,并且这两个部分递归地快速排序
示例:
Pivot:中间枢纽 5) , Portitiom:分区 , Two points:两个指针 i:左->右 j:左<-右)
2.算法属性:
时间复杂度:Onlogn)
空间复杂度:Onlogn)
稳定性:不稳定
3.代码实现
''' Onlogn) pivot枢纽,low和high为起点终点 ''' #划分分区(非就地划分) def partitionnums=list): pivot = nums[0] #挑选枢纽 lo = [x for x in nums[1:] if x < pivot] #所有小于pivot的元素 hi = [x for x in nums[1:] if x >= pivot] #所有大于pivot的元素 return lo,pivot,hi #快速排序 def quick_sortnums=list): #被分解的Nums小于1则解决了 if lennums) <= 1: return nums #分解 lo,pivot,hi = partitionnums) # 递归(树),分治,合并 return quick_sortlo) + [pivot] + quick_sorthi) lis = [7, 5, 0, 6, 3, 4, 1, 9, 8, 2] printquick_sortlis)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
''' 两部分,第一部分封装快排函数,第二部分调用快排 取枢纽key为pivot 学习版本 ''' import time def _quick_sortnums:list): if lennums) <= 1: return nums pivot = nums[0] #取第一个值为枢纽 #pivot左右边分别调用_quick_sort自身 #找左半边比pivot小的 left_nums = _quick_sort[x for x in nums[1:] if x < pivot]) #找右半边比pivot大的(此处选择x>=pivot意为pivot放在后半边比它大的元素前面) right_nums = _quick_sort[x for x in nums[1:] if x >= pivot]) return left_nums + [pivot] + right_nums def quick_sortnums:list,reverse=False): start = time.time) nums = _quick_sortnums) if reverse: nums = nums[::-1] t = time.time) - start return nums,t lis = [1,3,5,7,9,2,5,3,6,8,0] lis = quick_sortlis,reverse=False) printlis) #输出结果 [0, 1, 2, 3, 3, 5, 5, 6, 7, 8, 9], 0.0)
网上最多较多的版本
def quick_sortarray): #封装一层调用 def recursivebegin, end): #fecursive递归 if begin > end: return l, r = begin, end pivot = array[l] while l < r: while l < r and array[r] > pivot: r -= 1 while l < r and array[l] <= pivot: l += 1 array[l], array[r] = array[r], array[l] array[l], array[begin] = pivot, array[l] recursivebegin, l - 1) recursiver + 1, end) recursive0, lenarray) - 1) return array
第三个版本
''' 使用对象实例化,原理还是一样的 ''' class SQList: def __init__self, lis=None): self.r = lis def swapself, i, j): #定义一个交换元素的方法,方便后面调用。 temp = self.r[i] self.r[i] = self.r[j] self.r[j] = temp def quick_sortself): #调用入口 self.qsort0, lenself.r)-1) def qsortself, low, high): #递归调用 if low < high: pivot = self.partitionlow, high) self.qsortlow, pivot-1) self.qsortpivot+1, high) def partitionself, low, high): ''' 快速排序的核心代码。 其实就是将选取的pivot_key不断交换,将比它小的换到左边,将比它大的换到右边。 它自己也在交换中不断变换自己的位置,直到完成所有的交换为止。 但在函数调用的过程中,pivot_key的值始终不变。 :param low:左边界下标 :param high:右边界下标 :return:分完左右区后pivot_key所在位置的下标 ''' lis = self.r pivot_key = lis[low] while low < high: while low < high and lis[high] >= pivot_key: high -= 1 self.swaplow, high) while low < high and lis[low] <= pivot_key: low += 1 self.swaplow, high) return low def __str__self): ret = "" for i in self.r: ret += " %s" % i return ret if __name__ == '__main__': sqlist = SQList[4, 1, 7, 3, 8, 5, 9, 2, 6, 0, 123, 22]) sqlist.quick_sort) printsqlist)
4.快速排序可优化的地方:(以第三个版本举例)
1)优化选取的Pivot
前面我们每次选取Pivot的都是子序列的第一个元素,也就是lis[low],这就比较看运气。运气好时,该值处于整个序列的靠近中间值,则构造的树比较平衡,运气比较差,处于最大或最小位置附近则构造的树接近斜树。
为了保证pivot选取的尽可能适中,采取选取序列左中右三个特殊位置的值中,处于中间值的那个数为pivot,通常会比直接用lis[low]要好一点。在代码中,在原来的pivot = lis[low]这一行前面增加下面的代码:
m = low + inthigh-low)/2) if lis[low] > lis[high]: self.swaplow, high) if lis[m] > lis[high]: self.swaphigh, m) if lis[m] > lis[low]: self.swapm, low)
如果觉得这样还不够好,还可以将整个序列先划分为3部分,每一部分求出个pivot_key,再对3个pivot_key再做一次上面的比较得出最终的pivot_key。这时的pivot_key应该很大概率是一个比较靠谱的值。
2)减少不必要的交换
原来的代码中pivot_key这个记录总是再不断的交换中,其实这是没必要的,完全可以将它暂存在某个临时变量中,如下所示:
def partitionself, low, high): lis = self.r m = low + inthigh-low)/2) if lis[low] > lis[high]: self.swaplow, high) if lis[m] > lis[high]: self.swaphigh, m) if lis[m] > lis[low]: self.swapm, low) pivot_key = lis[low] # temp暂存pivot_key的值 temp = pivot_key while low < high: while low < high and lis[high] >= pivot_key: high -= 1 # 直接替换,而不交换了 lis[low] = lis[high] while low < high and lis[low] <= pivot_key: low += 1 lis[high] = lis[low] lis[low] = temp return low
3)优化小数组时的排序
快速排序算法的递归操作在进行大量数据排序时,其开销能被接受,速度较快。但进行小数组排序时则不如直接插入排序来得快,也就是杀鸡用牛刀,未必就比菜刀来得快。
因此,一种很朴素的做法就是根据数据的多少,做个使用哪种算法的选择而已,如下改写qsort方法:
def qsortself, low, high): """根据序列长短,选择使用快速排序还是简单插入排序""" # 7是一个经验值,可根据实际情况自行决定该数值。 MAX_LENGTH = 7 if high-low < MAX_LENGTH: if low < high: pivot = self.partitionlow, high) self.qsortlow, pivot - 1) self.qsortpivot + 1, high) else: # insert_sort方法是我们前面写过的简单插入排序算法 self.insert_sort)
4)优化递归操作
可以采用尾递归的方式对整个算法的递归操作进行优化,改写qsort方法如下:
def qsortself, low, high): """根据序列长短,选择使用快速排序还是简单插入排序""" # 7是一个经验值,可根据实际情况自行决定该数值。 MAX_LENGTH = 7 if high-low < MAX_LENGTH: # 改用while循环 while low < high: pivot = self.partitionlow, high) self.qsortlow, pivot - 1) # 采用了尾递归的方式 low = pivot + 1 else: # insert_sort方法是我们前面写过的简单插入排序算法 self.insert_sort)