【算法】6 比较排序之外学习新的线性时间排序

回顾比较排序

相信阅读过前面5篇博文的童鞋们已经发现了“在排序的最终结果中,各元素的次序依赖于它们之间的比较”。于是乎,这类排序算法被统称为”比较排序“。

比较排序是通过一个单一且抽象的比较运算(比如“小于等于”)读取列表元素,而这个比较运算则决定了每两个元素中哪一个应该先出现在最终的排序列表中。

声明:下面通过在维基百科中找到的非常完美的图示来介绍一系列比较排序。

插入排序

在该系列的【算法】1中我们便介绍了这个基本的算法,它的比较过程如下:

以下是用插入排序对30个元素的数组进行排序的动画:

选择排序

选择排序的比较过程如下:

其动画效果如下:

归并排序

前面多次写到归并排序,它的比较过程如下:

归并排序的动画如下:

堆排序

在该系列的【算法】4中我们便介绍了快排,构建堆的过程如下:

堆排序的动画如下:

快速排序

在该系列的【算法】5中我们便介绍了快排,它的比较过程如下:

快速排序的动画如下:

另外一些比较排序

以下这些排序同样也是比较排序,但该系列中之前并未提到。

Intro sort

该算法是一种混合排序算法,开始于快速排序,当递归深度超过基于正在排序的元素数目的水平时便切换到堆排序。它包含了这两种算法优良的部分,它实际的性能相当于在典型数据集上的快速排序和在最坏情况下的堆排序。由于它使用了两种比较排序,因而它也是一种比较排序。

冒泡排序

大家应该多少都听过冒泡排序(也被称为下沉排序),它是一个非常基本的排序算法。反复地比较相邻的两个元素并适当的互换它们,如果列表中已经没有元素需要互换则表示该列表已经排好序了。(看到列表就想到半年前在学的Scheme,欢迎大家也去看看,我开了2个专栏来介绍它们)

上面的描述中已经体现了比较的过程,因而冒泡排序也是一个比较排序,较小的元素被称为“泡(Bubble)”,它将“浮”到列表的顶端。

尽管这个算法非常简单,但大家应该也听说了,它真的非常的慢。

冒泡排序的过程如下:

冒泡排序的动画演示:

其最好情况、最坏情况的运行时间分别是:Θ(n)、Θ(n2)。

奇偶排序

奇偶排序和冒泡排序有很多类似的特点,它通过比较在列表中所有的单双号索引的相邻元素,如果有一对是错误排序(也就是前者比后者大),那么将它们交换,之后不断的重复这一步骤,直到整个列表排好序。

而鉴于此,它的最好情况、最坏情况的运行时间均和冒泡排序相同:Θ(n)、Θ(n2)。

奇偶排序的演示如下:

下面是C++中奇偶排序的示例:

template <class T>
void OddEvenSort (T a[], int n)
{
    for (int i = 0 ; i < n ; i++)
    {
         if (i & 1) // 'i' is odd
         {
             for (int j = 2 ; j < n ; j += 2)
             {
                  if (a[j] < a[j-1])
                      swap (a[j-1], a[j]) ;
             }
          }
          else
          {
              for (int j = 1 ; j < n ; j += 2)
              {
                   if (a[j] < a[j-1])
                       swap (a[j-1], a[j]) ;
              }
          }
    }
}

双向冒泡排序

双向冒泡排序也被称为鸡尾酒排序、鸡尾酒调酒器排序、摇床排序、涟漪排序、洗牌排序、班车排序等。(再多再华丽丽的名字也难以弥补它的低效)

和冒泡排序的区别在于它是在两个方向上遍历列表进行排序,虽然如此但并不能提高渐近性能,和插入排序比起来也没太多优势。

它的最好情况、最坏情况的运行时间均和冒泡排序相同:Θ(n)、Θ(n2)。


排序算法的下界

我们可以将排序操作进行得多块?

这取决于计算模型,模型简单来说就是那些你被允许的操作。

决策树

决策树(decision tree)是一棵完全二叉树,它可以表示在给定输入规模情况下,其一特定排序算法对所有元素的比较操作。其中的控制、数据移动等其他操作都被忽略了。

这是一棵作用于3个元素时的插入排序的决策树。标记为i:j的内部结点表示ai和aj之间的比较。

由于它作用于3个元素,因此共有A33=6种可能的排列。也正因此,它并不具有一般性。

而对序列<a1=7,a2=2,a3=5>和序列<a1=5,a2=9,a3=6>进行排序时所做的决策已经由灰色和黑色粗箭头指出了。

决策树排序的下界

如果决策树是针对n个元素排序,那么它的高度至少是nlgn。

在最坏情况下,任何比较排序算法都需要做Ω(nlgn)次比较。

因为输入数据的Ann种可能的排列都是叶结点,所以Ann≤l,由于在一棵高位h的二叉树中,叶结点的数目不多于2h,所以有:

n!≤l≤2h

对两边取对数:

=> lg2h≥lgn!

=> lg2h=hlg2≥lgn!

又因为:

lg2<1

所以:

n≥lgn!=Ω(nlgn)

因为堆排序和归并排序的运行时间上界均为O(nlgn),因此它们都是渐近最优的比较排序算法。

线性时间排序

计数排序

计数排序(counting sort)的思路很简单,就是确定比x小的数有多少个。加入有10个,那么x就排在第11位。

严谨来讲,在计算机科学中,计数排序是一个根据比较键值大小的排序算法,它也是一个整数排序算法。它通过比较对象的数值来操作,并通过这些计数来确定它们在即将输出的序列中的位置。它的运行时间是线性的且取决于最大值和最小值之间的差异,当值的变化明显大于数目时就不太适用了。而它也可以作为基排序的子程序。

COUNTING-SORT(A,B,k)
1   let C[0...k] be a new array
2   for i=0 to k
3       C[i]=o
4   for j=1 to A.length
5       C[A[j]]=C[A[j]]+1
6   // C[i] now contains the number of element equal to i.
7   for i=1 to k
8       C[i]=C[i]+C[i-1]
9   // C[i] now contains the number of element less than or equal to i.
10  for j=A.length downto 1
11      B[C[A[j]]]=A[j]
12      C[A[j]]=C[A[j]]-1

第2-3步,C数组的元素被全部初始化为0,此时耗费Θ(k)时间。

第4-5步,也许不太好想象,其实就是在C数组中来计数A数组中的数。比如说,A数组中元素”3”有4个,那么C[3]=4。此时耗费Θ(n)时间。

第7-8步,也是不太好想象的计算,也就是说如果C[0]=1、C[1]=4,那么计算后的C[0]不变,C[1]=5。此时耗费Θ(k)时间。

第10-12步,把每个元素A[j]放到它在输出数组B中的合适位置。比如此时的第一次循环,先找到A[8],然后找到C[A[8]]的值,此时C[A[8]]的意义就在于A[8]应在B数组中的位置。完成这一步后将C[A[8]]的值减一,因为它只是一个计数器。这里耗费的时间为Θ(n)。

当k=O(n)时,计数排序的运行时间为Θ(n)。

基数排序

基数排序(radix sort)是一个古老的算法,它用于卡片排序机上。说来也巧,写这篇博客的前一天晚上还在书上看到这种机器,它有80列,每一列都有12个孔可以打。

它可以使用前面介绍的计数排序作为子程序,然而它并不是原址排序;相比之下,很多运行时间为Θ(nlgn)的比较排序却是原址排序。因此当数据过大而内存不太够时,使用它并不是一个明智的选择。

关键在于依次对从右往左每一列数进行排序,其他的列也相应移动。

桶排序

这倒是一个有趣的算法了,它充分利用了链表的思想。

桶排序(bucket sort)在平均情况下的运行时间为O(n)。

计数排序假设n个输入元素中的每一个都在0和k之间,桶排序假设输入数据是均匀分布的,所以他们的速度都非常快。但并不能因为这些是假设就说它们不实用不准确,真正的意义在于你可以根据情况选择合适的算法。比如说,输入的n个元素并不是均匀分布的,但它们都在0到k之间,那么就可以用计数排序。

说到桶,我想到的是装满葡萄酒的酒桶以及装满火药的火药桶。这里是桶是指的算法将[0,1)区域划分为了n个相同大小的空间,它们被称为桶。

既然有了这个划分,那么就要用到它们。假设输入的是n个元素的数组A,且对于所有的i都有0≤A[i]<1。你也许会觉得怎么可能输入的数组元素都凑巧满足呢,当然不会这么凑巧,但是你可以人为地改造它们呀。比如<10,37,31,87>,你可以将它们都除以100,得到<0.10,0.37,0.31,0.87>。

还需要一个临时的数组B[0…n-1]来保存这些桶(也就是链表),而链表支持搜索,删除和插入。关于链表的部分后面的博客中会有详细介绍。

BUCKET-SORT(A)
1   n=A.length
2   let B[0...n-1] be a new array
3   for i=0 to n-1
4       make B[i] an empty list
5   for i=1 to n
6       insert A[i] into list B[小于等于nA[i]的最大整数]
7   for i=0 to n-1
8       sort list B[i] with insertion sort
9   concatenate the lists B[0],B[1],...B[n-1] together in order

学习算法一定要体会到这种算法内每一步的改变,也要体会不同算法之间的演化和进步。在后面的链表中,我会更加侧重于思路以及算法的进化。




感谢您的访问,希望对您有所帮助。 欢迎大家关注、收藏以及评论。



为使本文得到斧正和提问,转载请注明出处:
http://blog.csdn.net/nomasp


时间: 2024-09-06 21:39:05

【算法】6 比较排序之外学习新的线性时间排序的相关文章

【万字总结】快速排序详解与各种线性时间排序对比

什么是快速排序 快速排序简介 快速排序(英文名:Quicksort,有时候也叫做划分交换排序)是一个高效的排序算法,由Tony Hoare在1959年发明(1961年公布).当情况良好时,它可以比主要竞争对手的归并排序和堆排序快上大约两三倍.这是一个分治算法,而且它就在原地排序. 所谓原地排序,就是指在原来的数据区域内进行重排,就像插入排序一般.而归并排序就不一样,它需要额外的空间来进行归并排序操作.为了在线性时间与空间内归并,它不能在线性时间内实现就地排序,原地排序对它来说并不足够.而快速排序

【算法导论】排序 (四):决策树、线性时间排序(计数、基数、桶排序)

到目前为止,一共整理总结了五大排序算法: 1.插入排序 2.冒泡排序.选择排序.交换排序 (把这三种方法归为一种,因为他们的思想本质上都是一样的) 3.归并排序 4.堆排序 5.快速排序 以上五种排序都可以称为"比较排序",顾名思义,因为他们都是基于比较元素 来决定其相对位置的. 其中前两种的时间为O(n^2),归并排序和堆排序最坏O( n lg n ),快排平 均O( n lg n ) 定理:任意一种比较排序算法最坏情况下,都需要做 Ω( n lg n )次的比较. 我们通过决策树来

第八章 线性时间排序

摘要: 本章先回顾了前面介绍的合并排序.堆排序和快速排序的特点及运行运行时间.合并排序和堆排序在最坏情况下达到O(nlgn),而快速排序最坏情况下达到O(n^2),平均情况下达到O(nlgn),因此合并排序和堆排序是渐进最优的.这些排序在执行过程中各元素的次序基于输入元素间的比较,称这种算法为比较排序.接下来介绍了用决策树的概念及如何用决策树确定排序算法时间的下界,最后讨论三种线性时间运行的算法:计数排序.基数排序和桶排序.这些算法在执行过程中不需要比较元素来确定排序的顺序,这些算法都是稳定的.

C++线性时间的排序算法分析_C 语言

前面的文章已经介绍了几种排序算法,如插入排序(直接插入排序,折半插入排序,希尔排序).交换排序(冒泡排序,快速排序).选择排序(简单选择排序,堆排序).2-路归并排序(可以参考前一篇文章:各种内部排序算法的实现)等,这些排序算法都有一个共同的特点,就是基于比较. 本文将介绍三种非比较的排序算法:计数排序,基数排序,桶排序.它们将突破比较排序的Ω(nlgn)下界,以线性时间运行. 一.比较排序算法的时间下界 所谓的比较排序是指通过比较来决定元素间的相对次序. "定理:对于含n个元素的一个输入序列,

BAIR论文:通过“元学习”和“一次性学习”算法,让机器人快速掌握新技能

我们都知道,深度学习是在大数据的背景下火起来的,传统的基于梯度的深度神经网络需要大量的数据学习,而绝大多数的深度学习内容否基于大数据量下的广泛迭代训练,当遇到新信息时往往会出现模型失效的情况从而需要重新进行学习.在机器人领域,深度神经网络可以是机器人展示出复杂的技能,但在实际应用中,一旦环境发生变化,从头学习技能并不可行.因此,如何让机器"一次性学习",即在"看"了一次演示后无需事先了解新的环境场景,能在不同环境中重复工作尤为重要. 研究发现,具有增强记忆能力的架构

JavaScript学习笔记之数组随机排序_javascript技巧

推荐阅读:JavaScript学习笔记之数组求和方法 JavaScript学习笔记之数组的增.删.改.查 JavaScript中提供了sort()和reverse()方法对数组项重新排序.但很多时候这两个方法无法满足我们实际业务的需求,比如说扑克牌游戏中的随机洗牌. 在这篇文章一起来学习如何完成上面这个示例的效果,以及一些有关于数组随机排序的相关知识. 在网上查了一下有关于数组随机排序的相关资料,都看到了Math.random()的身影.打开浏览器控制器,输入: Math.random() 从图

常见的五类排序算法图解和实现(选择类:简单选择排序,锦标赛排序,树形选择排序,堆排序)

选择类的排序算法 简单选择排序算法 采用最简单的选择方式,从头到尾扫描待排序列,找一个最小的记录(递增排序),和第一个记录交换位置,再从剩下的记录中继续反复这个过程,直到全部有序. 具体过程: 首先通过 n –1 次关键字比较,从 n 个记录中找出关键字最小的记录,将它与第一个记录交换. 再通过 n –2 次比较,从剩余的 n –1 个记录中找出关键字次小的记录,将它与第二个记录交换. 重复上述操作,共进行 n –1 趟排序后,排序结束. 如图   过程图解 令 k=i:j = i + 1: 目

【NIPS2017现场+数据也疯狂】最佳论文大奖公布,算法关注度超越深度学习排第一

本次直播由CMU计算机学院副教授马坚.斯坦福AI博士生.李飞飞教授的学生Jim Fan.杜克大学温伟为大家带来NIPS 2017全程直播.特别感谢Jim 提供照片. 参会人数从100激增到8500,不变的是创造智能的梦 三十一年,一代科学巨匠们的辉煌历史!主席说,他当年在Denver参加第一届NIPS的时候只有一百多人,而今年有超过8500人注册.人数在逐年指数增长,而唯一不变的是那古老的创造智能的梦. 这次大会共有2个并行的track,都分别包括oral和spotlight.新增了艺术创作大赛

推荐系统主要算法总结及Youtube深度学习推荐算法实例概括

协同过滤 协同过滤(CF)及其变式是最常用的推荐算法之一.即使是数据科学的初学者,也能凭之建立起自己的个性化电影推荐系统,例如,一个简历项目. 当我们想要向某个用户推荐某物时,最合乎情理的事情就是找到与他/她具有相同爱好的用户,分析其行为,并且为之推荐相同的东西.或者我们可以关注那些与该用户之前购买物品相似的东西,并推荐相似的产品. 协同过滤(CF)有两种基本方法,它们分别是:基于用户的协同过滤技术和基于项目的协同过滤技术. 该推荐算法的以上情形中均包含两步: 1. 找到数据库中有多少用户/项目