pythonsort函数时间复杂度_合并排序算法——时间复杂度详解和python代码实现

递归形式

递归形式是算法中常用到的一种构造思路。递归允许函数或过程对自身进行调用,是对算法中重复过程的高度概括,从本质层面进行刻画,避免算法书写中过多的嵌套循环和分支语法。因此,是对算法结构很大的简化。

递归形式实际可以看做一个函数表达式:

f ( n ) = G ( f ( g ( n ) ) ) f(n)=G(f(g(n)))f(n)=G(f(g(n))),即f ( n ) f(n)f(n)可以通过一个f ( g ( n ) ) f(g(n))f(g(n))的表达式间接推出。当然,如果递归式可解,则最后也能将f ( n ) f(n)f(n)直接用n表示出来。

如:f ( n ) = f ( n − 1 ) + n f(n)=f(n-1)+nf(n)=f(n−1)+n (1)

f ( n ) = f ( n 2 ) + n f(n)=f(\frac{n}{2})+nf(n)=f(2n​)+n (2)

f ( n ) = f ( n 1 ) + f ( n 2 ) + . . . + f ( n k ) f(n)=f(n_1)+f(n_2)+...+f(n_k)f(n)=f(n1​)+f(n2​)+...+f(nk​) (3)

可以想到,如果运行时间可以用这样的递归表达式,那么求解f ( n ) f(n)f(n)可能会相对简单。

递归形式分为线性递归、二分递归、多分支递归等。

在上面举例中,(1)式即为线性递归(该递归关系实际上描述了求和过程);(2)式即为二分递归(后面会看到,该关系实际描述了合并排序过程)。(3)式即为多分支回归,即每次分成的子问题规模不一定相同。将这些递归形式应用于算法中,就形成了遍历、减治、分治等算法策略。此处重点讨论分治法,并且仅关注分治法应用于排序问题中的一种经典算法:合并排序。

合并排序

基本思想:分治法

分治法是把一个规模较大的问题分成若干个规模较小的问题,并递归的对这些问题进行求解,当问题规模足够小时,可以直接求解。最终将这些规模小问题的求解结果递归的合并,建立原问题的解。

使用分治法的意义之一是,这样容易求出算法的时间复杂度,有很多方法可以套用。可以达成这样目标的一个前提是,1.运行时间可以用一个递归式表示;2.分解到最后,规模足够小的子问题应当是常数时间就可以求解的,否则还是没法简化时间复杂度的计算。

在排序过程中,主要分成两个步骤:首先,将n个数用二分递归分解,直到每个子问题规模为1。再将这些子问题递归的合并,合并就是一个排序的过程。

下面以一个排序问题举例:对A=(3,2,4,6,1,5,7,8)用合并排序从小到大排列。

分解过程

这是一个规模为8的排序问题。首先将问题不断二分直到8个规模为1的子问题。因为每步操作的结构类似,因此这几步操作可以用一个递归函数merge-sort表示:merge-sort(A,p,r),其中p为每步分解操作中子序列头的位置,r表示子序列尾的位置。如何保证若干次操作后子序列规模能降到1呢?这就需要对头和尾数字的位置进行比较。其操作过程是:若p

回到上述例子,分解之后,A=({3},{2},{4},{6},{1},{5},{7},{8})

merge过程

当子问题变成单元素时,就开始调用merge过程,即两个已经排好序的子序列合并,在合并的过程中就要排序。因此,在每一次merge前,都有左右两个子序列已经排好序(这里和插入排序中,当选择新元素判断插入序列位置时,已有序列已经排好序这一思想有共通之处)。

此时思路仍可以类比抓牌。现在有两堆牌(A,B)。每堆都已经从小到大排列,小的放上面,大的放下面。先比较A和B堆中最上面的牌(A1,B1)。若A1>B1,则把B1拿出来放在第三堆(C)中,令为C1。接着,把留下的A1继续跟B2比,若A1还大则重复上述操作,若A1小则把A1取出,令为C2,而换B2和A堆中剩下元素比较。最终,某一堆的数字可能被全部取出,而另一堆还没取完,则此时直接把另一堆的数字追加到C堆后面即可。

可以看出,这样操作就不是插入排序那样的原地排序了,而是每层递归都要新生成一个空序列以存放每层排好序的元素。当然,为了节省空间复杂度,这个储存空间需要尽快释放。

总的来说,计算顺序是先对原序列递归分解→ \to→直到子序列为单元素→ \to→对子序列递归合并+排序,直到序列总长度等于原问题规模。

将解这个问题的过程写成merge-sort(n),问题规模为n。下面讨论如何将这个问题的运算时间T ( n ) T(n)T(n)用递归式表示,这里仍然是基于RAM模型的假设。解这个问题分为哪几个步骤呢?

首先,需要找到这个问题的中间元素,分解为两个子问题。而因为这一步只要计算中间的索引即可,其运算时间与问题规模无关,是常数c 1 c_1c1​;其次,需要对分成的两个子问题分别调用该过程merge-sort(n/2)。求解这两个子问题的时间即为T ( n 2 ) T(\frac{n}{2})T(2n​)。最后,当子问题全部解完之后,需要对这两个子问题合并,合并的merge(n)过程需要c 2 n c_2nc2​n运行时间。因为合并是将每个堆最上面的元素进行比较,若要合并成n个元素,则一共最多要比较n次(回想抓牌过程),每次比较只要常数时间c 2 c_2c2​。由于c 2 n + c 1 c_2n+c_1c2​n+c1​仍然是n的一个线性函数,可以表示为Θ ( n ) \Theta(n)Θ(n),因此得到运行时间的递归表达式:

T ( n ) = T ( n 2 ) + Θ ( n ) T(n)=T(\frac{n}{2})+\Theta(n)T(n)=T(2n​)+Θ(n)

伪代码

首先看merge-sort伪代码,即假设两侧序列都已经排好序,如何对这两段序列合并排序。

有一个需要注意的构造技巧。排序总体应当分为两个阶段:1.所有堆中的元素均非空时,这样只要一直比较两堆中最上面的元素即可;2.当某堆中的元素被取完时,这样直接把非空的那堆追加到排好序的序列后面即可。因此,在选取某堆最上面元素的时候,需要先判断该堆元素是否为空。一种自然的想法是把这堆元素循环计数,看个数是否为0。但这样的话,每轮合并都要把所有元素循环一遍很费时间。因此,简化的操作是每轮合并后对每堆序列的最下层追加一个一定不属于该序列的“哨兵”,因此只要某轮轮合并排序的时候发现了“哨兵”,说明该堆已空。

“哨兵”如何选择?首先,需要排序的元素里一定不存在∞ \infty∞,并且,利用∞ \infty∞比所有数大的性质也可以进一步简化代码,直接与两堆数字比较的过程融合,不需要单独写一行代码判断下一个数是否是哨兵。

以下是merge过程伪代码:

MERGE(A, p, q, r)

1 n1 ← q - p + 1

2 n2 ← r - q

3 create arrays L[1 ‥ n1 + 1] and R[1 ‥ n2 + 1]

4 for i ← 1 to n1

5 do L[i] ← A[p + i - 1]

6 for j ← 1 to n2

7 do R[j] ← A[q + j] //将A分成两堆,用这两堆的比较更新A序列

8 L[n1 + 1] ← ∞ //新堆尾部插入哨兵

9 R[n2 + 1] ← ∞

10 i ← 1

11 j ← 1

12 for k ← p to r

13 do if L[i] ≤ R[j] //两个新堆最上面的元素比较。这里可以合并遇到∞的情况,是对代码很大的简化

14 then A[k] ← L[i]

15 i ← i + 1

16 else A[k] ← R[j]

17 j ← j + 1

> 引自清华计算机系武永卫老师课件

以下是合并排序merge-sort整个过程的伪代码:

merge-sort(A,p,r)

if p

q = (p+r)/2 //注意这里q是下取整,因此最后总能循环到p>=q

merge-sort(A,p,q)

merge-sort(A,q+1,r)

MERGE(A,p,q,r)

python代码实现

以下是merge过程python代码的实现:

A = [1,4,6,2,4,5,7]

def MERGE(A, p, q, r): ##其中,r为原序列末位数的索引,p为原序列首位数的索引,q为中间某个数的索引(q左右两侧的数已经顺序排列)

L = A[p:q+1]

R = A[q+1:r+1]

L.append(float("inf"))

R.append(float("inf"))

i = 0

j = 0

for k in range(p,r+1):

if L[i]<=R[j]:

A[k] = L[i]

i = i+1

else:

A[k] = R[j]

j = j+1

return A

MERGE(A, 0, 2, 6)

输出结果是一个已经排好序的序列:

>>> [1, 2, 4, 4, 5, 6, 7]

以下是整个合并排序merge-sort过程的代码实现(需要调用前面定义的MERGE函数):

def merge_sort(A, p, r):

if p < r:

q = math.floor((p+r)/2)

merge_sort(A,p,q)

merge_sort(A,q+1,r)

MERGE(A, p, q, r)

return A

A = [1,6,4,2,5,4,7]

merge_sort(A, 0, 6)

>>> [1, 2, 4, 4, 5, 6, 7]

用递归树猜测时间复杂度

如何确定合并排序算法的时间复杂度?虽然用主定理可以直接确定,但这里还是从递归树的角度给出猜测。因为对于一些无法用主定理直接确定的递归算法,还是需要将递归树和代换法结合确定复杂度。用递归树可以提出猜想,用代换法则可以给出该猜想结果的数学证明。

如图所示,是一个递归树的结构。问题总规模为n。这里为了简化问题,令n = 2 k n = 2^kn=2k。令从上到下为第1,2,…m层。用递归树求解总的运算时间,需要每层都看。这也是和写递归式的不同之处。递归式只要看其中任意一层即可。可以从上往下看。第1层的运算时间是从第1-2层的分解加第2-1层的合并,前面提到过,分解与合并时间之和是c 1 + c 2 n c_1+c_2nc1​+c2​n,因为低次项在算渐进界时不重要,可以直接简化为c n cncn。第2层每个问题规模n/2,分解与合并时间之和均为c n 2 \frac{cn}{2}2cn​,因此,第2层总时间也为c n cncn。这样递推可得,所有m层每层运行时间均为c n cncn。如何计算总层数?从n = 2 k n = 2^kn=2k个元素二分降到1个元素,需要降k = l g n k=lgnk=lgn次,因此总层数为m = l g n + 1 m=lgn+1m=lgn+1,故有总运行时间T ( n ) = c n l g n + c n T(n)=cnlgn+cnT(n)=cnlgn+cn

我之前文章提到过,函数增长率只看高阶,因此,猜测该算法运行时间的渐进确界为n l g n nlgnnlgn。

用代换法可以证明,这一结论成立。则其时间复杂度即为Θ ( n l g n ) \Theta(nlgn)Θ(nlgn)。

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

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

相关文章

docker安装gitlab_docker 安装部署gitlab

下载镜像并且启动//下载镜像(镜像较大,如果网速不行可以切换阿里云镜像仓库) docker pull gitlab/gitlab-ce&#xff1a;latest //启动镜像 docker run --name gitlab -d -p 18080:80 -p 1443:443 -p 2222:22 -v /data/gitlab/config:/etc/gitlab -v /data/gitlab/logs:/var/lo…

easyexcel导出百万级数据_百万级别数据Excel导出优化

这篇文章不是标题党&#xff0c;下文会通过一个仿真例子分析如何优化百万级别数据Excel导出。笔者负责维护的一个数据查询和数据导出服务是一个相对远古的单点应用&#xff0c;在上一次云迁移之后扩展为双节点部署&#xff0c;但是发现了服务经常因为大数据量的数据导出频繁Ful…

运行catia_CATIA清除错误目录及防串链接

清除错误目录大家好今天我来教大家如何清除CATIA错误目录&#xff0c;由于我们在日常工作中经常会互相发送数据&#xff0c;而CATIA的缓存功能通常会记录这些文件的路径&#xff0c;所以我们打开别人发的数据时在桌面的右下角通常会有警告的提醒&#xff0c;那我们如何来清除缓…

python 键盘输入数字_九宫格键盘输入

九宫格键盘输入 Letter Combinations of a Phone Number 给定一个数字字符串&#xff0c;返回数字可能代表的所有可能的字母组合。 数字到字母的映射&#xff08;就像九宫格电话按钮一样&#xff09;如下图。 Given a digit string, return all possible letter combinations t…

python更新包列表出错_解决pycharm无法获取安装包文件列表

Pycharm 中Available packages nothing to showManage Repositories:https://pypi.python.org/pypihttps://pypi.tuna.tsinghua.edu.cn/simple/http://mirrors.aliyun.com/pypi/simple/Linux下&#xff0c;修改 ~/.pip/pip.conf (没有就创建一个文件夹及文件。文件夹要加“.”&…

java2组随机数的共通数_java随机数产生-指数分布 正态分布 等

1 指数分布指数分布的概率密度函数&#xff1a;ylamda*exp(-lamda*x)x>0由此可以计算概率分布函数&#xff1a;y1-exp(-lamda*x)x>0y是 X首先&#xff0c;把y当作是在(0&#xff0c;1)区间的均匀分布的随机变量。然后&#xff0c;求y1-exp(-lamda*x)的逆函数&#xff0c…

c# combobox集合数据不显示_excel打开数据时显示乱码/问号amp;看起来一样却v不出来怎么办...

1、乱码问题&#xff1a;今天正好碰到这种情况&#xff0c;想起来写一写。有时从客户那里拿到的CSV等文件&#xff0c;直接用excel打开是这样的&#xff1a;其实观察一下会发现&#xff1f;的地方一般就是中文&#xff0c;实质上是中文字符显示不出来。有小伙伴一直是单独下个W…

mysql 异步 同步 不支持_MySQL C#异步方法不起作用?

我在服务器中有一个数据库,似乎async方法不起作用.这是我的代码&#xff1a;static async void Example(){string connectionString "Servermydomainname.com;" "Port3306;" "Databasescratch;" "UidAssassinbeast;" "Passwordmy…

if嵌套while循环语句_Python学习笔记015--while循环嵌套

while循环嵌套前面学习过if的嵌套了&#xff0c;想一想if嵌套是什么样子的&#xff1f;类似if的嵌套&#xff0c;while嵌套就是&#xff1a;while里面还有while<1>while嵌套的格式while 条件1:条件1满足时&#xff0c;做的事情1条件1满足时&#xff0c;做的事情2条件1满足…

.net mysql 更新_升级 MySql.Data for NET 后遇到的神坑

今天把一个之前基于 http://ASP.NET MVC 5 EntityFramework 5 的项目里的 Nuget 包都升级了一下&#xff0c;包括将 EF 从 6.1.3 升级到 6.2.0&#xff0c;将 MySql.Data (用于 EF 连接 MySQL 数据库)从 6.9.9 升级到 8.0.12&#xff0c;MySql.Data.Entity 从 6.9.9 升级到 6.…

long类型怎么转换成string_Python知识点-Python变量类型有哪些?

Python的内置变量类型是非常基础的知识点&#xff0c;善用变量类型转换在日常的工作学习中会给我们带来非常大的帮助。相对于其他语言&#xff0c;Python的变量类型既有自己的特色方法&#xff0c;也有借鉴前辈的优秀之处。今天酷仔整理总结了关于Python内置变量类型一文&#…

mysql连接服务密码_Hydra爆破常见服务密码

Hydra是一款专业的服务密码探测工具&#xff0c;支持FTP、LDAP、MYSQL、Oracle、POP3、SMB、SSH等网络服务&#xff0c;多用于信息安全检查工作中弱口令登陆测试。实验环境操作系统&#xff1a;Kali 2019.4目标网络&#xff1a;192.168.168.0/24爆破教程第一步 升级系统、软件版…

矩阵求逆c语言实现_[V-SLAM] Bundle Adjustment 实现

SLAM问题的后端有主要有滤波和优化两种方案。目前&#xff0c;普遍认为优化的方法在精度上要超过滤波方法&#xff0c;因为它可以进行多次的线性化。近年来出现的SLAM算法也大都是基于优化的算法&#xff08;如ORB-SLAM、DSO等&#xff09;。优化问题的核心便是Bundle Adjustme…

centos安装 mysql_Linux centos 安装 mysql 5.6

一、mysql下载1、方式一(简单粗暴)直接在linux 目录下wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.43-linux-glibc2.12-x86_64.tar.gz2、方式二(官方下载)浏览器打开网址&#xff1a;https://www.mysql.com如下图依次点击1、2、3、4步如下图设置对应版本点击…

python定义一个类savingaccount表示银行账户_c#教程之c#模拟银行atm机示例分享

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ATM{abstract class Account{//账户号码protected long id;public long ID{get { return id; }set { id value; }}//账户密码protected string password;public string PassWor…

php 在线人数 mysql_PHP+MYSQL实例:编写网站在线人数的程序代码

以下为引用的内容&#xff1a;//Put your basic server info here$server "localhost"; //normally localhost$db_user "root"; //your MySQL database username$db_pass "password"; //your MySQL database password$database "users&…

tracepro杂散光分析例子_光刻机的蜕变过程及专利分析

来源&#xff1a;芯通社近两年&#xff0c;中国芯片产业受到了严重打击&#xff0c;痛定思痛之余也让国人意识到芯片自主研发的重要性。从2008年以来&#xff0c;十年间&#xff0c;芯片都是我国第一大宗进口商品&#xff0c;进口额远超于排名第二的石油。2018年我国进口集成电…

docker mysql日志_面试官问:了解Mysql主从复制原理么?我呵呵一笑

搭建Mysql主从同步之前&#xff0c;我们先来说他们之间同步的过程与原理&#xff1a;同步复制过程献上一张图&#xff0c;这张图诠释了整个同步过程主从复制过程&#xff1a;slave节点与主节点进行连接&#xff0c;建立主从关系&#xff0c;并把从哪开始同步&#xff0c;及哪个…

python 文件加密_python实现文件快照加密保护的方法

本文实例讲述了python实现文件快照加密保护的方法。分享给大家供大家参考。具体如下&#xff1a;这段代码可以对指定的目录进行扫描&#xff0c;包含子目录&#xff0c;对指定扩展名的文件进行SHA-1加密后存储在cvs文件&#xff0c;以防止文件被篡改调用方法&#xff1a;python…

查看socket缓冲区数据_什么是socket缓冲区?

Socket 就是发送和接收网络数据&#xff0c;Socket 有发送缓冲也有接收缓冲&#xff0c;这些缓冲区有什么作用&#xff1f;1、什么是Socket缓冲区&#xff1f;熟悉 Socket 的读者都知道&#xff0c;Socket 的发送和接收&#xff0c;就是调用 send 和 recv 函数。实际操作中&…