序言
本文使用python实现了一些常用的排序方法。文章结构如下如下:
上述所有的排序均写在一个python自定义类中,作为成员函数。
排序方法详细介绍
直接插入排序
直接插入排序(Straight Insertion Sort)是一种最简单的排序方法,它的基本操作是一个值插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。如下图所示:
由上图可知若最初始的有序表即为数组的第一个元素。用python实现如下:
1 | def straight_insertion_sort(self, value_list): |
希尔排序
希尔排序(Shell’s Sort)又称“缩小增量排序”(Diminishing Incerement Sort),它也是一种数插入排序的方法,但在时间效率上较前面的排序方法有较大的改进。它的基本思想是:先将整个待排记录序列分割成若干个子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。如下图所示:
即根据增量将原序列分割成多个子序列进行直接插入排序。增量应不断减小,且最后一个增量为1。用python实现如下(其中用到的子函数见前文):
1 | def shells_sort(self, value_list): |
冒泡排序
冒泡排序的过程很简单。首先将第一个记录的关键字和第二个记录的关键字进行比较,若逆序(与需要的顺序相反),则将两个记录交换之,然后比较第二个记录和第三个记录的关键字,以此类推。为第一趟冒泡结束,接着对前$n-1$个记录继续进行上述的过程。这样重复的过程直至$n-1=1$结束。排序过程如下所示:
用python实现如下:
1 | def bubule_sort(self, value_list): |
快速排序
快速排序(Quick Sort)是对冒泡排序的的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。其排序思想如下:
首先任意选取一个记录(通常可选第一个记录)作为枢轴,然后按下述原则重新排列记录:将所有关键字较它小的记录都安置在它的位置之前,将所有关键字较它大的记录都安置在它的位置之后。一趟快速排序的具体做法是:设两个指针low和high,他们的初值分别为最低位置的下一个位置和最高位,设最低位置位枢轴的关键字为pivotkey,则首先从high所指位置起像前搜索找到第一个关键字小于pivotkey的记录和枢轴记录互相交换。发生了交换后才从low所指向的位置起向后搜索,找到第一个关键字大于pivotkey的记录和枢轴记录互相交换。重复这两步直至low=how为止
如下图所示:
特别要注意换方向的时机是发生了交换后,用python实现如下:
1 | def quick_sort(self, value_list): |
简单选择排序
选择排序的基本思想是:每一趟在$n-i+1(i=1,2,…,n-1)$个记录中选取关键字最小的记录作为有序序列中第$i$个记录。简单选择排序:通过$n-1$次关键字的比较,从$n-i+1$个记录中选出关键字最小的记录,并和第$i(1\leq i\leq n)$个记录交换之。如下图所示:
用python实现如下:
1 | def simple_selection_sort(self, value_list): |
堆排序
堆的定义如下:$n$个元素的序列$\left \{k_{1},k_{2}, …k_{n} \right \}$当且仅当满足一下关系时,称之为堆。
若将序列看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端节点均不大于(或不小于)其左、右孩子节点的值。由此,若序列是堆,则堆顶元素必为序列中的最小值(或最大值)。如下图所示:
至此,我们可以给出堆排序的过程:若在输出堆顶的最小值后,使得剩余$n-1$个元素的序列又建成一个堆,则得到$n$个元素中的次小值。如此反复执行,便能得到一个有序序列。
故整个堆排序可以大致分为两个过程:
- 将无序序列建成堆。
- 输出堆顶元素后,用类似建堆的方法调整堆。
如下两个图所示:
根据堆排序的特点总结出两点注意事项:
- 利用把堆看成完全二叉树的特点,用完全二叉树的性质解决算法问题
- 建堆的过程是从树种的最后一个非终端节点逆序开始调整的。
- 每调整一次需要检查前后是否依然保持堆的特征。
本文利用了二叉树的孩子兄弟表示法来生成二叉树(堆)的。代码如下:
1 | class __CldSibNode: |
归并排序
归并排序(Merging),“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。假设初始序列有$n$个记录,则可看成是$n$个有序的子序列,每个子序列的长度为1,然后两两归并,得到$\left \lceil \frac{n}{2} \right \rceil$个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为$n$的有序序列为止,这种排序方法称为2-路归并排序。算法的基本思想如下图所示:
其中两个子序列的合并大有学问,基本思想就是:分别在两个序列头设置指针,比较两个序列指针所指的值的大小,将满足要求的值提取出来形成新列表,并将指针右移。当其中一个指针指向结尾之后时,表示其中一个列表已取尽,接着直接在新列表尾部连接另一个列表。如下图所示:
用python实现如下:
1 | def merging_sort(self, value_list): |
基数排序
基数排序(Radix Sort)是利用了多关键字排序的思想,分为从低位关键字开始排序的最低位优先法(Least Significant Digit first)和从高位关键字开始排序的高位优先法(Most Signficant Digit first)。基数排序是借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法。
如对三位数(0~999,即有三个关键字)进行最低位优先法排序:首先以静态链表存储$n$个待排记录,并另表头指针指向第一个记录;第一趟分配对低位关键字个位数进行,初始化10个空队列,每个队列中记录关键字的个位数相等。第一趟收集是改变所有费控队列的队尾指针域,令其指向下一个非空队列的队头,重新将10个队列链接成一个链表。第二次、第三次分别对十位和百位进行同样的操作,直至排序结束。
如下图所示:
排序时有两点需要注意:
- 每完成一趟排序,要清空队列。
- 队列的连接要找到第一个不为空的队列作为头,和绕开所有空队列。
用python实现如下:
1 | def radix_sort(self, value_list): |
测试代码
编写测试代码和运行结果如下:
1 | if __name__ == '__main__': |
测试运行结果:
1 | original sequence: [758, 74, 857, 781, 831, 719, 489, 785, 405, 621, 165, 568, 915, 939, 886, 4, 966, 461, 385, 757, 263, 505, 793, 259, 107, 437, 296, 702, 240, 644] |
总结
各个排序效率见下图:
可以得出以下几个结论:
- 从平均时间性能而言,快速排序最佳。
- 堆排序适用于$n$较大的数据。
- 基数排序是稳定的,时间复杂度较大的简单排序方法也是稳定的。
- 稳定性是由方法本身决定的。
- 没有最好的排序方法,视情况而定。
源码链接
仅供参考。