转载~基于比较的排序算法的最优下界为什么是O(nlogn)
基于比较的排序算法的最优下界为什么是O(nlogn)
发表于2013/12/21 16:15:50 1024人阅读
分类: Algorithm
1.决策二叉树
回答这个问题之前我们先来玩一个猜数字的游戏,我从1到8中挑一个数字出来让你来猜,每回合你都可以问我一个问题,我的回答“是”或“不是”(1或0),那么你至少需要几个回合才能保证猜出这个数字?比较符合这个游戏精神的玩法是从自己的幸运数字(比如我的是7)开始猜起,一个一个地问我“是不是X?”,可能你的运气足够好,一个回合就能够猜对,但是在最坏的情况下可能就需要8个回合,所以你的答案应该是“至少需要8个回合”(事实上你至少只需要一次就“有可能”猜出来,但为了“保证能”猜出来,你只好委曲求全地说8),换句话说这种猜法的最优下界是8。(平均性能是1×1/8+2×1/8+…+8×1/8=(1+…8)/8=4.5)
但因为你会二分,所以会这样问“是不是比4大?”……而且无论我挑出的数字是几,都只用3个回合。显然这是一种更佳的策略,那么它好在什么地方呢?
如果用信息论的思想来解释,这种猜法每一轮(提问并得到反馈)得到的信息量更大。因为在你不知道这个问题的答案时,我回答“是”和“不是”的概率是相同的(如果你不打开盖子,猫是死还是活的概率是相同的),因此每回合你所获取的信息量都是最大的(熵)。而第一种猜法,比方你第一次问我“是不是7?”,我回答“不是”的概率为“是”的概率的7倍(1/8:7/8),因此得到的信息量就少了。如果你问我“是不是42?”那么信息量就更少了(为0),因为我回答“是”的概率为0……相当于你这个问题白问了。
这就像一个未知的世界,一开始你对这个世界一无所知,然后你通过问我问题来获取一些信息,直到你所取得的信息量能够帮助你认清这个世界。
另一种更加形象的模型是决策树(如图),每一个决策都将引出两个结果,叶子节点代表数字已经猜出。二分思想的决策树十分平衡,因此每次猜测无论是对还是错都能将够将数字的范围缩小一半。最优下界即二叉树的深度,具有L片树叶的二叉树的深度至少是logL,所以logn是n个数字的最优下界。而下面那棵二叉树,虽然很有可能在在第一次分支处就使游戏终结,但是却有很大的概率会失败(需要接着往下猜),这个时候回过头来看刚刚的决策——仅仅将范围缩小了一点点。从直观上感觉这种方法也是比较冒风险的。
2.比较排序的决策树模型
绕了一个大圈子其实就是为了说比较排序的决策树模型。a1,a2,a3……an排序总共有n!总结果,(其中a1'<=a2'<=a3'……<=an')所占的概率是1/n!,每进行一次比较,就是在这n!种结果中进行二分,接着选择一个二分结果进行下一次二分,直到找到想要的排序。排序算法能不能自顶向下构造出一棵决策树?因为我们讨论的是基于输入元素的比较排序,每一次比较的返回不是0就是1,这恰好可以作为决策树的一个决策将一个事件分成两个分支。比如冒泡排序时通过比较a1和a2两个数的大小可以把序列分成a1,a2……an与a2,a1……an(气泡a2上升一个身位)两种不同的结果,因此比较排序也可以构造决策树。根节点代表原始序列a1,a2,a3……an,所有叶子节点都是这个序列的重排(共有n!个,其中有一个就是我们排序的结果a1',a2',a3'……an')。如果每次比较的结果都是等概率的话(恰好划分为概率空间相等的两个事件),那么二叉树就是高度平衡的,深度至少是log(n!)。又因为log(n!)的增长速度与 nlogn 相同,即 log(n!)=Θ(nlogn),这就是通用排序算法的最低时间复杂度O(nlogn)的依据。
-------------------------------------------------------------------------------------------
证明log(n!)=Θ(nlogn)等价于证明①、②
①log(n!)=O(nlogn)
显然n!<n^n,两边取对数就得到log(n!)<nlog(n)。
②log(n!)=Ω(nlogn)
n!=n(n-1)(n-2)(n-3)…1,把前n/2个因子(都大于n/2)全部缩小到n/2,后n/2个因子全部舍去,得
n!>(n/2)^(n/2)。两边取对数,log(n!)>(n/2)log(n/2),后者即Ω(nlogn)。
-------------------------------------------------------------------------------------------
为了理解O(nlogn)这个公式的含义,下面来看这样一道题:排序5个数至少需要几次比较?
用合并排序(merge sort,算法复杂度为O(nlogn))对3、2、5、1、4进行排序。下面给出了5个数合并排序的归并树,共需要4+2+1+1=8次比较。
[1][2][3][4][5]
/ \
[2][3][5] [1][4]
/ \ / \
[2][3] 5 1 4
/ \
3 2
那么,这是最优的吗?通过决策树模型,我们知道基于比较的排序算法的算法复杂度是log(n!),因此排序5个数所需要最小的比较次数应该是7次(log(5!)=log(120)≈6.91),而归并排序用了8次。
虽然log(n!)和nlogn的增长率相同,但在n比较小的时候,后者的值差不多是前者的两倍。(下表是这函数的增长规律,log(n!)比较难计算,用chromey calculator最多只能算到log(170!))。
n 2 3 4 5 10 20 30 50 100 150 170
log(n!) 1 3 5 7 22 61 108 214 525 873 1019
nlogn 2 5 11 12 33 86 147 282 664 1084 1260
如果我们用nlogn这个公式计算5数归并排序的算法复杂度,得到的结果应该是12,事实上只需要8次比较即可。原因是在用“递归树”(算法导论p22)计算merge sort的算法复杂度的时候,我们保守估计了每层的复杂度,n是已经是一个上界了,换句话说每层是O(n),有logn+1层,因此归并算法的最优下界是O(nlogn)。
实际的使用归并算法排n数的复杂度总是要低于nlogn(因为每层比较次数少于n)的,能否等于log(n!)(最优的下界)呢?不可能,反例就是n=5,(证明一个东西错误总是比证明它正确容易得多——Knuth),那么为什么不行呢?还是那个老问题,看它对于事件的划分。虽然我没有画出5数归并排序的决策树,但是从归并树上也可以看出问题出在“ [2][3][5](2次)”的这一步。这一步有两次比较:第一次比较2和5,较小的数字进入a[0];第二次将较大的那个数和3进行比较,较小的进入a[1],较大的进入a[2]。而在第一次比较时就出现了概率不均的场面,如果5<2将产生[5][2][3]这一种结果,反之将得到[2][5][3]和[2][3][5]两种结果,概率空间1:2!
下图给出了用7次排5数的决策树(如果对称则省去一支),可以看到每次划分都是十分均衡的。
划分的关键是第三、第四次比较,“a与b”和“c与d”的那比较肯定是等概率的,如果之后分别将e与a和b(或者c和d)比较,将会使两个分支出现一大一小的场面,在比较的初期,出现这种不平衡是致命的!一个分支可能提前“解放”,经过三两次二分就得到了结果,另一个分支的“责任”则突然变大,导致无法再指定次数内完成分解。
唯一的不均衡出现在第5层,因为15不能被2整除,所以7:8已经算得上是很不错的划分了。所以只有在n!=2^k时,才有可能出现一棵高度平衡的二叉树。
因此这个排序算法的最优下界好于merge sort,缺点是只能排5数,因此这个算法不是通用算法,而诸如归并、快排、堆排、希尔在内的最优下界为nlogn的算法都是通用的
转载~基于比较的排序算法的最优下界为什么是O(nlogn)的更多相关文章
- [ 转载 ] js十大排序算法:冒泡排序
js十大排序算法:冒泡排序 http://www.cnblogs.com/beli/p/6297741.html
- 基于Qt5的排序算法简单可视化
之前写了几个排序算法,然后看到别人将排序算法的过程可视化出来,所以就想尝试一下,然后就用Qt简单写了个界面,用QImage和QPainter来画图显示,代码比较简单. 我的想法是画图的时候,图像的X轴 ...
- 不基于比较的排序算法:Counting-sort和Radix-sort
- 排序算法之堆排序(Heapsort)解析
一.堆排序的优缺点(pros and cons) (还是简单的说说这个,毕竟没有必要浪费时间去理解一个糟糕的的算法) 优点: 堆排序的效率与快排.归并相同,都达到了基于比较的排序算法效率的峰值(时间复 ...
- 常见排序算法总结分析之选择排序与归并排序-C#实现
本篇文章对选择排序中的简单选择排序与堆排序,以及常用的归并排序做一个总结分析. 常见排序算法总结分析之交换排序与插入排序-C#实现是排序算法总结系列的首篇文章,包含了一些概念的介绍以及交换排序(冒泡与 ...
- 第32讲:List的基本操作实战与基于模式匹配的List排序算法实现
今天来学习一下list的基本操作及基于模式匹配的排序操作 让我们从代码出发 val bigData = List("hadoop","spark") val d ...
- 【转载】常见十大经典排序算法及C语言实现【附动图图解】
原文链接:https://www.cnblogs.com/onepixel/p/7674659.html 注意: 原文中的算法实现都是基于JS,本文全部修改为C实现,并且统一排序接口,另外增加了一些描 ...
- 十大经典排序算法(java实现、配图解,附源码)
前言: 本文章主要是讲解我个人在学习Java开发环境的排序算法时做的一些准备,以及个人的心得体会,汇集成本篇文章,作为自己对排序算法理解的总结与笔记. 内容主要是关于十大经典排序算法的简介.原理.动静 ...
- <Data Structure and Algorithm>排序算法
排序稳定:如果两个数相同,对他们进行的排序结果为他们的相对顺序不变.例如A={1,2,1,2,1}这里排序之后是A = {1,1,1,2,2} 稳定就是排序后第一个1就是排序前的第一个1,第二个1就是 ...
随机推荐
- Python学习之set集合
set集合以{}保存一组可迭代对象,如列表,字符串,set集合本身.集合内的元素若有重复的,将自动去除重复元素 a=set([1,2,3]) print(a) b=set('hello python' ...
- Android Stadio 导入moudle 不显示
Android Stadio 导入moudle 不显示,moudle 里面的java类也没有识别,只当是普通的txt文件. 后来,我发现,每个moudle 都有一个.iml 文件~ 然后我就随便翻翻配 ...
- Redis数据更新
技术交流群: 233513714
- cycling -avoid the vicious cycle
‘Numerous' studies in the past appear to have shown a link between cycling and ED. The researchers a ...
- Oracle 11g数据库安装与卸载的方法图解(windows)
一.Oracle 11g安装 安装之前要先确定自己的电脑配置,以windows为例,如果是win7以下系统如xp等,可以选择Oracle 10g.因为10g的程序文件只有200多兆,而11g及达到了2 ...
- 《Cracking the Coding Interview》——第12章:测试——题目3
2014-04-24 23:28 题目:玩象棋游戏,你要设计一个bool型的方法来检测一个棋子能否移动到指定位置. 解法:不同的棋子有不同的移动规则,那么应该采取棋子基类实现接口,各个棋子子类来实现的 ...
- 天性 & 如水一般,遇强则强 —— 梦想、行为、性格
开篇声明,我博客中“小心情”这一系列,全都是日记啊随笔啊什么乱七八糟的.如果一不小心点进来了,不妨直接关掉.我自己曾经写过一段时间的日记,常常翻看,毫无疑问我的文笔是很差的,而且心情也是瞬息万变的.因 ...
- Windows10使用pip安装python包时报错-UnicodeDecodeError: 'ascii' codec c
本人是Windows10,用的方法2解决的 原文链接http://blog.csdn.net/all_over_servlet/article/details/45112221 先交待下开发环境: 操 ...
- css 之 border-radius属性
css中给盒子设置圆角可以通过 border-radius 属性来实现(具体原理就不深入探讨了); 在开发过程中都会遇到浏览器兼容问题,这问题其实也不难解决,无非就是加上私有前缀,在这里先忽略掉. ...
- 第一天docker入门
[01 入门] docker 最核心为三部分组成 镜像,仓库和容器 镜像:一个只读的模板 仓库:代码仓库,镜像的集合 容器:镜像的实例化进程 我们可以这样理解 容器就是一个沙箱,docker利用容器运 ...