背景:数据结构与算法是IT相关的工程师一直以来的基础考察重点,很多经典书籍都是用c++或者java来实现,出于对python编码效率的喜爱,于是取search了一下python的快排实现,发现大家写的都比较个性,也所以我也总结下自己理解的python快排实现。

:本随笔注重代码实现,如果是对快速排序无任何接触的还是先看一下相关的书籍

快速排序简介:快速排序是突破O(n^2)时间复杂度上界的排序算法,其平均情况下和最好情况是的时间复杂度都是O(nlogn),最差情况下的时间复杂度为O(n^2)(最差情况下退化为选择排序),空间复杂度为O(logn)

核心思想

  核心为 partition() 函数,该函数每调用一次,会产生两个作用:

  例子:待排序数组为[3,5,1,8,2,4],调用一次该函数后数组变为[2,1,3,8,5,4]

  直接作用:确定待排序数组上某个位置的值(我们称这个值为枢轴);在上例中表现为确定了待排序数组中索引为2(第3个元素)的值,元素'3'即为枢轴的值

  副作用:将待排序数据分为了3个部分,即 [小于等于枢轴的待排序数组]+枢轴+[大于等于枢轴的待排序数组],副作用的贡献体现在减少了分治的次数

快速排序=对待排序数组采用分治+递归的方法调用partition()函数

partition()函数的时间复杂度为O(n),分治+递归调用的平均时间复杂度为O(logn),所以总体相乘为O(nlogn)

python代码实现

第一种实现,partition借助额外的list,所以partition函数的空间复杂度为O(n),因为涉及分治+递归调用,递归使用的隐含栈需要O(logn)的时间复杂度,所以整体空间复杂度为O(nlogn),借助额外的数据结构一般会起到两个效果:1、降低时间复杂度 或者 2、提高代码可读性(易于理解),这里并没有降低时间复杂度

def quick_sort1(lst):
"""快速排序"""
def partition(lst, left, right):
#借助两个临时列表存放小于枢轴的元素和大于枢轴的元素
l_list, r_list = [], []
#选取待排序列表的最左元素作为枢轴
pivot_value = lst[left]
for i in lst[left+1:right+1]:
if i<=pivot_value:
l_list.append(i)
else:
r_list.append(i)
#因为是原地排序,所以对原待排序数组的相应元素进行替换
lst[left:right+1] = l_list+[pivot_value]+r_list
return left+len(l_list) def q_sort(lst, left, right):
"""辅助函数,便于递归调用"""
if left>=right:
return
pivot_key = partition(lst, left, right)
q_sort(lst, left, pivot_key-1)
q_sort(lst, pivot_key+1, right) if not lst or len(lst)==0:
return lst q_sort(lst, 0, len(lst)-1) return lst

上述实现采用了额外的list,虽然增加了可读性,但是提高了空间复杂度,所以,可以对其优化,将partition函数的空间复杂度降为O(1)

第二种实现,不借助额外列表

def quick_sort2(lst):
"""快速排序"""
def partition(lst, left, right):
#默认选择列表最左元素作为枢轴
pivot_value = lst[left]
while left<right:
while left<right and lst[right]>=pivot_value:
right-=1
#当右指针对应元素小于枢轴的值,将左右指针对应元素交换,使小于枢轴的值位于枢轴的左侧
lst[left], lst[right] = lst[right], lst[left]
while left<right and lst[left]<=pivot_value:
left+=1
#当左指针对应元素大于枢轴的值,将左右指针对应元素交换,使大于枢轴的值位于枢轴的右侧
lst[left], lst[right] = lst[right], lst[left]
return left def q_sort(lst, left, right):
if left>=right:
return
pivot_key = partition(lst, left, right)
q_sort(lst, left, pivot_key-1)
q_sort(lst, pivot_key+1, right) if not lst or len(lst)==0:
return lst q_sort(lst, 0, len(lst)-1) return lst

这里通过元素交换的方式达到了与方法1同样的效果,所以在很多资料上,快速排序和冒泡排序都被分类为'交换排序',但有一点要注意,快速排序最差的情况下,会退化为选择排序而非冒泡排序

针对第二种情况,我们还可以继续优化,省去不必要的交换,将"交换"优化为“替换”

第三种实现

def quick_sort3(lst):
"""快速排序"""
def partition(lst, left, right):
#默认选择列表最左元素作为枢轴,同时也记录了left最初对应的元素值
pivot_value = lst[left]
while left<right:
while left<right and lst[right]>=pivot_value:
right-=1
#将left对应的元素替换为right(小于枢轴)对应的元素
lst[left] = lst[right]
while left<right and lst[left]<=pivot_value:
left+=1
#将right对应的元素替换为left(大于枢轴)对应的元素
lst[right] = lst[left]
#当left和right相等时,使用最初记录的left对应的元素值替换当前指针的元素
lst[left] = pivot_value
#返回枢轴对应的索引
return left def q_sort(lst, left, right):
if left>=right:
return
pivot_key = partition(lst, left, right)
q_sort(lst, left, pivot_key-1)
q_sort(lst, pivot_key+1, right) if not lst or len(lst)==0:
return lst q_sort(lst, 0, len(lst)-1) return lst

第三种方案和前两种一样,都是将列表的最左元素作为枢轴,这也是导致快速排序最差情况时间复杂度为O(n^2)的原因,比如每次列表的最左元素都为最大值或者最小值,那每次对partition函数的调用只起到了直接作用(确定了列表的最左端的最小值或者最右端的最大值),而没有起到副作用(副作用的目的是减小分治次数)

所以我们可以对枢轴的选取进行优化,优化的目的是使枢轴的选取避开最大值或最小值,尽量靠近中位数,优化的思路有两种

1、随机选取

2、选取列表中left, right, (left+right)//2,三个索引位置对应元素居中的元素

由于随机数的生成在编程语言API中的实现也要耗费一定的时间复杂度,所以我们选择2

第四种实现如下

def quick_sort4(lst):
"""快速排序"""
def partition(lst, left, right):
#计算中间索引
mid = (left+right)//2
#将三个元素中大小居中的元素交换至列表的最左侧
if lst[left]>lst[mid]:
lst[left], lst[mid] = lst[mid], lst[left]
if lst[mid]>lst[right]:
lst[mid], lst[right] = lst[right], lst[mid]
if lst[left]<lst[mid]:
lst[left], lst[mid] = lst[mid],lst[left] pivot_value = lst[left]
while left<right:
while left<right and lst[right]>=pivot_value:
right-=1
lst[left] = lst[right]
while left<right and lst[left]<=pivot_value:
left+=1
lst[right] = lst[left]
lst[left] = pivot_value
return left def q_sort(lst, left, right):
if left>=right:
return
pivot_key = partition(lst, left, right)
q_sort(lst, left, pivot_key-1)
q_sort(lst, pivot_key+1, right) if not lst or len(lst)==0:
return lst q_sort(lst, 0, len(lst)-1) return lst

经过2~4的优化,我们已经

1)把空间复杂度由O(nlogn)降至O(n),yi

2)并尽量优化了最差情况下的时间复杂度,使其比O(n^2)要好一些

但需要提醒一下,其最佳情况下的时间复杂度依旧使O(nlogn),而一些简单排序算法,如插入排序和优化后的冒泡排序的最优时间复杂度都可以达到O(n)

快排在面对大量数据排序时表现良好,

所以可以进行优化,当待排序数据的元素数量小于某个常数值时采用插入排序,否则使用快速排序

第五种实现

def quick_sort5(lst):
"""快速排序"""
def partition(lst, left, right):
#计算中间索引
mid = (left+right)//2
#将三个元素中大小居中的元素交换至列表的最左侧
if lst[left]>lst[mid]:
lst[left], lst[mid] = lst[mid], lst[left]
if lst[mid]>lst[right]:
lst[mid], lst[right] = lst[right], lst[mid]
if lst[left]<lst[mid]:
lst[left], lst[mid] = lst[mid],lst[left] pivot_value = lst[left]
while left<right:
while left<right and lst[right]>=pivot_value:
right-=1
lst[left] = lst[right]
while left<right and lst[left]<=pivot_value:
left+=1
lst[right] = lst[left]
lst[left] = pivot_value
return left def q_sort(lst, left, right):
if left>=right:
return
pivot_key = partition(lst, left, right)
q_sort(lst, left, pivot_key-1)
q_sort(lst, pivot_key+1, right) if not lst or len(lst)==0:
return lst
#取某个常数,待排序元素数量大于该常数时使用快排,否则使用插入排序
if len(lst)>50:
q_sort(lst, 0, len(lst)-1)
else:
#插入排序在此不实现了,大家自行解决
insert_sort(lst) return lst

经过上述优化,我们做到了

1)空间复杂度由O(nlogn)优化至O(logn)

2)  将最差情况下的时间复杂度O(n^2)尽可能提升

3)将时间复杂度的下界提升至O(n),当然,这已经不是单纯的快排了- -!

刚开始写博客,有不对的地方还望指教~~~

快速排序python实现总结的更多相关文章

  1. 快速排序--Python实现

    快速排序算法:1.选择一个基准数2.小于基准数的放左边,大于基准数的放右边3.利用递归的方法针对左边的数据进行快速排序,再对右边的数据进行快速排序4.递归停止的条件:数组为空或者只有一个元素 时间复杂 ...

  2. 快速排序python实现

    #--×--coding:utf-8-*- def main(): nlist = [] while 1: tmp = raw_input("Please input your elemen ...

  3. 快速排序(python版)

    #!coding:utf8 def quicksort(list_num, left, right): if left > right: return low = left high = rig ...

  4. 快速排序-python

  5. python数据结构与算法

    最近忙着准备各种笔试的东西,主要看什么数据结构啊,算法啦,balahbalah啊,以前一直就没看过这些,就挑了本简单的<啊哈算法>入门,不过里面的数据结构和算法都是用C语言写的,而自己对p ...

  6. 常见排序算法-Python实现

    常见排序算法-Python实现 python 排序 算法 1.二分法     python    32行 right = length-  :  ]   ):  test_list = [,,,,,, ...

  7. python实现简单排序算法

    算法 递归两个特点: 调用自身 有穷调用 计算规模越来越小,直至最后结束 用装饰器修饰一个递归函数时会出现问题,这个问题产生的原因是递归的函数也不停的使用装饰器.解决方法是,只让装饰器调用一次即可,那 ...

  8. <算法图解>读书笔记:第4章 快速排序

    第4章 快速排序 4.1 分而治之 "分而治之"( Divide and conquer)方法(又称"分治术") ,是有效算法设计中普遍采用的一种技术. 所谓& ...

  9. Python和Java的语法对比,语法简洁上python的确完美胜出

    Python是一种广泛使用的解释型.高级编程.通用型编程语言,由吉多·范罗苏姆创造,第一版发布于1991年.可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP.Python的设计哲 ...

随机推荐

  1. Linux 杀死进程方法大全(kill,killall)

    杀死进程最安全的方法是单纯使用kill命令,不加修饰符,不带标志.   首先使用ps -ef命令确定要杀死进程的PID,然后输入以下命令:   # kill -pid   注释:标准的kill命令通常 ...

  2. iOS 中UIWebView的cookie

    有关cookie是什么,大家可以自行百度,本文我获得cookie的目的是得到一个userID. 下面的是代码. - (void)getUserIDFromCookie { NSHTTPCookieSt ...

  3. EX_KMP算法总结

    EX_KMP算法总结 By viv 2014-8-9 0:30 吐槽1:字符串神马的我最讨厌了,但不学不行啊.TAT 吐槽2:写这东西差点错过CF(codeforces). 今天学了ex_kmp,故总 ...

  4. [LC] 77. Combinations

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. Example: I ...

  5. 单独安装jenkins-没有tomcat

    这里讲解war包的安装:windows的msi版安装很简单,双击即可,不用讲 1.官网下载 2. 3.把war包放到java目录下 4. 5.安装完成后打开:127.0.0.1:8080 输入密码后会 ...

  6. Linux BASH简单总结

    BASH: 变量的设置规则: 1.变量与变量内容以一个符号"="来链接 2.等号两边不能直接接空白字符 3.变量名称只能是英文字母与数字,但是不能以数字开头 4.变量内容若有空白字 ...

  7. mysql配置白名单

    1. 测试是否允许远程连接 $ telnet 192.168.1.8 3306 host 192.168.1.4 is not allowed to connect to this mysql ser ...

  8. mongodb use where and custom function to query mongodb存储过程

    function name regexObjSubObjKey function code function(proto,value) { var match=false; var reg = new ...

  9. Redis 事物、悲观、乐观锁 (详细)

    1,概论 事物这东西相信大家都不陌生吧,在学习Spring,Mybatis等框架中, 只要是涉及到数据存储和修改的,都会有事物的存在, 废话就不多说了下面我们来简单的介绍下Redis事物以及锁. 2, ...

  10. npm相关说明

    https://my.oschina.net/dkvirus?tab=newest&catalogId=5669676 https://my.oschina.net/dkvirus/blog/ ...