python数据结构与算法-13_高级排序算法-快速排序

快速排序

快速排序名字可不是盖的,很多程序语言标准库实现的内置排序都有它的身影,我们就直奔主题吧。
和归并排序一样,快排也是一种分而治之(divide and conquer)的策略。归并排序把数组递归成只有单个元素的数组,之后再不断两两
合并,最后得到一个有序数组。这里的递归基本条件就是只包含一个元素的数组,当数组只包含一个元素的时候,我们可以认为它本来就是有序的(当然空数组也不用排序)。

快排的工作过程其实比较简单,三步走:

  • 选择基准值 pivot 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。这个过程称之为 partition

  • 对这两个子数组进行快速排序。

  • 合并结果

在这里插入图片描述

根据这个想法我们可以快速写出快排的代码,简直就是在翻译上边的描述:

def quicksort(array):size = len(array)if not array or size < 2:  # NOTE: 递归出口,空数组或者只有一个元素的数组都是有序的return arraypivot_idx = 0pivot = array[pivot_idx]less_part = [array[i] for i in range(size) if array[i] <= pivot and pivot_idx != i]great_part = [array[i] for i in range(size) if array[i] > pivot and pivot_idx != i]return quicksort(less_part) + [pivot] + quicksort(great_part)def test_quicksort():import randomseq = list(range(10))random.shuffle(seq)assert quicksort(seq) == sorted(seq)

是不是很简单,下次面试官让你手写快排你再写不出来就有点不太合适啦。 当然这个实现有两个不好的地方:

  • 第一是它需要额外的存储空间,我们想实现 inplace 原地排序。
  • 第二是它的 partition 操作每次都要两次遍历整个数组,我们想改善一下。

这里我们就来优化一下它,实现 inplace 排序并且改善下 partition 操作。新的代码大概如下:

def quicksort_inplace(array, beg, end):    # 注意这里我们都用左闭右开区间,end 传入 len(array)if beg < end:    # beg == end 的时候递归出口pivot = partition(array, beg, end)quicksort_inplace(array, beg, pivot)quicksort_inplace(array, pivot+1, end)

能否实现只遍历一次数组就可以完成 partition 操作呢?实际上是可以的。我们设置首位俩个指针 left, right,两个指针不断向中间收拢。如果遇到 left 位置的元素大于 pivot 并且 right 指向的元素小于 pivot,我们就交换这俩元素,当 left > right 的时候退出就行了,这样实现了一次遍历就完成了 partition。如果你感觉懵逼,纸上画画就立马明白了。我们来撸代码实现:
在这里插入图片描述

def partition(array, beg, end):pivot_index = begpivot = array[pivot_index]left = pivot_index + 1right = end - 1    # 开区间,最后一个元素位置是 end-1     [0, end-1] or [0: end),括号表示开区间while True:# 从左边找到比 pivot 大的while left <= right and array[left] < pivot:left += 1while right >= left and array[right] >= pivot:right -= 1if left > right:breakelse:array[left], array[right] = array[right], array[left]array[pivot_index], array[right] = array[right], array[pivot_index]return right   # 新的 pivot 位置def test_partition():l = [4, 1, 2, 8]assert partition(l, 0, len(l)) == 2l = [1, 2, 3, 4]assert partition(l, 0, len(l)) == 0l = [4, 3, 2, 1]assert partition(l, 0, len(l))

大功告成,新的快排就实现好了。

时间复杂度

在比较理想的情况下,比如数组每次都被 pivot 均分,我们可以得到递归式:

T(n) = 2T(n/2) + n

上一节我们讲过通过递归树得到它的时间复杂度是 O(nlog(n))。即便是很坏的情况,比如 pivot 每次都把数组按照 1:9 划分,依然是 O(nlog(n)),感兴趣请阅读算法导论相关章节。

在这里插入图片描述

思考题

  • 请你补充 quicksort_inplace 的单元测试
  • 最坏的情况下快排的时间复杂度是多少?什么时候会发生这种情况?
  • 我们实现的快排是稳定的啵?
  • 选择基准值如果选不好就可能导致复杂度升高,算导中提到了一种『median of 3』策略,就是说选择 pivot 的时候 从子数组中随机选三个元素,再取它的中位数,你能实现这个想法吗?这里我们的代码很简单地取了第一个元素作为 pivot
  • 利用快排中的 partition 操作,我们还能实现另一个算法,nth_element,快速查找一个无序数组中的第 n 大元素,请你尝试实现它并编写单测。其实这个函数是 C++ STL 中的一个函数。
  • 你知道 Python 内置的 sorted 如何实现的吗?请你 Google 相关资料了解下。很多内置的排序都使用了快排的改良版。

源码

# -*- coding: utf-8 -*-def quicksort(array):size = len(array)if not array or size < 2:  # NOTE: 递归出口,空数组或者只有一个元素的数组都是有序的return arraypivot_idx = 0pivot = array[pivot_idx]less_part = [array[i] for i in range(size) if array[i] <= pivot and pivot_idx != i]great_part = [array[i] for i in range(size) if array[i] > pivot and pivot_idx != i]return quicksort(less_part) + [pivot] + quicksort(great_part)def test_quicksort():import randomseq = list(range(10))random.shuffle(seq)assert quicksort(seq) == sorted(seq)    # 用内置的sorted 『对拍』def quicksort_inplace(array, beg, end):    # 注意这里我们都用左闭右开区间if beg < end:    # beg == end 的时候递归出口pivot = partition(array, beg, end)quicksort_inplace(array, beg, pivot)quicksort_inplace(array, pivot + 1, end)def partition(array, beg, end):"""对给定数组执行 partition 操作,返回新的 pivot 位置"""pivot_index = begpivot = array[pivot_index]left = pivot_index + 1right = end - 1    # 开区间,最后一个元素位置是 end-1     [0, end-1] or [0: end),括号表示开区间while True:# 从左边找到比 pivot 大的while left <= right and array[left] < pivot:left += 1while right >= left and array[right] >= pivot:right -= 1if left > right:breakelse:array[left], array[right] = array[right], array[left]array[pivot_index], array[right] = array[right], array[pivot_index]return right   # 新的 pivot 位置def test_partition():l = [4, 1, 2, 8]assert partition(l, 0, len(l)) == 2l = [1, 2, 3, 4]assert partition(l, 0, len(l)) == 0l = [4, 3, 2, 1]assert partition(l, 0, len(l)) == 3l = [1]assert partition(l, 0, len(l)) == 0l = [2,1]assert partition(l, 0, len(l)) == 1def test_quicksort_inplace():import randomseq = list(range(10))random.shuffle(seq)sorted_seq = sorted(seq)quicksort_inplace(seq, 0, len(seq))assert seq == sorted_seqdef nth_element(array, beg, end, nth):"""查找一个数组第 n 大元素"""if beg < end:pivot_idx = partition(array, beg, end)if pivot_idx == nth - 1:    # 数组小标从 0 开始return array[pivot_idx]elif pivot_idx > nth - 1:return nth_element(array, beg, pivot_idx, nth)else:return nth_element(array, pivot_idx + 1, end, nth)def test_nth_element():l1 = [3, 5, 4, 2, 1]assert nth_element(l1, 0, len(l1), 3) == 3assert nth_element(l1, 0, len(l1), 2) == 2l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]for i in l:assert nth_element(l, 0, len(l), i) == ifor i in reversed(l):assert nth_element(l, 0, len(l), i) == iarray = [3, 2, 1, 5, 6, 4]assert nth_element(array, 0, len(array), 2) == 2array = [2,1]assert nth_element(array, 0, len(array), 1) == 1assert nth_element(array, 0, len(array), 2) == 2array = [3,3,3,3,3,3,3,3,3]assert nth_element(array, 0, len(array), 1) == 3if __name__ == '__main__':test_nth_element()

延伸阅读

  • 《算法导论》第 7 章
  • 《面试必备 | 排序算法的Python实现》

总结

面试经常问的就是常用排序算法的时间空间复杂,这里列一个表格方便记忆:

排序算法最差时间分析平均时间复杂度稳定度空间复杂度
冒泡排序O(n^2)O(n^2)稳定O(1)
选择排序O(n^2)O(n^2)不稳定O(1)
插入排序O(n^2)O(n^2)稳定O(1)
二叉树排序O(n^2)O(n*log2n)不一顶O(n)
快速排序O(n^2)O(n*log2n)不稳定O(log2n)~O(n)
堆排序O(n*log2n)O(n*log2n)不稳定O(1)

数据结构与算法-排序篇-Python描述

Leetcode

无序数组寻找第 k 大的数字,不止一种方法。
https://leetcode.com/problems/kth-largest-element-in-an-array/description/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/163064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker安装mysql挂着目录和mysql备份和恢复

第一&#xff0c;镜像拉取&#xff0c;运行镜像并挂载目录&#xff0c;尝试挂bin下&#xff0c;启动不了&#xff0c;不知为啥 docker run --privilegedtrue -itd --namevmysql -p 3306:3306 -v /home/vmysql:/home/vmysql -e MYSQL_ROOT_PASSWORD123456 mysql&#xff08;图…

Nancy (二)

最近做CS项目&#xff0c;一直在使用TCPSocket 做数据传输&#xff0c;不太爽&#xff0c;砸门可是多年BS的开发&#xff0c;这样开发接口出去比较费劲&#xff0c;但是又不想用asp.net mvc webapi,要按照IIS&#xff0c;有些工控机的系统环境也是很尴尬的&#xff0c;那么也可…

用好说 AI 玩转奥特曼表情包,居然还能和他们聊个天

你喜欢奥特曼吗&#xff1f;你相信光吗&#xff1f; 如果你已经追完了特摄剧、刷完了大电影、用滥了那几个表情包&#xff0c;那不如来试试用 AI 给自己整点活儿新 “物料”。 不管是和奥特曼 “面对面” 聊天还是 “无中生有” 表情包&#xff0c;AI 都能做&#xff01; (※…

Python 使用SQLAlchemy数据库模块

SQLAlchemy 是用Python编程语言开发的一个开源项目&#xff0c;它提供了SQL工具包和ORM对象关系映射工具&#xff0c;使用MIT许可证发行&#xff0c;SQLAlchemy 提供高效和高性能的数据库访问&#xff0c;实现了完整的企业级持久模型。 ORM&#xff08;对象关系映射&#xff0…

MySQL For Windows的下载与安装

教程https://www.bilibili.com/read/cv26499785/ windowse下载地址https://dev.mysql.com/get/Downloads/MySQLInstaller/mysql-installer-community-8.0.35.0.msi

代理模式 (Proxy Pattern)

定义&#xff1a; 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过提供一个代理&#xff08;或称代表&#xff09;对象来控制对另一个对象的访问。这种模式创建了一个代理对象&#xff0c;用来代表实际对象的功能&#xff0c;从而可以在…

spring boot 热部署

相信小伙伴们在日常的开发中&#xff0c;调试代码时&#xff0c;免不了经常修改代码&#xff0c;这个时候&#xff0c;为了验证效果&#xff0c;必须要重启 Spring Boot 应用。 频繁地重启应用&#xff0c;导致开发效率降低&#xff0c;加班随之而来。有没有什么办法&#xff0…

宏电股份受邀参加中国联通战新共创启航大会,共筑产业生态,链通数智未来

11月21日&#xff0c;由中国联通举办的主题为“共筑产业生态&#xff0c;链通数智未来”的网络安全现代产业链共链行动计划暨战新共创启航大会“5G工业互联网”专题供需对接会在北京顺利召开&#xff0c;宏电股份董事长左绍舟应邀出席活动。 会议现场&#xff0c;中国联通雁飞…

Rust开发——数据对象的内存布局

枚举与Sized 数据 一般数据类型的布局是其大小&#xff08;size&#xff09;、对齐方式&#xff08;align&#xff09;及其字段的相对偏移量。 1. 枚举&#xff08;Enum&#xff09;的布局&#xff1a; 枚举类型在内存中的布局通常是由编译器来确定的。不同的编译器可能有不…

centos7 系统keepalived 定时执行脚本

安装keepalived yum install -y keepalived 修改配置文件 配置文件路径 /etc/keepalived 配置文件内容 global_defs {router_id localhost.localdomain # 访问到主机&#xff0c;本机的hostname&#xff0c;需要修改 }vrrp_script chk_http_port {script "/etc/kee…

INFLOW:用于检测隐藏服务器的反向网络流水印

文章信息 论文题目&#xff1a;INFLOW: Inverse Network Flow Watermarking for Detecting Hidden Servers 期刊&#xff08;会议&#xff09;&#xff1a;IEEE INFOCOM 2018 - IEEE Conference on Computer Communications 级别&#xff1a;CCF A 文章链接&#xff1a;https:…

Docker 安装 Apache

目录 拉取官方 Apache 镜像 查看本地镜像 列出正在运行的容器 运行 Apache 容器 创建一个 HTML 文件&#xff1a;index.html 访问 Apache 拉取官方 Apache 镜像 查找 Docker Hub 上的 httpd 镜像。 可以通过 Tags 查看其他版本的 httpd&#xff0c;默认是最新版本 httpd…

人工智能学习阶段有哪些?

人工智能学习阶段有哪些? 人工智能是一个跨学科、跨领域的杂交学科&#xff0c;未来的趋势来看,人工智能的出现使人们的生活变得更美好、更便捷&#xff0c;许多小伙伴想学习人工智能&#xff0c;其实看似人工智能比较杂多&#xff0c;无从下手&#xff0c;我们只要从以下7个阶…

go build自建包报错package XXX is not in std

在构建包名的时候遇到了如下错误 C:\Users\xxx\Desktop\code\golx\src>go build example package example is not in std (D:\Go\src\example) 解决方法 修改 GO111MODULE go env -w GO111MODULEoff

SpringMVC 基础知识

学习目标 掌握基于 SpringMVC 获取请求参数与响应 json 数据操作熟练应用基于 REST 风格的请求路径设置与参数传递能够根据实际业务建立前后端开发通信协议并进行实现基于 SSM 整合技术开发任意业务模块功能 1 SpringMVC 简介 1.1 概述 1.1.1 web程序开发流程 【执行过程】…

【搭环境】装Python3.8 open3d

先装Python3.8 方法一试了找不到Python3.8的库&#xff0c;所以用方法二装上了。 Python3加入环境变量 更改Python默认指向 open3d需要Python3.6以上&#xff0c;最好用Ubuntu18版本&#xff0c;我用的16版本。。

从入门到精通!Python数据分析畅销书《利用Python进行数据分析》第三版中文版助你成为数据分析师!

Python数据分析畅销书《利用Python进行数据分析》第三版中文版助你成为数据分析师&#xff01; 个人简介什么是数据分析如何自学数据分析书籍推荐作译者简介作者简介译者简介 主要变动导读视频&#xff1a;购书链接&#xff1a;参与方式往期赠书回顾 个人简介 &#x1f3d8;️&…

【好玩的开源项目】Linux系统之部署proxx扫清黑洞小游戏

【好玩的开源项目】Linux系统之部署proxx扫清黑洞小游戏 一、proxx小游戏介绍1.1 proxx小游戏简介1.2 开源地址 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、部署Node.js环境4.1 下载Node.js安装包4.…

动态规划求二维网格中从左上角到右下角的最短路径( 每次只能向下、向右、向右下走 ) java 实现

dp[i][j] 表示在以点(0,0)作为左上角&#xff0c;点(i,i) 作为右下角的二维网格中 左上角到右下角的最短路径&#xff0c; 动态转移方程为&#xff1a;dp[i][j] min{ dp[i][j-1],dp[i-1][j],dp[i-1][j-1] }.distance weight[i][j] ImageUtils.java&#xff1a; import java.a…

vue3的 nextTick()的使用

引言&#xff1a; 当你修改了响应式状态时&#xff0c;DOM 会被自动更新。但是需要注意的是&#xff0c;DOM 更新不是同步的。Vue 会在“next tick”更新周期中缓冲所有状态的修改&#xff0c;以确保不管你进行了多少次状态修改&#xff0c;每个组件都只会被更新一次。 要等待…