排序算法总结及感悟

一、排序的概念

排序:有n个记录的序列{R1,R2,…,Rn},其相应关键字的序列是{K1,K2,…,Kn},相应的下标序列为1,2,…,n。通过排序,要求找出当前下标序列1,2,…,n的一种排列p1,p2,…,pn,使得相应关键字满足如下的非递减(或非递增)关系,即:Kp1≤Kp2≤…≤Kpn,这样就得到一个按关键字有序的记录序列:{Rp1,Rp2, …,Rpn}。

(1)内部排序

        整个排序过程完全在内存中进行,称为内部排序。

(2)外部排序

        由于待排序记录数据量太大,内存无法容纳全部数据,排序需要借助外部存储设备才能完成,称为外部排序。

 (3)排序算法

          

     (1)直接插入排序

            ①基本思想

                基本操作是将第i个记录插入到前面i-1个已排好序的记录中,具体过程为:将第i个记录的关键字Ki顺次与其前面记录                  的关键字Ki-1,Ki-2,…K1进行比较,将所有关键字大于Ki的记录依次向后移动一个位置,直到遇见一个关键字小于                  或者等于Ki的记录Kj,此时Kj后面必为空位置,将第i个记录插入空位置即可。

           ②  步骤

A){ 48 }       62         35       77       55      14       35         98
B){ 48         62 }       35      77        55      14      35        98
C){ 35        48        62 }       77       55      14       35         98 
D){ 35        48        62         77 }      55     14       35         98
E){ 35        48        55        62        77 }      14     35        98
F){ 14        35        48        55        62      77 }     35         98
G){ 14        35        35        48        55        62     77 }       98
H){ 14        35        35        48        55        62     77          98  } 
           ③  算法分析

从空间角度来看,它只需要一个辅助空间r[0]。

从时间耗费角度来看,主要时间耗费在关键字比较和移动元素上。

                        稳定的排序算法

  (2)折半插入排序

         ①基本思想

             把数组折成一半进行排序

         ②算法分析

采用折半插入排序法,可减少关键字的比较次数。每插入一个元素,需要比较的次数最大为折半判定树的深度,如插入第i个元素时,设i=2j,则需进行log2i次比较,因此插入n-1个元素的平均关键字的比较次数为O(nlog2n)。虽然折半插入排序法与直接插入排序法相比较,改善了算法中比较次数的数量级,但其并未改变移动元素的时间耗费,所以折半插入排序的总的时间复杂度仍然是O(n2)。

        ③ 步骤

{for(  i=2 ; i<=length ; ++i )

{x=r[i];low=1;  high=i-1;

 while (low<=high )                  /* 确定插入位置l */

{mid=(low+high) / 2;

       if ( x.key< r[mid].key   )   high=mid-1;

else   low=mid+1;}

for(  j=i-1 ; j>= low; --j )   r[j+1]=r[j];         /*  记录依次向后移动*/

r[low]=x;                                                      /* 插入记录 */

}

(3)表插入排序

    ①  基本思想

         表插入排序是采用链表存储结构进行插入排序的方法。表插入排序的基本思想是:先在待插入记录之前的有序子链表中查找应插入位置,然后将待插入记录插入链表。

    ②  步骤

intn=length;

r[0].next=n;  r[n].next=0;

for ( i=n-1 ; i>= 1; --i)

{   p=r[0].next;q=0;

 while( p>0 && r[p].key<r[i].key   )  /* 寻找插入位置*/

          {q=p;p=r[p].next;}

    r[q].next=i;  r[i].next=p;/*修改指针,完成插入 */

}

} /*  SLinkListSort  */

③算法分析

    

每插入一条记录,最大的比较次数等于已排好序的记录个数,即当前循环链表长度。总的比较次数为:

  (4)希尔排序

       ① 基本思想

先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量

   
=1(
   
<
   
…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

         ② 步骤

          input: an array a of length n with array elements numbered 0 to n − 1
  inc ← round(n/2)
  while inc > 0 do: 
  for i = inc .. n − 1 do: 
  temp ← a[i] 
  j ← i 
  while j ≥ inc and a[j − inc] > temp do: 
  a[j] ← a[j − inc] 
  j ← j − inc 
  a[j] ← temp 
  inc ← round(inc / 2.2)

 ③  算法分析

希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(

   
),希尔排序时间复杂度的下界是n*log2n。希尔排序没有 快速排序算法 快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的 数据排序 不是最优选择。但是比O(
   
)复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。

     (5)归并排序

         ①  基本思想

比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

②  步骤

如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11,;
逆序数为14;
③算法分析
归并排序是稳定的排序.即相等的元素的顺序不会改变.如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序.这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要.这也是它比快速排序优势的地方.
(6)堆排序
      ①  基本思想
将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区;
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];

由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

② 步骤

  初始化堆:将R[1..n]构造为堆;

 将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

③  算法分析

     

它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。

在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。

在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。

所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。

 

空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序是一种不稳定的排序方法


        

         

             

                

             

           

         

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值