本教程带你从初级到高级全面掌握 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 高阶特性一网打尽
今天我们聊一聊计算机中非常重要和常用的一种算法:分治算法。它在计算机领域应用广泛,几乎无处不在。不仅计算机领域,在信号处理领域,分而治之也是十分常见的一种信号处理方法。著名快速傅里叶变换算法 (FFT) 正是使用了分而治之的思路,才使得数字信号处理算能广泛使用,这也才造就了我们今天丰富多彩的生活。
分而治之是计算机领域中非常重要的一种思想:即将大规模问题每次通过分解成小问题进行处理,在处理完小问题之后再将结果合并成大问题的最终结果。
非常典型的一个示例是排序算法中的归并排序。假设我们要排序 8 个数,我们首先将 8 个数的列表分解成两个 4 个数的列表,一直划分下去直到列表中最多只有 2 个元素。接下来对所有的 2 个或者 1 个元素的列表进行排序,然后再两两合并,直到最后合并成最终长度为 8 的列表,则排序结束。其算法示意图如下所示:
图片描述
可以看到:归并排序中的一个核心步骤是对两个有序数组的合并过程,对此使用的合并算法过程如下图所示:
图片描述
有了上面的算法过程,我们就可以写出合并两个有序列表的代码,如下:
def merge_sorted_list(nums1, nums2):
"""
合并有序列表
"""
nums = [0] * (len(nums1) + len(nums2))
id, id1, id2 = 0, 0, 0
# 循环条件,当有一个等于数组长度时,终止
while id1 < len(nums1) and id2 < len(nums2):
# 对应着上图中第三个操作,多加了边界操作
while id1 < len(nums1) and id2 < len(nums2) and nums1[id1] <= nums2[id2]:
nums[id] = nums1[id1]
id += 1
id1 += 1
# 对应着上图第二个部分操作,多加了边界操作
while id1 < len(nums1) and id2 < len(nums2) and nums2[id2] < nums1[id1]:
nums[id] = nums2[id2]
id += 1
id2 += 1
# 对应于图片的最后部分,如果一个数组先走完,另一个数组剩余部分直接添加到新数组的最后
if id1 != len(nums1):
nums[id:] = nums1[id1:]
if id2 != len(nums2):
nums[id:] = nums2[id2:]
return nums
接下来从分解到合并这个过程是不是特别像递归?看看递归三要素应该怎么做,第一是终止条件,很明显在列表长度为 1 或者 2 时终止;第二是方法的返回值,可以是列表头,表示得到新的排序的列表;第三是递推公式,即将已经排好序的结果合并成完整的排序结果,用的正是上面的合并方法,这样我们就可以写出如下归并排序的代码:
def merge_sort(nums):
"""
递归终止条件
"""
if len(nums) == 1:
return nums
if len(nums) == 2:
if nums[0] > nums[1]:
nums[0], nums[1] = nums[1], nums[0]
return nums
# 递归公式,将列表平均分成两份,然后递归排序下去
half = len(nums) // 2
nums1 = merge_sort(nums[:half])
nums2 = merge_sort(nums[half:])
# 调用合并操作,得到最终结果
return merge_sorted_list(nums1, nums2)
从上面的排序算法示例中我们可以看到典型分治算法的解题步骤如下:
另一个分治算法的典型应用是大整数相乘问题。对于两个以字符串形式表示的长整数,计算其相乘结果。关于分治法比较核心的一个问题就是,找到如何将子问题的解合并成父问题的解。我们简单描述一下这个基于分治思想的大数乘法算法,又叫 Karatsuba 算法。
假设 x 和 y 是两个要做乘法的十进制大数,并且位数分别为 m 和 n。若将 x 和 y 分别均分成两部分,则 x 和 y 可以表示如下:
此时有:
这样大整数乘法就被分解成 4 个长度减半的整数的乘法。如此执行下去,直到执行的乘法的两数足够小时,然后计算反推最终结果。根据上面的执行思路,我们开始一步一步得到大整数相乘的分治代码。
首先处理初始以及最后分治的终止条件:如果 s1 或者 s2 中有一个为空串,那么相乘结果为 '0‘,注意输出字符串;此外,分解到最后 s1 和 s2 字符串的长度均为1 时,我们可以直接将两个字符串强装成数字然后进行相乘,最后返回的结果也要再转成字符串:
if not s1 or not s2:
# 有一个为空,则结果为0
return '0'
elif len(s1) == 1 and len(s2) == 1:
# 终止条件
return str(int(s1) * int(s2))
接下来开始对字符串 s1 和 s2 进行分治求解:
# 其余情况
l1 = len(s1) // 2
l2 = len(s2) // 2
# 将s1分成两部分
a = s1[:l1]
b = s1[l1:]
# 将s2分成两部分
c = s2[:l2]
d = s2[l2:]
接下来我们分别得到了4个子串:a、b、c、d。按照前面的公式,我们分别要计算 ac、bc、ad 和 bd 的值。这些值可以使用递归方法得到,要注意相乘结果的10次幂需要保留,以便后续能合成最终问题的解。
mi = len(b) # 对于a的10次幂
ni = len(d) # 对于c的10次幂
# 分别计算ac, ad, bc, bd,分别补上相应的幂次,使用分治计算
x1 = bigDataMultiple(a, c) + '0' * (mi + ni)
x2 = bigDataMultiple(a, d) + '0' * mi
x3 = bigDataMultiple(b, c) + '0' * ni
x4 = bigDataMultiple(b, d)
最后是计算 x1+x2+x3+x4 的值便可得到原大问题的解,由于 x1~x4 均为字符串,我们按照字符串上每位数字相加来进行。首先需要将所有字符串的长度统一,对于短字符串需要将前面补零:
max_len = max(len(x1), len(x2), len(x3), len(x4))
x1 = '0' * (max_len - len(x1)) + x1
x2 = '0' * (max_len - len(x2)) + x2
x3 = '0' * (max_len - len(x3)) + x3
x4 = '0' * (max_len - len(x4)) + x4
接着从字符串最后一位开始,计算每位上的值,注意进位问题即可:
res = ''
c = 0
for i in range(max_len - 1, -1, -1):
s = int(x1[i]) + int(x2[i]) + int(x3[i]) + int(x4[i]) + c
res = str(s % 10) + res
c = s // 10
# 注意,如果循环完后进位>0,需要补上进位值
if c > 0:
res = str(c) + res
然而,通过提交代码发现有时候在合成最终结果时,字符串前面会出现 ‘0’。因此下面的代码就是找到前面非 ‘0’ 开头字符的位置:
# 去掉res最前面的'0'
k = 0
while res and k < len(res) and res[k] == '0':
k += 1
最后得到大整数相乘的最终结果为:res[k:]。
综合前面的分析与讨论,得到完成的处理大整数相乘问题的 Python 实现如下:
def bigDataMultiple(s1, s2):
"""
计算 s1 * s2,返回大整数相乘结果
"""
if not s1 or not s2:
# 有一个为空,则结果为0
return '0'
elif len(s1) == 1 and len(s2) == 1:
# 终止条件
return str(int(s1) * int(s2))
# 其余情况
l1 = len(s1) // 2
l2 = len(s2) // 2
# 将s1分成两部分
a = s1[:l1]
b = s1[l1:]
# 将s2分成两部分
c = s2[:l2]
d = s2[l2:]
mi = len(b) # 对于a的10次幂
ni = len(d) # 对于c的10次幂
# 分别计算ac, ad, bc, bd,分别补上相应的幂次,使用分治计算
x1 = bigDataMultiple(a, c) + '0' * (mi + ni)
x2 = bigDataMultiple(a, d) + '0' * mi
x3 = bigDataMultiple(b, c) + '0' * ni
x4 = bigDataMultiple(b, d)
# 将计算的结果根据最长的补零,方便后面直接相加计算
max_len = max(len(x1), len(x2), len(x3), len(x4))
x1 = '0' * (max_len - len(x1)) + x1
x2 = '0' * (max_len - len(x2)) + x2
x3 = '0' * (max_len - len(x3)) + x3
x4 = '0' * (max_len - len(x4)) + x4
# 计算x1+x2+x3+x4的值,也就是原问题的解
res = ''
c = 0
for i in range(max_len - 1, -1, -1):
s = int(x1[i]) + int(x2[i]) + int(x3[i]) + int(x4[i]) + c
res = str(s % 10) + res
c = s // 10
# 注意,如果循环完后进位>0,需要补上进位值
if c > 0:
res = str(c) + res
# 去掉res最前面的'0'
k = 0
while res and k < len(res) and res[k] == '0':
k += 1
return res[k:]
这道题是牛客网上的原题,我实现的分治算法略微复杂,大家可以参考上面的一些题解,有非常精简和高效的实现。多参考优秀的代码对我们提升自己编程能力也是有莫大好处的。
分治法的时间复杂度可以这样来分析,假设对于规模为 n 的问题,其直接计算的复杂度为 。那么我们看一下分解成 2 个子问题后其复杂度如何?我们以前面的归并排序的为例,如果是采用前面的选择排序、冒泡排序,算法的复杂度为 ,我们将其分解成两个 长度的列表进行排序后再合并,这样一次分解的时间复杂度为:
注意:后面的 n 为合并方法的时间复杂度。如果问题规模 n 非常大,此时一次分解再合并后的时间复杂度相比原算法少了一半,如果我们再次分解问题规模呢?
可以看到再次减少大约一半的计算量!!!如果原问题的复杂度是 甚至 ,那么使用分治算法能明显改进原算法性能,当前前提是该算法能进行分治求解。对于前面归并排序问题,其平均的时间复杂度最终可以达到 。
本小节我们使用两个典型的例子对分治算法的原理以及算法实现步骤进行了相应的说明,通过这两个简单的例子我们能体会分治算法的特点:简单高效。接下来我们会用更多 leetcode 习题来帮助我们巩固这一节所学的算法。
0/1000