ACM预定函数学习与实践——排序

来源:互联网 发布:centos 64位安装 编辑:程序博客网 时间:2024/06/02 08:10

    排序(Sorting)是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个项值有序的序列。作为排序依据的数据项称为“排序码”,也即数据元素的关键码。为了便于查找,通常希望计算机中的数据表是按关键码有序的。如有序表的折半查找,查找效率较高。还有,二叉排序树、B-树和B+树的构造过程就是一个排序过程。若关键码是主关键码,则对于任意待排序序列,经排序后得到的结果是唯一的;若关键码是次关键码,排序结果可能不唯一,这是因为具有相同关键码的数据元素,这些元素在排序结果中,它们之间的的位置关系与排序前不能保持一致。

    若对任意的数据元素序列,使用某个排序方法,对它按关键码进行排序:若相同关键码元素间的位置关系,排序前与排序后保持一致,称此排序方法是稳定的;而不能保持一致的排序方法则称为不稳定的。

     排序分为两类:内排序和外排序。

      内排序:指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列。  

     外排序:指排序过程中还需访问外存储器,足够大的元素序列,因不能完全放入内存,只能使用外排序。

 

 

直接插入排序

首先应用序列21018259介绍插入排序的思想。

算法分析:  

设有n个记录,存放在数组r中,重新安排记录在数组中的存放顺序,使得按关键码有序。即

r[1].keyr[2].key≤……≤r[n].key

    先来看看向有序表中插入一个记录的方法:

设1<j≤n,r[1].keyr[2].key≤……≤r[j-1].key,将r[j]插入,重新安排存放顺序,使得r[1].keyr[2].key≤……≤r[j].key,得到新的有序表,记录数增1。

 算法:

   r[0]=r[j]         //r[j]r[0]中,使r[j]为待插入记录空位

      i=j-1             //从第i个记录向前测试插入位置,用r[0]为辅助单元, 可免去测试i<1

   r[0].keyr[i].key,转④。    //插入位置确定

   r[0].key < r[i].key时,

         r[i+1]=r[i]i=i-1;转②。    //调整待插入位置

   r[i+1]=r[0];结束。              //存放待插入记录

向有序表中插入一个记录的过程如下:

              r[1]  r[2]   r[3] r[4]   r[5]    存储单元

               2    10    18    25     9       r[5]插入四个记录的有序表中,j=5 r[0]=r[j]i=j-1 初始化

               2    10    18    25           r[i+1]为待插入位置 i=4r[0] < r[i]r[i+1]=r[i]i--调整待插入位置

               2    10    18         25      i=3r[0] < r[i]r[i+1]=r[i]i--          调整待插入位置

               2    10        18     25      i=2r[0] < r[i]r[i+1]=r[i]i--          调整待插入位置

               2        10    18     25      i=1r[0] r[i]r[i+1]=r[0]插入位置确定,向空位填入插入记录

               2     9    10    18     25      向有序表中插入一个记录的过程结束

    直接插入排序方法:仅有一个记录的表总是有序的,因此,对n个记录的表,可从第二个记录开始直到第n个记录,逐个向有序表中进行插入操作,从而得到n个记录按关键码有序的表。

算法:

void   InsertSort(S_TBL &p)

{     for(i=2i<=p->lengthi++)

         if(p->elem[i].key < p->elem[i-1].key)          /*小于时,需将elem[i]插入有序表*/

          { 

              p->elem[0].key=p->elem[i].key      /*为统一算法设置监测*/

              for(j=i-1p->elem[0].key < p->elem[j].keyj--)

                    p->elem[j+1]=p->elem[j]   /*记录后移*/

              p->elem[j+1]=p->elem[0]       /*插入到正确位置*/

          }          

}

    空间效率:仅用了一个辅助单元。

    时间效率:向有序表中逐个插入记录的操作,进行了n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。

    最好情况下:即待排序列已按关键码有序,每趟操作只需1次比较2次移动。

                 总比较次数=n-1

                 总移动次数=2(n-1)

最坏情况下:即第j趟操作,插入记录需要同前面的j个记录进行j次关键码比较,移动记录的次数为j+2次。

     平均情况下:即第j趟操作,插入记录大约同前面的j/2个记录进行关键码比较,移动记录的次数为j/2+2次。

 

直接插入排序的时间复杂度为O(n2)。是一个稳定的排序方法。

 

希尔排序(Shell’s Sort)

    希尔排序又称缩小增量排序,是1959年由D.L.Shell提出来的,较前述几种插入排序方法有较大的改进。

    直接插入排序算法简单,在n值较小时,效率比较高,在n值很大时,若序列按关键码基本有序,效率依然较高,其时间效率可提高到O(n)。希尔排序即是从这两点出发,给出插入排序的改进方法。

例 待排序列为 39,80,76,41,13,29,50,78,30,11,100,7,41,86。

步长因子分别取5、3、1,则排序过程如下:

p=5    

39  80  76  41  13  29  50  78  30  11  100   7  41  86

└─────────┴─────────┘

    └─────────┴──────────┘                        └─────────┴──────────┘                                                                                   └─────────┴──────────┘

                 └─────────┘

子序列分别为{39,29,100},{80,50,7},{76,78,41},{41,30,86},{13,11}。

第一趟排序结果:

p=3    

29   7  41  30  11  39  50  76  41  13  100  80  78  86

└─────┴─────┴─────┴──────┘

    └─────┴─────┴─────┴──────┘

        └─────┴─────┴──────┘

子序列分别为{29,30,50,13,78},{7,11,76,100,86},{41,39,41,80}。

第二趟排序结果:

p=1     13   7  39  29  11  41  30  76  41  50  86  80  78  100

    此时,序列基本“有序”,对其进行直接插入排序,得到最终结果:

 7  11  13  29  30  39  41  41  50  76  78  80  86  100

    希尔排序方法:

  1. 选择一个步长序列t1,t2,…,tk,其中ti>tj,tk=1;

2. 按步长序列个数k,对序列进行k趟排序;

3. 每趟排序,根据对应的步长ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅步长因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。

 算法:

void   ShellInsert(S_TBL &pint dk)

{     /*一趟增量为dk的插入排序,dk为步长因子*/

      for(i=dk+1i<=p->lengthi++)

         if(p->elem[i].key < p->elem[i-dk].key)  /*小于时,需elem[i]将插入有序表*/

          {  p->elem[0]=p->elem[i]       /*为统一算法设置监测*/

              for(j=i-dkj>0&&p->elem[0].key < p->elem[j].keyj=j-dk)

                    p->elem[j+dk]=p->elem[j]   /*记录后移*/

               p->elem[j+dk]=p->elem[0]       /*插入到正确位置*/

              }

}

void   ShellSort(S_TBL *p,int dlta[],int t)

{   /*按增量序列dlta[0,1…,t-1]对顺序表*p作希尔排序*/

    for(k=0;k<t;t++)

        ShellSort(p,dlta[k]); /*一趟增量为dlta[k]的插入排序*/

}

 效率分析

    希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于步长因子序列的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的步长因子序列的方法。步长因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:步长因子中除1外没有公因子,且最后一个步长因子必须为1。希尔排序方法是一个不稳定的排序方法。 

 

快速排序

快速排序是通过比较关键码、交换记录,以某个记录为界(该记录称为支点),将待排序列分成两部分。其中,一部分所有记录的关键码大于等于支点记录的关键码,另一部分所有记录的关键码小于支点记录的关键码。我们将待排序列按关键码以支点记录分成两部分的过程,称为一次划分。对各部分不断划分,直到整个序列按关键码有序。

 例: r[1] r[2] r[3] r[4] r[5] r[6] r[7] r[8] r[9] r[10]  存储单元

     49   14   38   74   96   65    8   49   55   27  记录中关键码

low=1;high=10; 设置两个搜索指针, r[0]=r[low];  支点记录送辅助单元,

    ↑                                           

     low                                           high

第一次搜索交换

    从high向前搜索小于r[0].key的记录,得到结果

     27   14   38   74   96   65    8   49   55    

                                                

     low                                          high

    从low向后搜索大于r[0].key的记录,得到结果

     27   14   38      96   65    8   49   55   74 

                                                

                    low                            high

第二次搜索交换

    从high向前搜索小于r[0].key的记录,得到结果

     27   14   38     8   96   65      49   55   74 

                                  

                      low            high

    从low向后搜索大于r[0].key的记录,得到结果

     27   14   38     8      65   96   49   55   74 

                                 

                          low       high

第三次搜索交换

    从high向前搜索小于r[0].key的记录,得到结果

     27   14   38     8      65   96   49   55   74 

                         ↑↑

                       low  high

    从low向后搜索大于r[0].key的记录,得到结果

     27   14   38     8      65   96   49   55   74 

                         ↑↑

                       low  high

low=high,划分结束,填入支点记录

     27   14   38     8   49   65   96   49   55   74

 一次划分方法:

    设1≤p<q≤n,r[p],r[p+1],...,r[q]为待排序列

   ① low=p;high=q;  

      r[0]=r[low];     //取第一个记录为支点记录,low位置暂设为支点空位

   ② 若low=high,支点空位确定,即为low。

      r[low]=r[0];                //填入支点记录,一次划分结束

      否则,low<high,搜索需要交换的记录,并交换之

   ③ 若low<high且r[high].key≥r[0].key

       high=high-1;转③                  //寻找r[high].key<r[0].key

       r[low]=r[high];   //找到r[high].key<r[0].key,设置high为新支点位置,

                                  //小于支点记录关键码的记录前移。

   ④ 若low<high且r[low].key≤r[0].key

       low=low+1;转④        //寻找r[low].key≥r[0].key

       r[high]=r[low]; //找到r[low].key≥r[0].key,设置low为新支点位置

       转②             //继续寻找支点空位

 一趟排序算法

int Partition(S_TBL *tbl,int low,int high)      /*一趟快排序*/

{   /*交换顺序表tbl中子表tbl->[lowhigh]的记录,使支点记录到位,并反回其所在位置*/

     /*此时,在它之前()的记录均不大()于它*/

       tbl->r[0]=tbl->r[low];   /*以子表的第一个记录作为支点记录*/

       pivotkey=tbl->r[low].key;  /*取支点记录关键码*/

       while(low<higu)                        /*从表的两端交替地向中间扫描*/

       {     while(low<high&&tbl->r[high].key>=pivotkey)  high--;

              tbl->r[low]=tbl->r[high]; /*将比支点记录小的交换到低端*/

              while(low<high&&tbl-g>r[high].key<=pivotkey)  low++;

              tbl->r[low]=tbl->r[high]; /*将比支点记录大的交换到低端*/

       }

       tbl->r[low]=tbl->r[0];     /*支点记录到位*/

       return low;    /*返回支点记录所在位置*/

}

 快速排序算法:

void  QSort(S_TBL *tbl,int low,int high)     /*递归形式的快排序*/

{     /*对顺序表tbl中的子序列tbl->[lowhigh]作快排序*/

       if(low<high)

       {     pivotloc=partition(tbl,low,high); /*将表一分为二*/

              QSort(tbl,low,pivotloc-1);  /*对低子表递归排序*/

              QSort(tbl,pivotloc+1,high);  /*对高子表递归排序*/ f

       }

}

选择排序

    选择排序主要是每一趟从待排序列中选取一个关键码最小(最大)的记录,也即第一趟从n个记录中选取关键码最小(最大)的记录,放到第一个位置;第二趟从剩下的n-1个记录中选取关键码最小(最大)的记录,放到第二个位置;依此类推,直到整个序列的记录选完。这样,由选取记录的顺序,便得到按关键码有序的序列。

简单选择排序

    操作方法:第一趟,从n个记录中找出关键码最小(最大)的记录与第一个记录交换;第二趟,从第二个记录开始的n-1个记录中再选出关键码最小(最大)的记录与第二个记录交换;如此,第i趟,则从第i个记录开始的n-i+1个记录中选出关键码最小(最大)的记录与第i个记录交换,直到整个序列按关键码有序。

树形选择排序。

    按照锦标赛的思想进行,将n个参赛的选手看成完全二叉树的叶结点,则该完全二叉树有2n-2或2n-1个结点。首先,两两进行比赛(在树中是兄弟的进行,否则轮空,直接进入下一轮),胜出的再兄弟间再两两进行比较,直到产生第一名;接下来,将作为第一名的结点看成最差的,并从该结点开始,沿该结点到根路径上,依次进行各分枝结点子女间的比较,胜出的就是第二名。因为和他比赛的均是刚刚输给第一名的选手。如此,继续进行下去,直到所有选手的名次排定。)

 

 

堆排序(Heap Sort)

    堆排序:对n个元素的序列进行堆排序,先将其建成堆,以根结点与第n个结点交换;调整前n-1个结点成为堆,再以根结点与第n-1个结点交换;重复上述操作,直到整个序列有序。

两路归并的迭代算法:

    1个元素的表总是有序的。所以对n个元素的待排序列,每个元素可看成1个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1外,其余子

表长度均为2。再进行两两合并,直到生成n个元素按关键码有序的表。

建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。n个结点的完全二叉树,则最后一个结点是第n/2个结点的子女。对第n/2个结点为根的子树筛选,使该

子树成为堆,之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

基数排序

基数排序是一种借助于多关键码排序的思想,是将单关键码按基数分成“多关键码”进行排序的方法。

 

8.5.1  多关键码排序

    以扑克牌为例讲解基数排序。

    多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序

逐次排序,分两种方法:

    最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD法。

    最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法二即是LSD法。

原创粉丝点击