排序算法学习整理

  排序,定义上来说就是重新排列表中元素,令表中的元素满足按关键字有序的过程。

  排序的稳定性:一个待排序表中有两个元素a和b,它们两个具有一样的关键字key。在排序前,a在b前面;若使用某个排序算法后,a仍然在b前面,则这种排序是稳定的。

  在实际操作中,有时候待排序的数据过大,或者其他的一些原因,要排序的数据并不都在内存中,这样的排序就叫做外部排序;反之都在内存内排序,称为内部排序。

  按照大致的操作方式,常见的排序算法可以被分为以下几类:插入排序、交换排序、选择排序、归并排序和技术排序五类。插入排序中主要存在以插入方式进行排序的操作,交换排序则是以交换两个元素来进行排序,归并和基数较为特殊。

  在评价一个算法前,有这些点需要进行关注:

  1)元素的初始位置;不同的算法对元素初始顺序敏感度不同,有的算法不关注初始位置,不管元素怎么排它都要按规定比较、移动,有的排序算法则更适合处理已经差不多有序的表。

  2)元素的个数;

  3)算法的空间复杂度和时间复杂度 ;

  4)算法是否稳定。有的算法在计算的时候会改变元素已有的相对顺序。

  5)元素的存储结构。有的算法可适应顺序存储和链表,有的算法不适用于链表。

  6)元素的排序过程。有的算法每次必能排出部分有序的队列,有的算法则要到最后才能得到有序表。

此处均默认从小到大排列,堆取小根堆

 

1.插入排序

  基本思想:把一个要排序的元素插入到已经排序好的部分。可以分为直接插入、折半插入和希尔排序。

 

  1)直接插入算法:在一开始便将第一个元素视为有序,从第二个开始,逐个向前对比,找到合适的位置就插入。

  举例:表中前3个元素已经排列有序,排列第四个元素时,从第3个开始对比,若小于第三个数,则插入到第三个元素前;若大于,继续向前对比;直到插入到最前端。

  在使用这种算法时,因为插入的时候其他数据需要腾位置,所以需要常数个辅助空间,空间复杂度o(1)。时间取平均状态,时间复杂度o(n2)最好情况下每个元素已经有序,只比较一次,不用排列,时间复杂度只有o(n)。最坏情况元素逆序。

 

  2)折半插入排序

  基本思想:普通的插入每次要把待排序元素依次和每个已排序元素对比,折半排序则是以折半的形式和已排序元素做对比。相比于直接插入,它减少了元素对比的次数,元素移动的次数并未改变,时间复杂度还是o(n2)

 

  3)希尔排序

  基本思想:直插排序对已经有序的序列处理速度更快,对其进行修改,即得到了希尔排序,又叫缩小增量排序。具体操作:取一个步长d,以此步长将整个表分为多个小表,例如步长为5,那么第1、6、11个数为一组,第2、7、12个为一组。在组内进行直接插入排序。经历完一轮后称为一趟排序。在一趟排序完成后,选择更小的步长e<d,继续进行排序,直到步长等于1。即所有的元素都在一组,再进行一次直插排序。

  在空间上,同插入排序,只需要常数个辅助空间,空间复杂度o(1)。时间上则较为复杂,它的时间复杂度依赖于增量,某个范围内时间复杂度为o(n1.3),最坏情况下为o(n2)。在稳定性上,由于相同关键字可能会被分到不同子表,所以顺序也可能被打乱,希尔排序是不稳定排序。此外, 希尔排序只适用于顺序存储

 

  在做题考试中,可能会遇到给出表,认定已经进行了n趟排序,要求指出这是什么类型算法的情况,要注意,直接插入和折半插入,每次都能产生一个有序元素

--------------------------

2.交换排序

  基本思想:根据两个元素关键字的比较来直接交换两个元素的位置。

  

  1)冒泡排序

  基本思想:从前往后(或从后往前)依次对比两个元素之间的关键字并交换位置。第1个和第2个比,第2个和第3个比。。。。这样便称为一趟排序。下一趟排序从第2个元素开始,继续互相比较。。。每次完成这样的一趟排序,都能有一个最小/最大元素被排到正确位置。因为元素会像气泡一样逐渐上升,因此称为冒泡排序。是比较简单常见的排序。并且它是一种稳定排序

  在交换过程中需要一个辅助空间做临时存储,空间复杂度o(1)。对元素初始顺序不敏感。如果在算法里加一个标志位,用于判断本趟排序是否有元素被移动;当遍历一次后标志位还是未移动,那么就说明已经有序。在标志位帮助下,最好情况时时间复杂度为o(n)。平均情况和最坏情况下时间复杂度都是o(n2)

 

  2)快速排序

  基本思想:

  快速排序基于分治算法。在整个待排序表中,先选择一个元素作为基准(一般选首元素)并在首尾各放置一个指针i j,用一个辅助空间储存基准。此时首元素的位置就相当于一个可用的“空位”。

在进行排序时,j先向前移动,遇到一个小于基准的数,就将基准和指针j指向元素交换;接下来再移动指针i,遇到一个比基准大的数,再交换基准和指针i指向元素。再移动指针j....如此往复,直到i和j碰头。称为完成了一趟排序

初始:  5是基准   

    5    15   x   x   x   x   x   x   x   3

    i               j

首先,--j向前移动,3比5小,5和3交换位置,j前移。        (5可以取出来存储,把基准置空也可)

    3   15   x   x   x   x   x   x   x   5

    i             j

i++后移,15比5大,交换基准和15.

    3   5   x   x   x   x   x   x   x   15

      i             j

  快速排序被称为快速排序,是因为在进行一趟排序后,一个表就会被分为两个子表。在下一趟排序中,可以同时对两个子表进行排序。直到最后每个子表只剩下一个元素,即有序。

  快速排序的算法可以被写为递归的形式,需要借用一个栈来存储递归信息,最好和平均情况下空间复杂度为0(log2n),最坏情况为o(n)。在时间上,未排序表中元素的顺序会影响性能,最糟糕的情况就是每次切出来n个元素和0个元素的表,这也就意味着快速排序不适合处理已经有序和逆序的表,最坏情况下,时间复杂度为o(n2),最好情况和平均情况下,时间复杂度为o(nlog2n)。

  同时,快速排序在排序过程中不会生成有序表,但是每一趟排序后,基准元素都会被放到正确的位置

 

------------------------------

3.选择排序

  基本思想:每一趟都在未排序元素中选取一个最小值放入有序部分,需要做n-1趟。

  1)简单选择排序

  思路:直接进行选择。空间复杂度0(1),时间复杂度o(n2),不随着元素顺序改变。最好情况下,数据已经有序,只对比,不用移动。并且元素比较的次数和元素初始顺序无关。由于每次要选择一个元素,所以元素间顺序可能改变,属于不稳定排序。

 

  2)堆排序

  基本思路:将表视为一个完全二叉树,那么这张表的n/2处一定就是叶节点和非叶节点的分界线。将这个表按要求建立成小根堆,每次堆顶所要求的当前最小元素。每次取出要求元素后继续处理,就能获得所需的有序表。

   (如图所示为初始堆的建立,最后的元素变红表示不再参与排序,可视为取出)

 

  对于堆排序,有两个问题需要处理:一开始的初始堆如何建立?在插入、去除元素后如何重新排序?

  初始建堆时,自下而上,从右向左(看序号就能理解,其实就是从小到大),从最后一个树开始对比。若父节点大于子节点,交换两者。然后继续处理右侧、上方的树。值得注意的是,在构建初始堆时,修改非底层子树的节点,可能会破坏底部已经排序好的树,因此在处理非底层子树时,还要把已经处理过的子树再检查一遍。

  除过初始建堆,堆排序也支持加入和删除元素。插入或删除元素一般在堆的末尾,取出一般是在堆顶。在这里需要注意的是,建立初始堆和插入元素的时间复杂度不同,插入元素时只需要调整新元素直到根节点的这一条子树,比较次数 最多为log2n(向下取整)。

  对于堆排序,如果需要排序所有元素,一般会把已排序部分放在表的末尾。在空间上只需要常数个辅助空间,空间复杂度为o(1)。建堆时间为o(n),还需要n-1次排序对其进行调整(堆排序一开始只保证堆顶满足需求),每次调整为o(n),所以堆排序在最好/最坏/平均下时间复杂度为o(nlog2n)。此外,堆排序可能会把后面的同关键字元素排到前面,所以它也是一种不稳定算法。

 

------------------------------

4.归并排序

  基本思想:将多个小的有序表合并为一个完整的有序表。

  操作:待排序表有n个元素,可以视为n个长度为1的有序表。两两归并,直到完全合并。这种行为称为2路归并排序。

值得一提的是,在对两个小有序表进行排序时,采用了类似“车轮战”的比较方法:先把它们都复制到辅助数组B中(足够大)。记录两个小表的长度和表头。每次从两个小表中各取出一个元素,小的元素写入原表,大的继续保留,下一个元素继续来比,直到一个小表完全比完,剩下的直接放回原表,这样就完成了两个子表的合并(不是一趟,仅仅是两个子表)。这个过程也用到了分治的思想。

  在合并子表时,辅助空间也为n,所以空间复杂度o(n)每趟归并都会将n个表合并为n/2个表,用时o(n),总共要log2n次归并,所以时间复杂度为o(nlog2n)。此外,它是稳定算法。

初始: 49  38  65  97  76  13  27      7组

一趟: 38  49  65  97  13  76  27      4组

二趟: 38  49  65  97  13  27  76      2组

三趟: 13  27  38  49  65  76  97      1组

 

-------------------------------------

5.基数排序

  基本思想:与人的想法较为接近,直接对比某一位的值。例如:43的十位是4,那么肯定大于38.

  在实际排列时,先从最低位开始排列,一直排到最高位。在升高的过程中,已经排列好的相对位置不改变。

  空间效率:需要r个队头指针和队尾指针,并且会反复利用,因此空间复杂度为o(r)。在时间上,需要d趟分配和收集,分配需要o(n),收集需要o(r)。所以基数排序的时间复杂度为o(d(n+r))。并且它与序列的初始位置无关。是稳定排序。

   (如图,即为将10个数按照个位值排列的结果)

 

 

(注:分配是指把元素挂在0 1 2 3等指针后,收集是指把这些元素重新组合为表)

 

---------------------------------------------------------------

各种排序算法间的对比:

  直插排序、选择排序、冒泡和希尔排序都只需要常数个辅助空间用于元素的转移,快速排序中由于存在递归,所以需要一个栈来暂存数据,一般情况下为o(log2n),最坏情况下为o(n)。对于归并排序,它需要大量空间用于两个子表的合并,空间复杂度为0(n)。

    各种排序算法的性质

算法类型 时间复杂度 空间复杂度 稳定性
最好 平均 最坏
直接插入 o(n) o(n2) o(n2) o(1) 稳定
折半插入 o(n) o(n2) o(n2) o(1) 稳定
希尔排序 难以认定 o(1) 不稳定
简单选择 o(n2) o(n2) o(n2) o(1) 稳定
快速排序 o(nlog2n) o(nlog2n) o(n2) o(log2n)-o(n) 不稳定
冒泡排序 o(n) o(n2) o(n2) o(1) 稳定
堆排序 o(nlog2n) o(nlog2n) o(nlog2n) o(1) 不稳定
2路归并 o(nlog2n) o(nlog2n) o(nlog2n) o(n) 不稳定
基数排序 o(d(r+n)) o(d(r+n)) o(d(r+n)) o(r) 稳定

排序趟数受元素初始顺序影响的算法:直插排序、简单选择排序、基数排序                      (交换类算法都会受影响)

元素的移动次数受元素初始顺序影响的算法:基数排序

 

 

交换类排序(如冒泡)如何受元素位置的影响:在算法中加入标志位,如果某趟排序后标志位还是提示无移动,那就说明已经有序,故影响了实际排序次数。

posted @ 2021-06-04 22:06  namezhyp  阅读(155)  评论(0编辑  收藏  举报