程序员修炼之路-(3)排序(上):基本排序

1 基本排序

对于所有排序算法,被排序元素需要满足下列数学性质:

?  自反性(reflextive):for all v,v=v

?  对称性(antisymmetric):for all v and w,if v<w then w>v and if v=w then w=v

?  传递性(transitive):for all v,w and x,if v<=w and w<=x then v<=x

对于包含这样元素的数组,我们才能对其排序。

1.1 选择排序(selection sort)

思路与实现

选择排序的思路很简单:数组的前半部分是排好序的,那么之后不断从后半部分选择出最小元素,交换到后半部分的第一个位置上。

实现思路很清晰,要注意的是要用数组下标记录min的位置,然后才能swap,避免低级错误…

选择排序的特点

1)输入不敏感:在任何输入上的性能总是n平方,不存在最好、平均、最坏情况之分。

2)数据移动最小化key的swap次数仅为n-1。这也是在众多排序算法中,选择排序的与众不同之处

1.2 冒泡排序(bubble sort)

思路与实现

冒泡排序不断交换两个元素的位置,将最大元素“冒泡”到数组末尾。

基本排序的思想很简单,所以实现时一般循环体比较好写,难的是循环条件。例如,冒泡排序的inner loop写成j<i-1,没想好是<还是<=,结果造成最后一个元素永远不会被排序。

冒泡排序与选择排序类似,在任何输入上性能都一样。但是swap次数却与输入相关。

1.3 插入排序(insertion sort)

思路与实现

与选择排序很类似,插入排序也将数组看成前后两部分。但是插入排序是将后半部分第一个元素,插入到它应在的位置上。生活中最典型的例子就是玩牌时排序手中的牌,但编程实现中,我们要挪动数组中的元素才能将后面的牌插入到它应在的位置。

实现时想到了两种做法:

1) 第一种是比较后将A[i]向前swap,然后inner loop下一次迭代继续比较A[i]与前一个元素,后面能看到JDK 6中就是采用这种实现方式,但能够证明这种方式inner loop的body中操作更多,整体上比第二种方式要慢(《算法设计与分析基础》插入排序的一道练习题)。

2) 另一种则较为标准,将A[i]与前面元素逐个比较找到其位置,比较过程中将这些大于A[i]的元素后移。注意循环终止条件对后面代码的推导作用。

插入排序的特点

在已经或几乎排好序的数组上,运行的非常快。因为每一轮比较都会立即或很快结束。怎么定义几乎排好序?这里引入逆序对(inversion)的概念,指与最终顺序相反的两个元素。当逆序对少于数组长度的常数倍时就认为数组是部分排序的(partially sorted)。当逆序对很少时,插入排序要快于任何其他排序算法(选择、冒泡、归并、快速排序等)。同时,插入排序对小数组也是个不错的排序算法,这也是为什么JDK 6的Arrays.sort()中对长度小于7的原始类型的小数组用插入排序,而大数组时用快速排序(JDK 7中已升级为DualPivotQuicksort)。

以下是总结的插入排序的几种典型应用情况:

1) 每个元素都离最终位置不远。

2) 只有少数元素不在最终位置。

3) 小数组拼接到一个已排序的大数组后面。

4) 很小的数组。

时间: 2024-10-27 16:51:46

程序员修炼之路-(3)排序(上):基本排序的相关文章

程序员修炼之路

0.前言:在路上,再上路 在前言<程序员修炼之路-(0)前言:在路上, 再上路>中已经对知识体系进行了梳理,主要分为问题定义.算法与数据结构.系统平台API.编程语言实现.代码设计.测试验证等等.目前集中精力编写算法与数据结构部分,这可能也是最艰难的一部分.其他部分之前零零散散有所涉及,之后再慢慢补充. 1.计算机数学 离散数学与具体数学. 2.算法分析设计 对于这一部分的内容编排,主要以<算法设计与分析基础>.<算法>以及LeetCode算法题中的问题分类为横向,以&

程序员修炼之路-(0)目录

前言:<程序员修炼之路-(0)前言:在路上, 再上路> 在前言中已经对知识体系进行了梳理,主要分为问题定义.算法与数据结构.系统平台API.编程语言实现.代码设计.测试验证等等.目前集中精力编写算法与数据结构部分,这可能也是最艰难的一部分,之后再考虑补充其他部分. 对于这一部分的内容编排,主要以<算法设计与分析基础>.<算法>以及LeetCode算法题中的问题分类为横向,以<算法设计与分析基础>中罗列的经典设计技巧为纵向,贯穿每一小节.因为传统算法书籍的结构

程序员修炼之路-(1)基础(中):性能分析

3 算法分析 "分析"二字的含义很广泛,在算法分析的领域,指的就是算法的效率,包括运行时间效率(时间复杂度)和内存空间使用效率(空间复杂度)两方面.同时,研究表明,通常时间方面比空间能取得更大的进展,因此我们进一步将分析的重点放在时间复杂度上. 3.1 算法分析 - 科学实验方法 科学家用来理解自然世界的方法对分析程序的运行时间来说同样有效: 1)     观察:观察自然世界的一些特性,并用准确的度量表示. 2)     假设:假设一个能与观察保持一致的模型. 3)     预测:用上

Java程序员修炼之路(一)我们为什么选择Java

我们为什么选择Java大多数人选择Java可能只是因为听说Java前景好.Java比较好找工作.Java语言在TIOBE排行榜上一直位于前三等等之类的原因,但是Java具体好在哪里,心里却是没有什么概念的.其实我选择Java也是出于以上的原因,但是现在确实真正地爱上了Java.那么现在我们来分析下Java的好处究竟在哪里.创一个小群,供大家学习交流聊天如果有对学JAVA方面有什么疑惑问题的,或者有什么想说的想聊的大家可以一起交流学习一起进步也希望大家对学JAVA能够持之以恒JAVA爱好群,如果你

程序员修炼之路-(1)基础(下):正确性证明

来自<Writing Solid Code>的一则小故事,Donald Knuth在其著名的排版软件TEX的封面上写到:"I believe that the final bug in TEX was discovered and removed on November 27, 1985. But if, somehow, an error still lurks in the code, I shall gladly pay a finder 's fee of $20.48 to

程序员修炼之路-(2)线性表(上):数组与链表

1 两块基石 数组与链表构成各种数据结构的基石,是实现所有数据结构必不可少的元素. 1.1 数组 数组一般内置于编程语言中,直接通过索引(index)读写.索引一般为数字,有的语言甚至直接支持如字符串等其他类型的索引.在很多数据结构中都能看到数组的身影,例如字符串.动态数组.堆.栈和队列(用链表也可以,但用数组实现很高效)等. 1.2 链表 概念上都能理解,但实现起来还真有很多容易出错的地方. 实现细节 ?  表头(header):为什么要添加一个表头?因为有了表头,在第一个结点前添加结点或删除

程序员修炼之路-(2)线性表(下):栈、队列

2 基础数据结构 数组和链表是实现各种数据结构的基石,本节中的三种最基本的数据结构都可以用数组或者链表来实现. 2.1 栈 用数组实现"栈"非常简单.下面以C++为例,实现一个简单的固定大小的"栈". 首先,接口API定义如下,核心函数就是push()和pop(): 2.1.1 数组实现 下面用数组方式的实现"栈",当然也可以用链表实现数组,但是一般尽管栈会处理很多操作,但任意时刻保存在栈中的元素不会很多,所以使用数组实现比链表会更加高效: Lo

程序员修炼之路-(4)搜索(中):二叉查找树

2 查找:鱼与熊掌 前面已经介绍了线程查找和二分查找的符号表(symbol table)实现,但现代应用的特点是查找与插入或删除操作交叉在一起,无法预测,并且表非常巨大.因此问题的关键就是我们能否设计出具有对数性能的search和insert/delete操作的数据结构和算法?为了实现高效的插入,我们需要链表结构.但是单链表却又阻止了二分查找的使用,因为二分查找依赖数组的快速访问才能快速定位到每次迭代的中间元素."为了结合二分查找的高效和链表结构的灵活性,我们需要更加复杂的数据结构,这就是接下来

程序员修炼指南——引导你成为真正的编程高手

@程序员修炼指南——引导你成为真正的编程高手 评,这是一个指导方向,不一定是你自己的目标,但如果这些你都做到了,那还有哪些是不能做到的? 前言 你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把以下这些课外练习走一遍(朋友的抱怨:学校课程总是从理论出发,作业项目都看不出有什么实际作用,不如从工作中的需求出发) 建议: 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累而且还会在未来至少10年通用. 回顾一下历史,看看历史上时间线上技术的发展,你才能