本教程带你从初级到高级全面掌握 Javascript 的使用方法
这是一个很好的简单课程,只需2小时你就可以学习TypeScript基础知识。
本教程带您从零开始学习 Vue 框架的使用,让您轻松应对 Vue 项目的开发。
本教程涵盖Ajax的实现原理,及Ajax封装,最后是框架实现方法。
对比 ES5 进行学习 ES6+,理解 ES6+ 语法背后的思想
Yarn得相关基础知识和高级进阶
从零开始学习 ECharts ,掌握 ECharts 核心内容
本课程从盒模型、文字、颜色、过渡、动画、布局、伪类等方面介绍 CSS3 的使用。
本文详细介绍了雪碧图的由来历史以及各种使用方式
由于移动互联网的兴起,移动端项目占据了很大一部分比重,本章将详细讲解几种常见布局
最新一代的HTML标准,增加了许多实用的特性
前端项目中 Sass 的快速入门教程
从零讲解 HTML,掌握基础 HTML 知识内容
本教程带你从初级到高级全面掌握canvas的使用方法
从零开始学习 uni-app 框架,轻松上手应用开发
本教程使您掌握 Nginx 安装、配置、核心模块的详解、实际使用的能力。
从协议原理开始到 Web 服务器以及 Web 安全一网打尽
从 Docker 的基础概念开始,从实际问题入手带你学习 Docker
本教程由浅入深,系统性的讲解Linux Shell脚本编程。
本教程从安装 Linux 开始,囊括 Linux 基础命令操作以及进阶系统管理
本教程使您掌握实际使用gradle进行项目构建、测试、打包、发布的能力。
课程主要讲解Vim的安装配置,四种模式、基本操作,以及包管理工具和寄存器等内容。
本教程从什么是 REST 开始带你领略 Web 开发中无处不在的规范
DW 是一款同时具有网页制作和网页管理功能的网站开发工具,可以快速进行网站建设
本课程涵盖 Markdown 的基本及扩展语法。
从最基础的安装 Maven 开始到 Maven 在开发中的实际应用
本教程从Eclipse安装开始带你轻松掌握Eclipse常用开发技巧
本教程带你轻松掌握最实用的 GitHub 知识
Android Studio 编程技巧一网打尽
工作经常用到的 PyCharm 编辑器使用技巧一网打尽
花里胡哨展示sublime编辑器的各种功能
Postman 由Google 开发用来做接口请求测试,前后端开发人员都可以使用
从入门到精通。
本教程从语法基础、进阶知识等各方面详解 C 语言。
本教程从 Go 语言的基本语法掌握到进阶编程实践
从 Kotlin 的基础语法到高级特性一网打尽
本教程从 Ruby 的各种对象开始学习到 Ruby 的实际使用
本教程主要讲解 ThinkPHP 框架如何上手开发应用
深入浅出讲解 Java 语言基础知识,带你入门 Java 语言
为你解析最实用的 Android 技术,让你平滑上手,顺利进阶,为开发保驾护航
分析讲解常见算法的思想及使用
通俗易懂的带你了解 Java 数据结构
本教程展现了Lambda表达式的基础语法以及在程序中的应用
本教程为Java并发原理入门教程,在Java程序开发中占据着举足轻重的地位
带你分析最常见的九个设计模式
本课程简洁明了展示最基本的并发工具类相关概念及应用方法。
JVM 入门教程,对JVM结构进行分模块讲解,简单易懂。
超系统的RabbitMQ基础知识课程,你还在等什么?
Java 网络编程核心要点详解
带你系统梳理后端高频面试题,轻松丰富你的校招&社招阶段
循序渐进讲解 Spring Boot 企业级应用开发
通俗易懂 渐进式讲解 Spring 企业级开发应用
由浅入深讲解 Hibernate 企业级 JDBC 应用框架
本教程整理出"百分之二十"的知识,帮你办到"百分之八十"事情
通俗易懂讲解 Spring MVC 框架应用
本课程以图文并茂的方式带你学习 Swagger 核心知识和应用剖析
由浅入深的 学习 ZooKeeper 的基本使用以及高级使用
由浅入深的讲解 Netty 的核心知识体系,快速上手使用和理解 Netty
本课程涵盖了 Spring Security 框架的基本原理和集成方法
系统介绍 Hystrix 支持特性与实际应用场景实战
本教程带你从 Python 的基础语法开始学习 Python。
本教程从爬虫基础知识到进阶技巧到实际应用。
本教程涵盖 Python 的面向对象、标准库解析、异常处理直至最后的领域应用
用 Python 代码实现常用算法并汲取算法核心思想。
从 Web 基础到 Django 框架的实际开发应用
Flask 框架快速入门实现一个 TodoList 功能
本教程从基础的数据类型开始到 NumPy 的高级应用一网打尽
从爬虫基础开始到使用 Scrapy 框架抓取各大网站数据
通过本教程对 TensorFlow 框架快速入门
本教程带你使用Python快速操作Excel、Word、PPT,处理各种文件
本教程从基础的数据类型开始到 Pandas 的高级应用一-网打尽
本教程主要讲解 MySQL 增删改查等基础操作
本教程讲解使用 SQL 访问和处理数据系统中的数据的方法。
那些你还不理解的 MySQL 高阶特性一网打尽
上节课我们学习了一个经典的排序算法—冒泡排序,本节我们来聊一下基础排序中的插入排序算法。
插入排序的基本思想是:将整个数组 nums 分为有序和无序的两个部分。前者在左边,后者在右边。最开始有序的部分只有第一个元素,其余都属于无序的部分。每次取出无序部分的第一个(最左边)元素,把它加入有序部分。通过比较找到属于该元素的位置 k,然后将原 k 位置及其后面的有序部分元素都向右移动一个位置,有序的部分即增加了一个元素,无序部分减少了一个元素。这样一直继续下去,直到无序的部分没有元素,整个插入排序就算完成。
下面我们用一个简单的图片描述下插入排序的过程,如下所示。
图片描述
从上面的过程我们可以看到,比较关键的一步就是找到准备插入元素的位置。简单点的话,可以从有序部分的最后开始往前依次比较。若有序元素比该插入值大则该有序元素后退一个位置,然后继续向前比较,直到有序列表中的元素小于该插入值。
图片描述
看到前面的插入算法的思路有没有跃跃欲试,想赶紧把它写出来的冲动?先别急,我们先思考几个问题:
我们现在分别实现3种情况下的插入排序算法:
我们直接看代码:
def insert_sort(nums):
"""
插入排序
"""
if not nums:
return False
for i in range(1, len(nums)):
t = nums[i]
# 从i-1位置开始往前找
for j in range(i - 1, -1, -1):
k = j
# 如果nums[j]大于t,继续向前,直到找到小于等于t的数
if nums[j] <= t:
k = k + 1
break
# 每次找到的nums[j]大于t,则将当前值想向移动一位
nums[j + 1] = nums[j]
# 此时找到t的位置,并将t放到对应位置
nums[k] = t
return True
看上面的代码,第二个 for 循环便是从有序列表的最后一个元素开始和待插入值进行比较,并逐步向前移动比较,一但待插入值大于或等于当前值,那么待插入值就在该值的后面位置插入;否则将当前位置的值向后移动一位,给待插入值腾出空间 (因此插入值必须一开始先保存,如果被最后一个值给覆盖了,该插入值就没了)。
从上面的内容中可以很明显看出插入排序有一个可以优化的地方,就是插入排序中每次查找插入元素的位置时,我们可以利用前面已排好序的特点,使用二分法快速定位插入元素位置,这样会比从后向前一个一个比较要高效许多,特别是在排序规模较大时,尤为明显。
二分法查找是一种非常高效的搜索方法,主要原理是每次搜索可以抛弃一半的值来缩小范围。其时间复杂度是O(log2n),一般用于对普通搜索方法的优化。使用二分法时一定要保证数组是排序的,例如下面的数组:
5 8 10 12 17 20 25 26
假设想查找 10 的位置,首先给个头尾指针:first 和 end,分别指向 0 和 7 的位置。取中间数 mid = (0 + 7) / 2 = 3 ,而 nums[3] = 12 > 10,可知 10 的位置肯定在 first 和 mid 指针之间。此时我们将 end = mid,然后继续使用前面那样的方式在左边数组中查找,直到最后 first >= end 为止。
在 Python 中有一个二分查找模块:bisect,该模块中的方法都是基于二分查找法,可以使用 bisect_right() 方法来辅助我们快速实现插入元素的定位。首先在 Python 的交互式模式下测试下该方法:
>>> from bisect import bisect_right
>>> x = [2, 3, 4, 7, 9, 12]
>>> bisect_right(x, 5)
3
>>> bisect_right(x, 1)
0
>>> bisect_right(x, 13)
6
>>> bisect_right(x, 7)
4
可以看到,bisect_right() 方法快速返回了待插入元素在有序列表中的位置。
注意:在 bisect.py 模块中,bisect_right() 方法又给了一个别名,就是 bisect:
# 源码位置:lib/bisect.py
# ...
# Create aliases
bisect = bisect_right
于是我们给出基于二分法的插入排序算法:
from bisect import bisect
def insert_sort2(nums):
"""
插入排序: 使用二分法实现元素快速插入
"""
if not nums:
return False
for i in range(1, len(nums)):
k = bisect(nums[:i], nums[i])
nums[k], nums[k + 1: i + 1] = nums[i], nums[k:i]
return True
可以看到,使用了二分模块之后,插入排序算法的代码变得非常简洁,而且也相比原来的代码高效了不少。大家可以把排序的规模弄到万级别上进行测试和对比,就能够看到代码的区别。
对于使用的链表结构的数据我们无法向前面那样使用二分法进行插入位置的获取,但这样的链表结构有一个好处,就是我们找到对应的插入位置后,后面的元素不用整体后移,只需要 O(1) 的复杂度即可插入成功。
我们就来实现一下这个基于链表的插入排序算法。首先是要分析链表插入的思路,来看初始的状态:
图片描述
我们自定义一个有序链表的头部 head,这样是为了后面操作的方便。接下来每次从无序链表中选择一个数据插入到有序链表中,首先完成初始代码如下:
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
def insertion_sort_list(head):
# 定义一个新的有序节点
new_sorted = ListNode(0)
p = head
while p:
# 每次插入值为p.val的节点
# ...
# 重新指向下一个待插入的数据
p = p.next
return new_sorted.next
可以看到,比较核心的代码就是对于有序链表,如何插入一个值并保证链表的有序。我们的有序链表插入过程如下,需要准备 prev 和 cur 两个指针,分别表示当前比较的值的位置和上一个元素的位置,来看看插入有序链表的示意图:
图片描述
由于链表的单向性,我们需要从有序链表的第一个元素开始比较,直到找到元素值大于该插入节点的值,然后就需要执行插入动作。这里需要考虑两种极端情况,一种是插入到最前面,另一种是插入到最后面。完整的链表插入排序算法的代码如下(代码中已做好详细注释):
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
def insert_link_sort(head):
sorted_head = ListNode(0)
p = head
while p:
prev = sorted_head
cur = prev.next
if not cur:
# 针对于第一次,第一个元素直接挂到sorted_head后即可
sorted_head.next = ListNode(p.val)
else:
find = False
while cur:
if p.val > cur.val:
# 插入节点值大于当前指向的节点值,将cur和prev之后后移一位再比较
cur = cur.next
prev = prev.next
else:
# 当前节点值大于插入元素的值,在此执行插入操作然后退出循环
insert_data = ListNode(p.val)
prev.next = insert_data
insert_data.next = cur
find = True
break
# 对于大于所有的值,需要插入到有序链表的末尾
if not find:
prev.next = ListNode(p.val)
p = p.next
return sorted_head.next
最后这个在 leetcode 上有一道对应的编程题,题号为147,难度标记为中等。我这里的代码并不优秀,只是看起来比较简单明了,额外用了许多空间。大家有兴趣的话可以优化下代码,实现在原链表的基础上完成插入排序。
本小节中,我们介绍了插入排序算法的排序原理及思路,同时分别完成了3种情况的插入排序算法,特别是最后一种情况的实现,涉及到链表操作,大家要多多练习,掌握这些基础的排序算法。
Tips:文中动图制作参考:https://visualgo.net/zh/sorting。
0/1000