大家好,我是雨乐。

今天在搜论文的时候,偶然发现一篇文章,名为<Is this the simplest (and most surprising) sorting algorithm ever?>,看了里面的内容,蛮有意思,所以今天借助此文,分享给大家。

算法

下面我看下伪代码实现,在证明该排序算法正确性之前,我们暂且将其命名为ICan’tBelieveItCanSort。

ICan’tBelieveItCanSort(A[1..n]) {
  for i = 1 to n do
    for j = 1 to n do
      if A[i] < A[j] then
        swap(A[i], A[j])
}

看了上面代码的第一反应是什么?会不会跟我一样,觉得

这不就是一个错误的冒泡排序算法么,只是把第二行把范围写错,第三行交换的条件写反了罢了。

下面是冒泡排序的伪代码:

BubbleSort(A[1..n]) {
  for i = 1 to n do
    for j = i + 1 to n do
      if (A[i] > A[j]) then
        swap(A[i], A[j]);
}

为了后续描述方便,我将该算法统一称之为"新算法"。

从上面两个伪代码的实现来看,新算法ICan’tBelieveItCanSort和传统的冒泡排序算法BubbleSort的区别如下:

  • 在新算法中,内循环为 for j = 1 to n do

    而在传统的冒泡算法中,内循环为 for j = i + 1 to n do

  • 在新算法中,交换的条件为 if A[i] < A[j] then

    而在传统的冒泡排序算法中,交换条件为 if A[i] > A[j] then

好了,我们言归正传,重新转回到新算法,为了方便大家阅读,再此重新贴一次新算法的伪代码:

ICan’tBelieveItCanSort(A[1..n]) {
  for i = 1 to n do
    for j = 1 to n do
      if A[i] < A[j] then
        swap(A[i], A[j])
}

先不论算法的正确与否,因为在A[i] < A[j]时候才进行交换,所以上述代码给我们的第一印象就是 按照降序排列。但实际上,通过代码运行结果来分析,其确实是升序排列。

下面给出证明过程。

证明

下面将通过数学归纳法来证明此算法的正确性。

假设Pᵢ是经过 i 次(1 ≤ i ≤ n)外循环后得到的数组,那么前i项已经是升序排列,即 A[1] ≤ A[2] ≤ . . . ≤ A[i]。

要证明该算法正确,只需要证明P对于任何[i + 1..n]都成立。

根据数学归纳法,我们只要证明 P₁成立,假设 Pᵢ成立,接着再证明 Pi+1 也成立,命题即可得证。

P₁显然是正确的,而且这一步和普通的冒泡算法降序没有区别,经过第1次外循环,A[1]就是整个数组的最大元素。

接着我们假设Pᵢ成立,然后证明 Pi+1 成立。

下面我们开始证明新算法的正确性。

首先假设存在一个下标K:

首先假设 A [k](k 介于 1~i 之间)满足 A[k] > A[i+1] 最小的一个数,那么 A [k−1]≤A [i+1](k≠1)。

如果 A [i+1]≥A [i],那么这样的 k 不存在,我们就令 k=i+1。

现在,我们考虑以下几种情况:

  • 1 ≤ j ≤ k−1 此时,由于A[1..i]是递增有序,且A[K]是满足A[k] > A[i+1] 最小的一个数,所以A[j] < A[i +1],没有任何元素交换发生。

  • k ≤ j ≤ i (显然,当k = i+1的时候,不会进入此步骤)

    由于 A[j] > A[i+1],所以每次比较后都会有元素交换发生。

    我们使用 A[] 和 A′[] 来表示交换前和交换后的元素,所以A[i+1] = A[k],A′[k]=A[i+1]。

    经过一系列交换,最大元素最终被放到了 A[i+1] 位置上,原来的A[i+1]变成了最大元素,A[k]被插入了大小介于原来A[k]和A[k-1]之间的元素。

  • i+1 ≤ j ≤ n

    由于最大元素已经交换到前 i+1 个元素中,此过程也没有任何元素交换。

经过上面一系列条件,最终,P就是升序排序算法执行完以后的结果。

由于内外循环完全一样,所以此算法可以说是最简单的排序算法了。

优化

从上面的证明过程中,我们可以发现,除了 i=1 的循环以外,其余循环里 j=i-1 之后的部分完全无效,因此可以将这部分省略,得到简化后的算法。

ICan’tBelieveItCanSort(A[1..n]) {
  for i = 2 to n do
    for j = 1 to i - 1 do
      if A[i] < A[j] then
        swap(A[i], A[j])
}

对比

但从代码来看,新算法像是冒泡算法的变种,但是从上面证明过程来看,新算法实际上是一种插入算法。

下面为新算法的模拟图:

新算法

下面为冒泡算法的模拟图:

冒泡算法

实现

代码实现比较简单,如下:

#include 
#include 
#include 

void SimplestSort(std::vector &v) {
  for (int i = 0; i < v.size(); ++i) {
    for (int j = 0; j < v.size(); ++j) {
      if (v[i] < v[j]) {
 std::swap(v[i], v[j]);
      }
    }
  }
}

int main() {
  std::vector v = {9, 8, 1, 3,2, 5, 4, 7, 6};
  SimplestSort(v);

  for (auto item : v) {
    std::cout << item << std::endl;
  }

  return 0;
}

输出结果:

1
2
3
4
5
6
7
8
9

更简单的算法?

看完这篇论文,突然想起之前有个更简单且容易理解的算法,我们暂且称之为休眠算法。

思想:

构造n个线程,它们和这n个数一一对应。初始化后,线程们开始睡眠,等到对应的数那么多个时间单位后各自醒来,然后输出它对应的数。这样最小的数对应的线程最早醒来,这个数最早被输出。等所有线程都醒来,排序就结束了。

例如对于 [4,2,3,5,9] 这样一组数字,就创建 5 个线程,每个线程睡眠 4s,2s,3s,5s,9s。这些线程睡醒之后,就把自己对应的数报出来即可。这样等所有线程都醒来,排序就结束了。

算法思路很简单,但是存在一个问题,创建的线程数依赖于需要排序的数组的元素个数,因此这个算法暂且只能算是一个思路吧。

结语

这个算法不一定是史上最简单的排序算法,但却是最神奇的排序算法。神奇之处在于 大于号和小于号颠倒了却得到了正确的结果。

其实,我们完全可以用另外一个简单的思路来理解这个算法,那就是冒泡两次,第一次非递增排序,第二次非递减排序,算是负负得正,得到了正确的结果吧。

由于"最简单"算法的时间复杂度过高,其仅仅算是一种实现思路,也算是开拓一下思路,实际使用的时候,还是建议使用 十大经典排序算法。

今天的文章就到这里,下期见。

史上最简单的排序算法?看起来却满是bug的更多相关文章

  1. 史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。

    重要的话 开头说,not for the RecyclerView or ListView, for the Any ViewGroup. 本控件不依赖任何父布局,不是针对 RecyclerView. ...

  2. [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)

    [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例) 踏雁寻花 发表于 2015-8-23 23:31:28 https://www.itsk.com/thread-35 ...

  3. (转) 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)

    一.spring cloud简介 spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选.分布式会话等等.它运 ...

  4. 史上最简单的SpringCloud教程 | 第十篇: 高可用的服务注册中心(Finchley版本)

    转载请标明出处: 原文首发于 https://www.fangzhipeng.com/springcloud/2018/08/30/sc-f10-eureka/ 本文出自方志朋的博客 文章 史上最简单 ...

  5. 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本)

    转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springcloud/2018/08/30/sc-f1-eureka/ 本文出自方志朋的博客 一.spring ...

  6. 史上最简单的 SpringCloud 教程

    史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)史上最简单的SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)史上最简单的Spri ...

  7. 史上最简单的 SpringCloud 教程 | 终章

    https://blog.csdn.net/forezp/article/details/70148833转载请标明出处:http://blog.csdn.net/forezp/article/det ...

  8. 史上最简单的 GitHub 教程

    史上最简单的 GitHub 教程 温馨提示:本系列博文已经同步到 GitHub,如有需要的话,欢迎大家到「github-tutorial」进行Star和Fork操作! 1 简介 GitHub 是一个面 ...

  9. 史上最简单的 MySQL 教程(十五)「列属性 之 自动增长」

    自动增长 自动增长:auto_increment,当对应的字段,不给值,或者是默认值,或者是null的时候,就会自动的被系统触发,系统会从当前字段中取已有的最大值再进行+1操作,得到新的字段值. 自增 ...

随机推荐

  1. truncate表时报“唯一/主键被启用的外部关键字引用”解决办法

    前言:清空表时提示"唯一/主键被启用的外部关键字引用"这一警告信息 原因:是因为主键被子表引用,所以对主键进行更改就好了 解决: 使用 alter table table_name ...

  2. 基于预计算的全局光照(Global Illumination Based On Precomputation)

    目录 基于图像的光照(Image Based Lighting,IBL) The Split Sum Approximation 过滤环境贴图 预计算BRDF积分 预计算辐射度传输(Precomput ...

  3. MyBatis实现批量添加

    在进行后端的操作时,批量添加总是少不了,话不多说,下面贴上代码 Mybatis代码: <insert id="batchInsert" parameterType=" ...

  4. caffe运行错误 target_blobs.blobs_size()与 source_layer.blobs_size() 不一致

    解决方法参考:http://blog.csdn.net/zhangla1220/article/details/50697352 感谢博主!!! 最新下载的caffe代码,运行mnist,训练时可以正 ...

  5. The type name or alias SqlServer could not be resolved.Please check your configuration

    The type name or alias SqlServer could not be resolved.Please check your configuration file.... 检查一下 ...

  6. InstallSheild相关

    一.关于使用InstallSheild制作安装包的总结. 1.定制化制作需要了解InstallScript语法,相关资料可以去网上查找,后续提供比较好的资料. 2.有些软件运行是需要一些环境的,譬如使 ...

  7. Vulnstack内网靶场2

    环境配置 内网2靶场由三台机器构成:WIN7.2008 server.2012 server 其中2008做为对外的web机,win7作为个人主机可上网,2012作为域控 网络适配器已经设置好了不用自 ...

  8. Go语言核心36讲(Go语言进阶技术一)--学习笔记

    07 | 数组和切片 我们这次主要讨论 Go 语言的数组(array)类型和切片(slice)类型. 它们的共同点是都属于集合类的类型,并且,它们的值也都可以用来存储某一种类型的值(或者说元素). 不 ...

  9. CentOS 用户与群组

    目录 1.用户管理 1.1.切换用户 1.2.添加用户 1.3.删除用户 1.4.修改用户 2.群组管理 2.1.查看群组 2.2.添加群组 2.3.删除群组 2.4.修改群组 1.用户管理 Linu ...

  10. 【UE4 C++】解析与构建 XML 数据,XmlParser 与 tinyxml

    XmlParser 简单读取 XmlParser 为引擎自带模块 XML 文件 <?xml version="1.0" encoding="UTF-8"? ...