内部排序
- 排序方法是稳定的:待排序序列中可能存在多个关键字相等的记录,如果在排序前Ri与Rj关键字相同,Ri领先于Rj,排序后Ri也领先于Rj,则称是稳定的。反之不稳定
- 内部排序的方法很多,但就其全面性能而言,很难提出一种被认为是最好的方法,每一种方法都有各自的优缺点,适合在不同的环境(如记录的初始排列状态)下使用。
- 如果按排序过程中依据的不同原则和对内排方法进行分类,则大致可分为插入排序、交换排序、选择排序、归并排序、和计算排序。
- 如果按内排过程中所需的工作量来区分,则可分为简单的排序方法 其时间复杂度为O(n2) 先进的排序方法,其时间复杂度为O(nlogn) 基数排序,其时间复杂度为O(d*n).
- 更多算法见《计算机程序设计技巧》第三卷排序和查找
- 在学习时除了掌握算法本身之外,更重要的是了解该算法在进行排序时所依据的原则,以利于学习和创造更加新的算法
- 排序过程中需进行两个基本操作,(1)比较两个关键字大小(2)移动位置 前一个操作对大多数排序方法都是必要的,而后一个操作可以通过改变记录的存储方式来予以避免。 待排序的记录可有三种存储方法(1)顺序存储结构 即待排序的两个相邻的记录其存储位置也相邻,即记录的之间的次序关系由其存储位置决定,则实现排序必须借助移动记录。(2)静态链表,记录之间的次序关系由指针指示,则实现排序不需要移动记录,仅需修改指针(3)待排序记录本身存储在一组地址连续的存储单元内,同时另设一个指示各个记录存储位置的地址向量,在排序过程中不移动记录本身,而移动向量中这些记录的地址,在排序结束之后再按照地址向量中的值调整记录的存储位置。
- 插入排序
- 有直接插入排序 折半插入排序 希而排序等
- 直接插入排序要点说明
- 最简单的算法是先顺序查找(从1开始)查找好后再插入。
- 也可以从后往前查找,这样查找的过程中就能移动记录了,即边查找边移动 所以要有一个临时变量记录最后一个记录。因为记录不一定是一个变量,可能是结构体,所以要么建一个同一类型的变量,要么用r[0]
- 整个排序过程要进行n-1次。即你先要设计一个函数,该函数完成向i个有序序列中插入记录r的操作,然后在主函数中一个n-1的大循环,依次从2开始插入到n
- 算法简单 当n很小时,效率也很高
- 折半插入排序
- 对有序表可采用折半查找,其性能优于顺序查找。当然这仅限于顺序表。
- 折半插入排序附加存储空间与直接插入排序相同,从时间上比较,折半插入排序仅减少了关键字间的比较次数,而记录的移动资料不变,因此折半插入排序的时间复杂度为O(n2)
- 希尔排序
- 又叫 缩小增量排序。也是一种插入排序,但在时间效率上比前几种插入排序有较大的改进。是对直接插入排序的改进。
- 希尔排序基本思想
直接插入排序 算法时间复杂度为O(n2),但是若待排记录序列为正序时,其时间复杂度到O(n),由此可设想,若待排记录序列按关键字“基本有序”则效率就大大提高。 - 希尔排序高效的原因
子序列不是简单地逐段分割,而是将相隔某个增量的记录组成一个子序列。 因此关键字较小的记录就不是一步一步往前挪动,而是跳跃式移动,从而使得最后一趟增量为1的插入排序时,序列已基本有序。 - 增量序列最后一个增量必须为1 其它的按递减的方式减小增量,至于如何减自己看着办。
- 一趟排序的含义
假设有10个记录 刚开始增量为3 则把(1 4 7 10)(2 5 8)(3 6 9)三个序列排好序后,算一趟希尔排序。
但注意了 实际程序并不是对每个子序列单独排序。即并不是先对(1 4 7 10)排序 再对(2 5 9)排序。而是for(i=d+1;i<=n;i++) 即 i先从4开始,调用shellins,排好(1 4)然后i变成5 调用shellins 排好(2 5) 然后i变成6 排好(3 6) 然后i变成7 排好(1 4 7) 然后i变成8 排好(2 5 8) 然后i变成9 排好(3 6 9 )至此一趟排序结束,改变增量进行第二趟。 - 具体算法分三个函数
- shellSort 总函数 每次改变增量 调用shellPass 完成一趟排序。
- shellPass 完成一趟希尔排序 每次都调用shellins 保证子序列有序
- shellins 保证子序列有序 如保证r1 r6 有序。
- 交换排序
- 起泡排序 bubble sort
- 显然 判别起泡排序结束的条件应该是“在一趟排序过程中没有进行过交换记录的操作”以前写程序从来没有这个判别过,都是for(i=1;i<n;i++)
- 若初始序列为正序,则只需进行一趟排序,在排序过程 中进行n-1次关键字间的比较,且不移动记录;反之,若初始序列为逆序,则需进行n-1趟排序,需进行n(n-1)/2次比较,并用等数量级的记录移动,因此总的时间复杂度为O(n2)
- 快速排序 quick sort
- 是对起泡的改进。基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
- 具体作法:先从后往前找,如果有比支点小的,则交换支点与该记录,再从前往后找,如果有比支点大的,则交换。直到i==j
- 改进:可以不交换,只赋值,最后支点的位置肯定是i==j的地方
- 从时间看,快速排序的平均性能优于前面讨论过的各种排序方法,从空间上看,快速排序需一个栈空间来实现递归。
- 起泡排序 bubble sort
- 选择排序
- 简单选择排序
- 基本思想:每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中第i个记录。其中最简单、且最为读者熟悉的是简单选择排序 simplae slection sort
- 写算法时分成两部分写,一部分是一趟排序的函数。另一部分才是主算法函数。你以前写时基本是一个函数。
- 简单选择排序过程 中,所需进行记录移动的操作次数较小,其最小值为0 最大值为3(n-1)。然而,无论记录的初始排列如何,所需进行的关键字间的比较次数相同。均为n(n-1)/2 因此总的时间复杂度也是O(n2)
- 堆排序
- 堆定义:有一个关系式这里不好表达出来
- 若将序列对应的一维数组看成一个完全二叉树,则堆的含义表明 完全二叉树中所有非终端结点的值均不大于(或不小于)其左右孩子结点的值。由些 则堆顶元素必须(或完全二叉树的根)必为序列中最小值(或最大值)
- 在输出堆顶最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素中的次小值。如此反复,便得到一个有序序列。这个过程叫堆排序
- 实现堆排序要解决两个问题:一如何由一个无序序列建成一个堆?二是如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
- 筛选:自堆顶到叶子的调整过程。
- 基本方法:
- 建堆:从一个无序序列建堆的过程就是一个反复筛选的过程 。若将序列看成一个完全二叉树,则从最后一个非叶子结点开始进行筛选,即小于n/2的整数。
- 进行如下 n-1次
- 删除堆顶元素:交换底与顶 调整(筛选)
- 堆排序对于记录数较少的文件并不提倡,但对n较大的文件很有效。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复筛选上。由此堆排序在最坏的情况下,其时间复杂度为O(nlogn)。相对于快排,这是堆最大优点。此外,堆仅需一个记录大小供交换使用。
- 简单选择排序
- 归并排序
- merging sort是又一类不同的排序方法,归并的含义是将两个或两个以上的有序表组合成一个新的有序表。它的实现方法早已被熟悉,无论是顺序存储结构还是链表存储结构。都可以在O(m+n)的时间量级上实现。
- 与快速排序和堆排序相比,归并排序的最大特点是它是一种稳定的排序方法。但在一般情况下很少使用。
- 排序总结
- 性能图 290页
排序方法 平均时间 最坏情况 辅助存储 简单排序 O(n2) O(n2) O(1) 快速排序 O(nlogn) O(n2) O(logn) 堆排序 O(nlogn) O(nlogn) O(1) 归并排序 O(nlogn) O(nlogn) O(n) 基数排序 O(d(n+rd)) O(d(n+rd)) O(rd) - 从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者相比较的结果是,当n较大时,归并排序所需时间较堆排序省,但其所需辅助存储最多。{堆刚开始还要建堆呢}
- 上表中的“简单排序”包括除希尔排序之外的所有插入排序,起泡排序和简单选择排序。其中以直接插入排序最为简单,当序列中的记录“基本有序”或n值较小时,它是最佳的排序方法,因此常将它和其它的排序方法,诸如快速排序、归并排序等结合使用
- 从方法的稳定性上来看,基数排序是稳定的内排方法。所有时间复杂度为O(n2)的简单排序法也是稳定的,然而,快速排序 堆排序 和希尔排序等时间性能好的排序方法都是不稳定的。一般来说,排序过程的“比较”是在“相邻的两个记录关键字”间进行的排序方法是稳定的。 由于大多数情况下排序是按记录的主关键字进行的,则所用的排序方法是否稳定无关紧要。若排序按记录的次关键字进行,则应根据问题慎重选择排序方法及其描述算法。
- 性能图 290页