一、什么是排序
排序:将一组杂乱无章的数据按一定规律顺次排列起来。即,将无序序列的数据节点包含多个数据域,那么排序往往是针对其中某个域而言。
二、排序方法的分类
1.按数据存储介质可分为:
内部排序:数据量不大、数据在内存,无需内外存交换数据
外部排序:数据量较大、数据在外存(文件排序)。外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂的多。
2.按比较器个数可分为:
串行排序:单处理机(同一时刻比较一对元素)
并行排序:多处理机(同一时刻比较多对元素)
3.按主要操作可分为:
比较排序:用比较的方法。 插入排序、交换排序、选择排序、归并排序
基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置
4.按辅助空间可分为:
原地排序:辅助空间用量为O(1)的排序方法。(所占的辅助存储空间与参加排序的数据量大小无关)
非原地排序:辅助空间用量超过O(1)的排序方法。
5.按稳定性可分为:
稳定排序:能够使任何数值相等的元素,排序以后相对次序不变
非稳定性排序:不是稳定排序的方法
排序的稳定性只对结构类型数据排序有意义
排序方法是否稳定,并不能衡量一个排序算法的优劣。
6.按自然性可分为:
自然排序:输入数据越有序,排序的速度越快的排序方法
非自然排序:不是自然排序的方法
三、排序类型
插入排序
基本思想:每步将一个待排序的对象,按其关键码大小,插入前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
即边插入边排序,保证子序列中随时都是排好序的。
在插入a[i]前,数组a的前半段(a[0]~a[i-1])是有序段,后半段(a[i]~a[n-1])是停留于输入次序的“无序段”。
插入a[i]使a[0]~a[i-1]有序,也就是要为a[i]找到有序位置j(0<=j<=i),将a[i]插入在a[j]的位置上。
直接插入排序
每次插入数据时都要比较两次,判断下标是否越界,因此为了减少一次比较次数,加入“哨兵”,将索引为0的位置赋值为要加入的数据的值。
直接插入排序------性能分析
实现排序的基本操作有两个:
(1)“比较”序列中两个关键字的大小
(2)“移动”记录
最好的情况(关键字在记录序列中顺序有序):
“比较的次数”: “移动”的次数:0
最坏的情况(关键字在记录系列中逆序有序):
“比较的次数”: “移动”的次数:
平均的情况
“比较”的次数:
“移动”的次数:
时间复杂度:
折半插入排序
折半插入排序------算法分析
折半查找比顺序查找快,所以折半插入排序就平均性能来说比直接插入排序要快;
它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数。在插入第i个对象时,需要经过次关键码比较,才能确定它插入的位置;
当n较大时,总关键码比较次数比直接插入排序的最坏情况要好得多,但比其最好情况要差;
在对象的初始排列已经按关键码排好序或接近有序时,直接插入排序比折半插入排序执行的关键码比较次数要少;
折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列
减少了比较次数,但没有减少移动次数
平均性能优于直接插入排序
时间复杂度为O(n^2)
空间复杂度为O(1)
是一种稳定的排序方法
希尔排序(Shell Sort)
基本思想:先将整个待排记录序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
希尔排序算法,特点:
(1)缩小增量
(2)多遍插入排序
希尔排序特点:
一次移动,移动位置较大,跳跃式地接近排序后的最终位置
最后一次只需要少量移动
增量序列必须是递减的,最后一个必须是1
增量序列应该是互质的
希尔排序算法分析
希尔排序法是一种不稳定的排序算法
交换排序
基本思想:两两比较,如果发生逆序则交换,直到所有记录都排好序为止。
常见的交换排序方法:冒泡排序、快速排序
冒泡排序
优点:每趟结束,不仅能挤出一个最大值到最后面位置,还能同时部分理顺其他元素;
一旦某一趟比较时不出现记录交换,说明已经排好序了,就可以结束本算法。
设置一个标记位flag,如果发生交换,flag=1;若本趟没发生交换,flag保持为0
冒泡排序算法分析
最好情况(正序)
比较次数:n-1
移动次数:0
最坏情况(逆序)
比较次数:
移动次数:
冒泡排序最好时间复杂度是O(n)
冒泡排序最坏时间复杂度是O(n^2)
冒泡排序最好时间复杂度是O(n^2)
冒泡排序算法中增加一个辅助空间temp,辅助空间为S(n)=O(1)
冒泡排序是稳定的
快速排序
------改进的交换排序
基本思想:任取一个元素(如:第一个)为中心(pivot:枢轴、中心点);
所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表;
对各子表重新选择中心元素并依此规则调整(递归思想);
直到每个子表的元素只剩一个。
快速排序
基本思想:通过一趟排序,将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序。
具体实现:选定一个中间数作为参考,所有元素与之比较,小的调到其左边,大的调到其右边。
(枢轴)中间数:可以是第一个数、最后一个数、最中间一个数、任选一个数等。
每一趟的子表的形成是采用从两头向中间交替式逼近法;
由于每趟中对各子表的操作都相似,可采用递归算法。
快速排序算法分析:
时间复杂度:
可以证明,平均计算时间是O()。
Qsort():O()
Partition():O(n)
实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。
空间复杂度:
快速排序不是原地排序
由于程序中使用了递归,需要递归调用栈的支持,而栈的长度取决于递归调用的深度。(即使不用递归,也需要用用户栈)
在平均情况下:需要O(logn)的栈空间
最坏情况下:栈空间可达O(n)
快速排序是一种不稳定的排序方法
快速排序不适于对原本有序或基本有序的记录序列进行排序
划分元素的选取是影响时间性能的关键;
输入数据次序越乱,所划分元素值的随机性越好,排序速度越快,快速排序不是自然排序方法;
改变划分元素的选取方法,至多只能改变算法平均情况下的时间性能,无法改变最坏情况下的时间性能。即最坏情况下,快速排序的时间复杂性总是O()。
选择排序
简单选择排序
基本思想:在待排序的数据中选出最大(小)的元素放在其最终的位置。
基本操作:
1.首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换;
2.再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换;
3.重复上诉操作,共进行n-1趟排序后,排序结束。
简单选择排序算法分析
时间复杂度:
记录移动次数:
最好情况:0
最坏情况:3(n-1)
比较次数:无论待排序列处于什么状态,选择排序所需要进行的“比较”次数相同。
简单选择排序是不稳定排序
堆排序
若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重新又建成一个堆,则得到n个元素的次小值(次大值).......如此反复,便能得到一个有序序列,这个过程称之为堆排序。
实现堆排序需要解决两个问题:
1.如何由一个无序序列建成一个堆?
2.如何在输出堆顶元素后,调整剩余元素为一个新的堆?
解决问题2:堆的调整
(1)输出堆顶元素之后,以堆中最后一个元素替代之;
(2)然后将根结点值与左、右子树的根结点值进行比较,并与其中小(大)者进行交换;
(3)重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。
可以看出:对一个无序序列反复“筛选”就可以得到一个堆;即:从一个无序序列建堆的过程就是一个反复“筛选”的过程。
解决问题1:堆的建立
很显然,单结点的二叉树是堆;
在完全二叉树中所有以叶子结点(序号i>n/2)为根的子树是堆。
这样,我们只需依次将以序号为n/2,n/2-1,.....,1的结点为根的子树调整为堆即可。
即:对应由n个元素组成的无序序列,“筛选”只需从第n/2个元素开始。
由以上分析可知:
若对一个无序序列建堆,然后输出根;重复该过程就可以由一个无序序列输出为有序序列。
实质上,堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的。
算法性能分析:
堆排序的时间主要耗费在建初始堆和调整建新堆时进行反复“筛选”上。堆排序在最坏情况下,其时间复杂度也为0(),这是堆排序的最大优点。无论待排序列中的记录是正序还是逆序排列,都不会时堆排序处于“最好”或“最坏”的状态。
另外,堆排序仅需一个记录大小供交换用的辅助存储空间。
然而堆排序是一种不稳定的排序方法,它不适用于待排序记录个数n较少的情况,但对n较大的文件还是很有效的。
归并排序
基本思想:将两个或两个以上的有序子序列“归并”为一个有序序列。
在内部排序中,通常采用的是2-路归并排序。即:将两个位置相邻的有序子序列R[/...m]和R[m+1...n]归并为一个有序序列R[/...n]
两两比较大小
算法分析:
时间效率:O(nlog2n)
空间效率:O(n)
因为需要一个与原始序列同样大小的辅助序列(R1)。这正是此算法的缺点
稳定性:稳定
基数排序
基本思想:分配+收集
也叫桶排序或箱排序:设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的连接。
基数排序:数字是有范围的,均由0-9这十个数字组成,则只需设置十个箱子,相继按个、十、百...进行排序。
基数排序算法分析:
时间效率:O(k*(n+m)) k:关键字个数,m:关键字取值范围为m个值
空间效率:O(n+m)
稳定性:稳定