1. 很久以前没有胭脂,女子的脸只为情郎红。
2. 世界如此广阔,你却走进了悲伤的墙角
1. 排序:
2. 插入排序
2.1. 直接插入排序
2.2. 折半插入排序
2.3. 希尔排序
3. 交换排序
3.1. 冒泡排序
3.2. 快速排序
4. 选择排序
4.1 简单选择排序
4.2 堆排序
5. 归并排序和基数排序
6. 各种内部排序算法的比较及其应用
要点
前面的排序都是在内存中进行的(称之为内部排序)。然而实际应用中,经常需要对大文件进行排序,因为文件中记录很多,信息量庞大,所以无法将整个文件拷贝进内存中进行排序。
因此,需要将待排序的记录存储在外存上,排序时再把数据一部分一部分的调入内存中进行排序。在排序的过程中,需要多次进行内存和外存之间的交换,对外存文件中的记录排序之后仍然存到原文件,这种排序方法称之为外部排序
主要考虑:
主要关心访问磁盘的次数。因为磁盘的读写的机械动作(I/O次数)所需要的时间要远远超过内部排序的时间
根据外存设备的不同,可以将排序分之为磁盘文件排序(直接存取设备)和磁带文件排序(顺序存取设备)
外排主要采用归并排序方法。分为两个阶段:
第一阶段:1,2,3,由此得到归并段;
第二阶段:4:归并段二路归并得到更大的归并段。
例子:
一个含有2000个记录的文件,每个磁盘块可以容纳250个记录,则该文件包括8个磁盘块(R1-R8)。然后对该文件做二路归并排序,每次往内存中读取两个磁盘块,排序后再写回磁盘。若把内存工作区分为3个缓存区,其中两个为输入缓存区(缓存区1和缓存区2),一个为输出缓存区,可以在内存中利用内部排序方法(二路归并merge函数)实现二路归并。
具体做法:
1. 从参加归并排序的两个输入归并段R1和R2中分别读入一个块(注意用词,这里说的是一个块,没说一定要把所有都写到对应的输入缓冲区中,第一次归并的时候直接将两个归并块放入输入缓存区正好放满,但是之后的第二第三次归并就要再取)放在输入缓存区1(每次在输入缓存区中找最小值?下面验证)和输入缓存区2,然后在内存中进行二路归并。归并出来的对象顺序存放在输出缓存区中。
若输入缓存区存满,则将其内的对象顺序写到输出归并段R1'中,再将输出缓存区清空,继续存放归并后的对象。
若某一个输入缓存区中的对象取空,则从对应的输入归并段中再读取下一磁盘块(这种情况在第一趟归并时不会出现),继续参加排序,如此继续归并,直到两个归并段中的对象全部读入内存并都归并完成为止。
2. 当R1和R2归并完之后,再归并R3和R4,R5和R6,R7和R8,这样算一趟归并。
3. 再把上次归并得到的结果R1',R2',R3',R4'两两归并,这是第二次归并。
4. 最后把R1''和R2''两个归并段进行排序,这是第三次归并。
所以一共进行了3趟归并。
外部排序的总时间:
外部排序的总时间 =
内部排序所需要的总时间(二路归并merge) +
外存信息读写的时间(I/O操作) +
内部归并所需要的时间(二路归并)
Note:
由于外存上信息的读/写是以“物理块”为单位的,并且每个物理块可容纳250个记录。可知每一趟归并需要8次“读”和8次“写”,3趟归并加上内部排序的时候还要进行一次I/O(将外存上的对象一个个放进内存再放回去(这算是一个读写过程)),所以总共需要((8+8)*(3+1)) = 64次读写,所以上述二路平衡排序的总时间是:
r * 每一个归并段内部排序的总时间 + 64 * T(I/O) + S(n-1)* 每做一次归并,取得一个关键字最小的记录的时间。
r是归并段数 = 8
S是归并趟数
n是每趟参加二路归并的记录的个数
但是对于四路归并排序,那么就只需要进行两趟归并,外排时总的读写次数便减小至2 * 16 + 16 = 48次
因此,增大归并路数就可以减少归并趟数,从而减少总的磁盘I/O总数
只要增大归并路数m,或者减少初始归并段个数r,都能减少读写磁盘次数d,进而提高外排速度。
注意哈,上面增加了归并路数的时候,可以减少归并段数S,进而减少I/O次数,但是!
当增加归并段的时候,内部排序的时间也会增加,这将抵消增大m而减少I/O次数带来的效益,所以!
不能使用普通的内部归并算法。
为了使内部排序的时间不受增大m的影响,提出了败者树。(树形排序的一种变形,可以看做是完全二叉树)
特点:
叶节点:存放各归并段在归并过程中当前参加比较的记录
内部结点:存放左右子树中的失败者,而让胜利者继续向上比较,一直到根节点
如此比较两个数,大的为失败者,小的为胜利者,则根节点指向的数为最小数
使用败者树之后,内部归并的比较次数就和m没有关系了,因此,只要内存空间允许,增大归并路数就能有效地减少归并树的高度,从而减少I/O操作的次数,提高外部排序的速度。
但是凡事都有一个度,m并不是越大越好,当归并路数增加的时候,就要相应地增加内存中输入缓冲区的个数,如果可以使用的内存空间不变,这样势必会减少输入缓冲区的容量,使得内外存交换数据的次数增大,所以读写外存的次数也会增加。
前面提到,不光是增加归并路数,减少初始段的长度也是一个好办法减少外排的时间。