基于比较的排序算法的最优下界为什么是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)的更多相关文章

  1. [ 转载 ] js十大排序算法:冒泡排序

    js十大排序算法:冒泡排序  http://www.cnblogs.com/beli/p/6297741.html

  2. 基于Qt5的排序算法简单可视化

    之前写了几个排序算法,然后看到别人将排序算法的过程可视化出来,所以就想尝试一下,然后就用Qt简单写了个界面,用QImage和QPainter来画图显示,代码比较简单. 我的想法是画图的时候,图像的X轴 ...

  3. 不基于比较的排序算法:Counting-sort和Radix-sort

  4. 排序算法之堆排序(Heapsort)解析

    一.堆排序的优缺点(pros and cons) (还是简单的说说这个,毕竟没有必要浪费时间去理解一个糟糕的的算法) 优点: 堆排序的效率与快排.归并相同,都达到了基于比较的排序算法效率的峰值(时间复 ...

  5. 常见排序算法总结分析之选择排序与归并排序-C#实现

    本篇文章对选择排序中的简单选择排序与堆排序,以及常用的归并排序做一个总结分析. 常见排序算法总结分析之交换排序与插入排序-C#实现是排序算法总结系列的首篇文章,包含了一些概念的介绍以及交换排序(冒泡与 ...

  6. 第32讲:List的基本操作实战与基于模式匹配的List排序算法实现

    今天来学习一下list的基本操作及基于模式匹配的排序操作 让我们从代码出发 val bigData = List("hadoop","spark") val d ...

  7. 【转载】常见十大经典排序算法及C语言实现【附动图图解】

    原文链接:https://www.cnblogs.com/onepixel/p/7674659.html 注意: 原文中的算法实现都是基于JS,本文全部修改为C实现,并且统一排序接口,另外增加了一些描 ...

  8. 十大经典排序算法(java实现、配图解,附源码)

    前言: 本文章主要是讲解我个人在学习Java开发环境的排序算法时做的一些准备,以及个人的心得体会,汇集成本篇文章,作为自己对排序算法理解的总结与笔记. 内容主要是关于十大经典排序算法的简介.原理.动静 ...

  9. <Data Structure and Algorithm>排序算法

    排序稳定:如果两个数相同,对他们进行的排序结果为他们的相对顺序不变.例如A={1,2,1,2,1}这里排序之后是A = {1,1,1,2,2} 稳定就是排序后第一个1就是排序前的第一个1,第二个1就是 ...

随机推荐

  1. POJ:2100-Graveyard Design(尺取)

    Graveyard Design Time Limit: 10000MS Memory Limit: 64000K Total Submissions: 8504 Accepted: 2126 Cas ...

  2. [bzoj3071]N皇后

    哈哈哈水题~ 但是不能一眼看出来的..我想了一个小时?! 题面 Description “国际象棋中,一方的皇后数不能超过5个” 一个N*N的棋盘,任意摆放皇后,最坏情况下最少需要多少个皇后才能保证所 ...

  3. [工具使用]xshell 中“快速命令集”的使用

    突然看到朋友的xshell比我多一个按钮,且一点,哈哈哈 ,实现了很炫酷的功能,耐不住好奇,问了一句,原来是快速命令集! 1.选择快速命令集(两种方法a&b) a:文件 > 属性 > ...

  4. 解决NSTimer循环引用

    NSTimer常见用法 @interface XXClass : NSObject - (void)start; - (void)stop; @end @implementation XXClass ...

  5. Win10开始菜单中的天气不更新问题的解决方法

    两台电脑同时做的Win10系统,最新的1703 Creator Update 版本,其中一台的开始菜单中天气方块总是显示图标,试了各种方法都不行,最后是点开天气App,在App的顶端有几个按钮,其中有 ...

  6. 《Cracking the Coding Interview》——第5章:位操作——题目4

    2014-03-19 06:15 题目:解释(n & (n - 1)) == 0是什么意思? 解法:n&n-1是去掉最低位‘1’的方法.根据运算符优先级,貌似用不着加那个括号,但位运算 ...

  7. 《Cracking the Coding Interview》——第2章:链表——题目4

    2014-03-18 02:27 题目:将一个单链表按照一个值X分为两部分,小于X的部分放在大于等于X的部分之前. 解法:按照值和X的大小,分链表为两条链表,然后连起来成一条. 代码: // 2.4 ...

  8. jmeter学习(二),如何安装jmeter?

    官网地址:http://jmeter.apache.org/download_jmeter.cgi 如下图数字3.2表示的是版本号,jmeter是基于java的压力测试工具.所以运行环境一定要满足最低 ...

  9. 使用pip命令报You are using pip version 9.0.3, however version 18.0 is available pip版本过期.解决方案

    使用pip命令安装或卸载第三方库时报You are using pip version 9.0.3, however version 18.0 is available.错误,一般情况下是pip版本过 ...

  10. 孤荷凌寒自学python第十六天python的迭代对象

    孤荷凌寒自学python第十六天python的迭代对象 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 迭代也就是循环. python中的迭代对象有相关的如下几个术语: A容器 contrai ...