大家好,我是雨乐。

今天在搜论文的时候,偶然发现一篇文章,名为<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. 项目部署(ubuntu+uwsgi+nginx+supervisor+django)

    一.在开发机上的准备工作 1. 确认项目没有bug. 2.设置`ALLOW_HOST`为你的域名,以及ip地址. 4.设置`DEBUG=False`,避免如果你的网站产生错误,而将错误信息暴漏给用户. ...

  2. Hive On Spark保姆级攻略

    声明: 此博客参考了官网的配置方式,并结合笔者在实践网上部分帖子时的踩坑经历整理而成 这里贴上官方配置说明: [官方]: https://cwiki.apache.org//confluence/di ...

  3. P6800-[模板]Chirp Z-Transform【NTT】

    正题 题目链接:https://www.luogu.com.cn/problem/P6800 题目大意 给出一个\(n\)此多项式\(P\),对于\(k\in[0,m-1]\)所有的求\(P(c^k) ...

  4. AT4518-[AGC032C]Three Circuits【欧拉回路】

    正题 题目链接:https://www.luogu.com.cn/problem/AT4518 题目大意 给出\(n\)个点\(m\)条边的一张简单无向联通图,求能否把它分成三个可重复点的环. \(1 ...

  5. Excel 高亮当前行、高亮重复行的探索

    本文原创,转载请注明出处:https://www.cnblogs.com/wotent/p/15348891.html TLDR 下载文件 高亮.zip ,将解压后的"高亮.xlam&quo ...

  6. 面试官问我MySQL调优,我真的是

    面试官:要不你来讲讲你们对MySQL是怎么调优的? 候选者:哇,这命题很大阿...我认为,对于开发者而言,对MySQL的调优重点一般是在「开发规范」.「数据库索引」又或者说解决线上慢查询上. 候选者: ...

  7. csp-j 复赛感想

    作者:博客园小蔡编程 这次是作者第一次参加csp-j的比赛 内心还是挺激动的 今天,作者就来和大家讨论一下这次csp-j的学习心得和感想 T1 分糖果 这题描述看似复杂 其实就是一道求最大取模的题 L ...

  8. Java编程开发学习路线图(附所有免费课程+在线自测)

    转自  https://yq.aliyun.com/articles/134286?spm=5176.100239.0.0.1UfveS 摘要: 长期以来,Java一直占据TIOBE编程语言排行版第一 ...

  9. 了解 js 堆内存 、栈内存 。

    js中的堆内存与栈内存 在js引擎中对变量的存储主要有两种位置,堆内存和栈内存. 和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean.Number.String.U ...

  10. 学习手册 | MySQL篇 · 其一

    InnoDB关键特性 插入缓冲(Insert Buffer) 问题:   在InnoDB插入的时候,由于记录通常都是按照插入顺序,也就是主键的顺序进行插入的,因此,插入聚集索引是顺序的,不需要随机IO ...