排序

排序

排序(sorting)又称为分类。概要地说,排序就是将待排序文件中的记录按排序码不减(或不增)的次序排列起来。  

其确切定义为:
设{R1,R2,…,Rn}是由n个记录组成的文件,{K1,K2,…,Kn}是相应的排序码的集合。 

所谓排序,就是确定1,2,…,n的一种排列p1,p2,…,pn,使得各排序码满足如下的非递减(或非递增)关系。
    
    如果待排序文件中,存在多个排序码相同的记录,经过排序后这些记录仍保持它们原来的相对次序,则称这种排序算法是“稳定的”;
    否则称该排序算法是“不稳定的”。  
    在排序过程中,整个文件都放在内存中处理的排序方法称为内排;  
    在排序过程中,不仅需要使用内存,而且还要使用外存的排序方法称为外排序。  
按所采用的策略不同,排序方法可分为五类:插入排序、交换排序、选择排序、归并排序和基数排序。
排序的过程就是对待排序文件的处理。其处理方式与存储结构相关,主要有三种。
(1)对记录本身进行物理重排,经过比较和判断,把记录移到合适的位置;
(2)对文件的辅助表(例如由排序码和指向记录的指针组成的目录表)的表目进行物理地重排,只移动辅助表的表目,而不移动记录本身。
(3)既不移动记录本身,也不移动辅助表的表目,而是在记录或辅助表的表目之间增加一条链,排序过程只改变这条链接上的指针,用以表示被排的顺序。
  1. 插入排序(insertion sort)
     每次将一个待排序的记录,按其排序码值的大小插到前面已经排序的子文件中的适当位置,直到全部记录插入完为止。

     本节主要讲述直接插入排序、希尔(Shell)排序和其他插入排序(包括折半插入排序和表插入排序)。

    1. 直接插入排序(straight insertion sort)
       每步完成一个记录的插入,对于 n个记录的排序问题需要从 2 到 n 共 n -1 趟。

       当要插入第 i (2≤i≤n)个记录时,文件已分为两部分:其中,前i-1个记录已排好序(称为有序子文件)。  
       
       这时Ki与,,…,逐个进行比较,找到应该插入的位置,从该位置的记录到第i-1个记录,都往后顺移一个位置,然后将Ri插入。   
       
       这一趟的处理结果为其中前 i 个记录已为有序。  
       
       从上述过程可以看出,每趟完成一个记录的插入,而每插入一个记录,前边有序子文件的记录个数就增1,
       因此,经过有限步之后,n个记录则都进入到有序子文件之中而达到全部有序。
      

    该算法的附加空间为O(1),算法的平均时间复杂度也为O(n2)。

    1. 希尔排序
      希尔(Shell)排序又称为缩小增量排序. Shell排序的平均时间复杂度为O(n1.3)

       首先取一个正整数 d1(d1< n),把文件的全部记录分成 d1个组;
       所有距离为 d1倍数的记录都放在同一组中,在各组内进行排序; 
       
       然后取正整数d2<d1,重复上述的分组和排序过程;  
       直到取dlast=1,即所有记录都放在同一组中排序为止。  
       由于开始时d1的取值较大,每组中的记录个数较少,排序速度较快;  
       待到排序的后期,di的取值逐渐变小,每组中的记录个数逐渐变多,但由于有前面工作的基础,  
       大多数记录已基本有序,所以排序速度仍然很快。
      
    2. 折半插入排序
       直接插入排序中,在插入Ri时,,…,已是排好序的。因此在插入Ri时,可改用折半比较的方法来寻找 Ri的插入位置。按这种方法进行的插入排序称为折半插入排序(binaryinsertion sort),也称为二分法插入排序。
       时间复杂度为O(nlog2n)。

    3. 表插入排序
      表插入排序(list insertion sort)的基本思想是:在每个结点中增加一个指针字段,在插入Ri(i=2,…,n)时,R1到Ri-1已经用指针按排序码值不减的次序链结起来,这时循链顺序比较,找到Ri应该插入(链入)的位置,然后做链表的插入,这样就得到从R1到Ri的一个通过链接指针排好序的链表。如此重复处理,直到把Rn插入链表中排好序为止。

      执行表插入排序可以用数组 R 存放待排序的文件,记录之间的链接关系用next 字段表示,next字段取值为数组元素的下标,头指针就放在R[0].next字段中。
      这种形式的链表称作静态链表。

  2. 交换排序
    交换排序(exchange sorting)的基本思想是:两两比较待排序记录的排序码,并交换不满足顺序要求(反序)的那些偶对,直到不再存在这样的偶对为止。本节介绍两种交换排序:冒泡排序和快速排序。

    1. 冒泡排序
       冒泡排序(bubble sort)又称为起泡排序,它是一种简单的交换排序方法。其具体做法是:设n个记录的待排序文件存放在数组R[1‥n] 中,首先比较Kn-1和Kn,如果Kn-1> Kn(发生逆序),则交换Rn-1和Rn;
       然后Kn-2和Kn-1(可能是刚交换来的)做同样的处理;重复此过程直到处理完K1和 K2。上述的比较和交换的处理过程称为一次起泡。第一趟结果是将排序码最小的记录交换到文件第一个记录R1的位置(也是最终排序的位置),其他的记录大多数都向着最终排序的位置移动。但也可能出现个别的记录向着相反的方向移动的情况。第二趟再对R[2‥n] 部分重复上述处理过程,这一趟的结果是将排序码次最小的记录交换到文件第二个记录R2的位置。如此一趟一趟地进行下去……至多经过n-1趟(n-1次起泡)就可达到全部有序。

      冒泡排序算法的平均时间复杂度为O(n2),空间复杂度为O(1)。

    2. 快速排序
       快速排序(quick sort)又称划分交换排序。

      其基本思想是:在待排序文件的n个记录中任取一个记录(例如就取第一个记录)作为基准,
      将其余的记录分成两组,第一组中所有记录的排序码都小于或等于基准记录的排序码;
      第二组中所有记录的排序码都大于或等于基准记录的排序码,
      基准记录则排在这两组的中间(这也是该记录最终排序的位置);
      然后分别对这两组记录重复上述的处理,直到所有的记录都排到相应的位置为止。  
      

      快速排序的时间复杂度为O(nlog2n)。

  3. 选择排序

     选择排序(selection sort)基本思想是:第一趟在有n个记录的待排序文件中,选出排序码最小(大)的记录,
     并把它与剩余的n-1个记录分开;然后在剩余的n-1个记录再选出排序码最小(大)的记录,
     并把它与剩余的n-2个记录分开;依次重复下去,……,一般第i趟(i = 1,2,…,n-1)在当前剩余的 n- i+1 个待排序记录中选出排序码最小(大)的记录,
     作为有序子文件第 i个记录。
     待到第n-1趟结束时,剩余的待排序文件中只剩下一个记录,
     它就是整个待排序文件中的排序码最大(小)的记录,至此排序已完成。
     本节将介绍直接选择排序、树形选择排序和堆排序三种选择排序的方法。  
    
    1. 直接选择排序

      直接选择排序(straight selection sort)又称为简单选择排序(simple selection sort),它是一种简单的排序方法。

       其做法是:首先在所有记录中选出排序码最小(大)的记录,  
       把它与第一个记录交换,然后在其余的记录中再选出排序码最小(大)的记录与
       第二个记录交换,以此类推,直到所有记录排序完成。  
      
    2. 树形选择排序
      树形选择排序(tree selection sort),又称锦标赛排序(tournament sort)。 树形选择排序总的时间开销为O(nlog2n),总的附加空间量为2n-1。

       直接选择排序时,为了从n个排序码中找出最小的排序码,需要进行n-1次比较,
       然后为在n-1个排序码中找出次最小的排序码需要进行n-2次比较。 
       
       树形选择排序的具体做法是:把n个排序码两两进行比较,取出⎡n/2⎤ 个较小的排序码作为第一步比较的结果保存下来,再把这 ⎡n/2⎤ 个排序码两两分组并进行比较,…,
       如此重复,直到选出一个排序码最小的记录为止。
       
       在选择次最小排序码时,
       只要将结点中最小排序码改成+∞(实现时用机器最大数来代替),
       重新进行比较,这时,实际上只要修改从树根到刚刚改为+∞的叶结点这条路径上的各结点的值,
       其他结点均保持不变。如此反复,直到排序完成。
      
    3. 堆排序
      堆排序(heap sort)是由它的发现者J.W.J.Williams 于1964 年命名的,是对树形选择排序的进一步改进。
      使得时间开销与树形选择排序相同,也为O(nlog2n)。
      同时又不需增加像树形选择排序那么多的附加存储空间,堆排序的附加存储空间仅为O(1)。

       堆排序的基本思想是:  
       (1)将待排序文件的n个记录,利用堆的调整算法FilterDown( )建成初始堆;  
       
       (2)输出堆顶记录;  
       
       (3)对剩余的记录重新调整成堆;  
      
  4. 归并排序

     将已有序的子文件进行合并,得到完全有序的文件。
     合并时只要比较各有序子文件的第一个记录的排序码,排序码最小的那个记录就是排序后的第一个记录;
     取出这个记录,然后继续比较各个子文件的第一个记录,便可找出排序后的第二个记录;
     如此继续下去,只要经过一趟扫描就可以得到最终的排序结果。
     
     对于排序码任意排列的待排序文件进行归并排序时,可以把文件中的n个记录看成n个子文件。
     每个子文件只包含一个记录,显然对于个子文件来说是有序的。
     
     但是,要想只经过一趟扫描就将n个子文件全部归并成一个有序的文件显然是困难的。
     通常,可以采用两两归并的方法,即每次将两个子文件归并成一个较大的有序子文件。
     
     第一趟归并后,得到个长度为2的有序子文件(最后一个子文件长度可能为1);
     在此基础上,再进行以后各趟的归并,每经过一趟后,子文件的个数约减少一半,而每个子文件的长度约增加一倍。
     如此反复,直到最后一趟将两个有序子文件归并到一个文件中,这时整个排序工作就完成了。
    

    时间代价为O(nlog2n),空间复杂度为O(n)。

  5. 基数排序

     基数排序(radix sort)又称为桶排序(bucket sort),
     是一种采用“分配”和“收集”的办法,
     借助于多排序码排序的思想来实现对单排序码进行排序的方法。
     是利用“分配”和“收集”两种操作对单排序码进行排序的一种内部排序的方法。
     
     所以总的时间复杂度为 O(d(n+radix))。
     算法所需要的附加存储空间是为每个记录增设的指针字段,
     以及每一个队列的队头和队尾指针,总共为n+2radix。
    
  6. 外排序

     外排序过程主要分为两个阶段:
     (1)根据为外排序所用的内存缓冲区的大小,将外存上含有n个记录的待排序文件划分成若干个子文件或段(segment),
     依次读入内存并用有效的内排序方法对各段进行排序。然后将这些经过排序的段回写到外存,通常称这些经过排序的段(即有序子文件)为归并段或顺串(run)。
     
     (2)对这些归并段进行逐趟归并,使归并段的长度逐趟增大,
     直到最后归并成一个大归并段(即整个有序文件)为止。
    
    • 2路平衡归并

      由于内、外存在读写时间上存在着很大的差异,因此提高外排序速度的关键是减少对数据的扫描的遍数(即对顺串归并的趟数)。
      
      因此,增大归并路数,可减少归并趟数,从而减少总的读写外存的次数d。
      
      从表8-1中可以看出,采用6路归并比2路归并可减少近一半的读写外存的次数。
      
      一般地,对m个顺串,做k路平衡归并,归并树可用正则k叉树(即只有度为0和度为k的结点的k叉树   )来表示。
      
    • k路平衡归并与败者树

      败者树(tree of loser)实际上是一棵完全二叉树,它是树形选择排序的一种变型。
      败者树就是在比赛(选择)树中,每个非叶结点均存放其两个子女结点中的败者,而让胜者去参加上一层的比赛。
      叶结点指向对应缓冲区中的当前第一个记录。
      此外,在根结点之上还增加进一个双亲结点,它为比赛的“冠军”。
      
    • 最佳归并树

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值