这一篇博文我们将讨论一些与排序算法有关的定理,这些定理将解释插入排序博文中提出的疑问(为什么冒泡排序与插入排序总是执行同样数量的交换操作,而选择排序不一定),同时为讲述高级排序算法做铺垫(高级排序为什么会更快)。

  在讨论相关定理之前,我们必须先掌握一个与顺序有关的概念:逆序数。

  所谓逆序数,就是“逆序组合的个数”,假设我们希望的顺序为从小到大(反之同理):

  设有元素互异数列X0,X1,X2……Xn-1,(元素互异即数列中任取两数均不相等)从中任取两数作为组合(Xa,Xb),若a<b(即Xa在数列中位于Xb之前)且Xa>Xb,则(Xa,Xb)逆序,若a<b且Xa<Xb,则(Xa,Xb)不是逆序。整个数列的逆序数即数列中所有逆序组合(Xa,Xb)的个数。(不难发现,若数列有n个数,则任取两数的组合(Xa,Xb)共有n(n-1)/2个,n为元素个数。)

  

  下面我们来举几个计算逆序数的例子(依然假设我们希望的顺序为从小到大):

  1.设有数列1,2,3,4,5,因为从中任取两数(Xa,Xb)且a<b,均有Xa<Xb,即任一组合(Xa,Xb)均非逆序,所以数列1,2,3,4,5的逆序数为:逆序组合的个数=0

  2.设有数列5,4,3,2,1,因为从中任取两数(Xa,Xb)且a<b,均有Xa>Xb,即任一组合(Xa,Xb)均为逆序,所以数列1,2,3,4,5的逆序数为:逆序组合的个数=n(n-1)/2=5*4/2=10

  2.设有数列2,3,4,5,1,从中任取两数(Xa,Xb),a<b且Xa>Xb的组合有(2,1),(3,1),(4,1),(5,1)共4个,即逆序组合共4个,所以数列2,3,4,5,1的逆序数为:逆序组合的个数=4

  从例1我们可以推出定理1:若数列为有序,则其逆序数为0。例1的计算过程即本定理的证明。

  而从定理1我们又可以得出定理2:若数列非有序,则其逆序数必大于0。证明过程类似于定理1,略

  从定理1和定理2我们又可以得出定理3:将数列从非有序转换为有序(即排序)的过程,就是减少数列逆序数的过程。

  明白了什么是逆序数,以及排序算法的“本质”之后,我们开始讨论定理4:

  设有元素互异数列X0,X1,X2……Xn-1,其反数列为Xn-1,Xn-2,……X0,则这两个数列的逆序数之和必为n*(n-1)/2

  因为数列元素互异,所以任取两数Xa,Xb且a<b,要么Xa>Xb,要么Xa<Xb。也就是说组合(Xa,Xb)若在原数列中非逆序,则必在反数列中逆序,反之则反。而元素个数为n的数列中组合(Xa,Xb)的个数共为n*(n-1)/2个,任一组合要么在原数列逆序,要么在反数列逆序,所以原数列与反数列的逆序数之和即组合(Xa,Xb)的个数:n*(n-1)/2

  有了定理4,我们就可以给出定理5:

  有n个互异元素的数列,其平均逆序数为n*(n-1)/4

  从定理4可知,给定的数列与其反数列的逆序数之和为n*(n-1)/2,所以该数列的平均逆序数即两者之和的一半n*(n-1)/2。

  接下来是简单的定理6:

  对于元素互异数列,交换其中相邻的两个数Xn,Xn+1的位置,则数列的逆序数要么+1,要么-1。

  假设(Xn,Xn+1)为逆序,则交换两者将使该组合变为非逆序,但不会影响其他组合如(Xa,Xn)(Xn,Xb)的顺序,从而数列逆序数-1

  假设(Xn,Xn+1)为非逆序,则交换两者将使该组合变为逆序,但不会影响其他组合如(Xa,Xn)(Xn,Xb)的顺序,从而数列逆序数+1

  根据

  定理3:将数列从非有序转换为有序(即排序)的过程,就是减少数列逆序数直至0的过程。

  定理5:有n个互异元素的数列,其平均逆序数为n*(n-1)/4

  定理6:对于元素互异数列,交换其中相邻的两个数Xn,Xn+1的位置,则数列的逆序数要么+1,要么-1。

  我们可以得出定理7:

  通过交换相邻元素来完成排序的算法,其平均时间复杂度为Ω(n2

  对于n元素互异的数列,其平均逆序数为n*(n-1)/4,而排序即减少逆序数至0的过程,因为交换相邻元素最多使逆序数-1,所以必须有n*(n-1)/4次交换才能使数列逆序数为0,即交换相邻元素的排序算法平均需要执行n*(n-1)/4次交换操作,也就是说平均时间复杂度为Ω(n*(n-1)/4),即Ω(n2)。虽然我们一直强调元素互异看起来有所不妥,但排序算法必然要可以处理元素互异情况,而且Ω(n)也只是平均时间复杂度,没有考虑任何具体情形。

   

  至此,本篇博文对于排序算法的定理就算是介绍完毕了。但有关排序算法的分析尚未结束。

  我们先来回答插入排序博文中的问题:为什么冒泡排序和插入排序执行的交换操作总是一样多。其原因如下:

  这两个算法都是通过交换相邻元素来排序的,而且它们都不会执行使数列逆序数+1的交换,也就是说它们每执行一次交换就会且只会使数列的逆序数-1。所以对于给定数列,若其逆序数为n,则两者都将执行n次交换操作。

  那么,选择排序有时候只需要更少的交换次数的原因也就出来了,因为选择排序并不是通过交换相邻元素来排序的算法,比如数列5,4,3,2,1,选择排序第一次“实质交换”后,数列变为了1,4,3,2,5,也就是说它这一次交换就使得数列的逆序数-7。所以选择排序可以使用更少的“实质交换”来完成排序,当然这并不意味着选择排序就会很快,原因不再赘述。

  接下来,根据前面的定理(3、5、6)。我们可以推出本文最后一个定理,也就是定理7的推理,也是所有高级排序算法的根本:

  要令排序算法的时间复杂度低于O(n2),必须令算法执行“远距离的元素交换”,使得平均每次交换减少不止1逆序数。

  

  下一篇博文我们将讨论希尔排序,它是插入排序的升级版,升级之处显然就是“令元素远距离的交换”,而且升级的思想非常直白,具体的实现我们将在下一篇博文介绍。

深入浅出数据结构C语言版(17)——有关排序算法的分析的更多相关文章

  1. 深入浅出数据结构C语言版(17)——希尔排序

    在上一篇博文中我们提到:要令排序算法的时间复杂度低于O(n2),必须令算法执行"远距离的元素交换",使得平均每次交换减少不止1逆序数. 而希尔排序就是"简单地" ...

  2. 深入浅出数据结构C语言版(22)——排序决策树与桶式排序

    在(17)中我们对排序算法进行了简单的分析,并得出了两个结论: 1.只进行相邻元素交换的排序算法时间复杂度为O(N2) 2.要想时间复杂度低于O(N2),算法必须进行远距离的元素交换 而今天,我们将对 ...

  3. 深入浅出数据结构C语言版(1)——什么是数据结构及算法

    在很多数据结构相关的书籍,尤其是中文书籍中,常常把数据结构与算法"混合"起来讲,导致很多人初学时对于"数据结构"这个词的意思把握不准,从而降低了学习兴趣和学习信 ...

  4. 深入浅出数据结构C语言版(16)——插入排序

    从这一篇博文开始,我们将开始讨论排序算法.所谓排序算法,就是将给定数据根据关键字进行排序,最终实现数据依照关键字从小到大或从大到小的顺序存储.而这篇博文,就是要介绍一种简单的排序算法--插入排序(In ...

  5. 深入浅出数据结构C语言版(20)——快速排序

    正如上一篇博文所说,今天我们来讨论一下所谓的"高级排序"--快速排序.首先声明,快速排序是一个典型而又"简单"的分治的递归算法. 递归的威力我们在介绍插入排序时 ...

  6. 深入浅出数据结构C语言版(21)——合并排序

    在讲解合并排序之前,我们先来想一想这样一个问题如何解决: 有两个数组A和B,它们都已各自按照从小到大的顺序排好了数据,现在我们要把它们合并为一个数组C,且要求C也是按从小到大的顺序排好,请问该怎么做? ...

  7. 深入浅出数据结构C语言版(5)——链表的操作

    上一次我们从什么是表一直讲到了链表该怎么实现的想法上:http://www.cnblogs.com/mm93/p/6574912.html 而这一次我们就要实现所说的承诺,即实现链表应有的操作(至于游 ...

  8. 深入浅出数据结构C语言版(8)——后缀表达式、栈与四则运算计算器

    在深入浅出数据结构(7)的末尾,我们提到了栈可以用于实现计算器,并且我们给出了存储表达式的数据结构(结构体及该结构体组成的数组),如下: //SIZE用于多个场合,如栈的大小.表达式数组的大小 #de ...

  9. 深入浅出数据结构C语言版(12)——从二分查找到二叉树

    在很多有关数据结构和算法的书籍或文章中,作者往往是介绍完了什么是树后就直入主题的谈什么是二叉树balabala的.但我今天决定不按这个套路来.我个人觉得,一个东西或者说一种技术存在总该有一定的道理,不 ...

随机推荐

  1. cloudstack下libvirtd服务无响应问题

    在cloudstack4.5.2版本下,偶尔出现libvirtd服务无响应的情况,导致virsh命令无法使用,同时伴随cloudstack master丢失该slave主机连接的情况.最初怀疑是lib ...

  2. CentOS IP DNS设置

    1.CentOS 修改DNS 修改对应网卡的DNS的配置文件 # vi /etc/resolv.conf 修改以下内容 nameserver 8.8.8.8 #google域名服务器 nameserv ...

  3. node-xlsx

    1.安装 必要组件 npm install node-xlsx -S /*Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator.Generator ...

  4. Python 文件的处理

    简单的读取文件 f.read()  是读取这个文件的所有内容 f.readline()  是读取文件的一行 .write()  会去检查这个文件是否存在,不存在则创建,存在的话,则以覆盖的方式将内容写 ...

  5. ASP.NET程序运行出现WebDev.WebServer40.exe已停止工作解决方法(netsh winsock reset)

    问题描述:在开发系统时候运行程序突然报出"WebDev.WebServer40.exe已停止工作"的错误,程序调试运行,发现程序在打开数据库时候报错,也就是Connection.O ...

  6. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  7. SolrCloud(一)搭建Zookeeper

    搭建Zookeeper 三台服务器: AMouse: 192.168.3.201 BCattle : 192.168.3.202 Ctiger   : 192.168.3.203 一 下载Zookee ...

  8. MySQL的grant,revoke使用

    MySQL 赋予用户权限命令的简单格式可概括为: grant 权限 on 数据库对象 to 用户 一.grant 普通数据用户,查询.插入.更新.删除 数据库中所有表数据的权利. grant sele ...

  9. webdriver

    导入模块: from selenium import webdriver from selenium.common.exceptions import NoSuchElementException 选 ...

  10. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...