常规排序算法Java详解

来源:互联网 发布:windows安装python3.6 编辑:程序博客网 时间:2024/06/05 08:26

这篇文章主要介绍常规的排序算法,包括选择排序、插入排序、冒泡排序、希尔排序等。


关于快速排序的详解可参见:快速排序算法Java详解

关于堆排序的详解可以参见:堆排序算法Java详解

关于归并排序的详解可参见:归并排序算法Java详解

关于基数排序的详解可参见:基数排序算法Java详解


0 辅助函数

在所有排序中可能都要用到的辅助函数在这里首先列出来。

//交换private void exchange(int[] items, int a, int b) {int t;t = items[a];items[a] = items[b];items[b] = t;}//比较,然后交换。如果items[b]<items[a],则交换这两个值private void compexch(int[] items, int a, int b) {if(items[b] < items[a])exchange(items, a, b);}

1 选择排序

选择排序是最简单的排序算法。首先,选出数组中最小的元素,将它与数组中第一个元素进行交换;然后找出次小的元素,并将它与数组中第二个元素进行交换。按照这种方法一直进行下去,知道整个数组完成排序。

例如如下的数组,前4个已经在正确的位置上了,后4个暂时无序:

当i=l+4时,启动内循环,找到了最小的一个元素15。然后将15和22调换,得到如下的数组。此时前5个事有序且处于正确位置的,后三个是无序的。


重复上述步骤,直到全部循环完成,整个数组也就变成有序的了。选择排序算法的Java实现如下:

public void selectionSort(int[] items, int l, int r) {for(int i=l; i<r; i++) {int min = i;for(int j=i+1; j<=r; j++) //找到从i+1到r中最小的一个值,该值的下标为minif(items[j] < items[min])min = j;exchange(items, i, min); //交换}}
选择排序有一个缺点,它运行的时间对文件中已有序的部分依赖较少,即没有很好的利用已经有序的部分。所以其最坏和最好的时间复杂度都是O(n^2)。


2 插入排序

我们在打牌时候使用的排序方式通常就是插入排序,摸牌时每次只考虑一张牌,将牌插入已经排好了序的牌的适当位置,插入后手中的牌还是有序的。插入排序的基本操作就是将一个记录插入已经排好序的有序表中,从而得到一个新的、记录数增1 的有序表。

例如如下的数组,前4个已经有序,后4个暂时无序:

在对14进行插入排序后,数组的情况如下,此时前5个是有序的,后3个是无序的。


插入排序的Java代码实现如下:

public void insertionSort(int[] items, int l, int r) {for(int i=r; i>l; i--) compexch(items, i-1, i);//上面是:将最小的元素挪到最前面的位置,将它作为“观察哨关键字”,这样可以使后面的循环更快for(int i=l+2; i<=r; i++) {int j = i;int v = items[i];while(v < items[j-1]) { //元素一直后移items[j] = items[j-1];j--;}items[j] = v;}}
由上述代码可以看到,在数组基本有序或完全有序时可以保证时间复杂度为O(N)。而在最坏的情况下(数组逆序),时间复杂度为O(N^2)。其平均时间复杂度为O(N^2)。

3 冒泡排序

冒泡排序的思路非常简单:遍历文件,如果近邻的两个元素大小顺序不对,就将两者交换,重复这样的操作指导整个文件排好序。

对于l到r-1之间的值,在内部循环过程中,逐渐将较小的值“冒”到前面。在外循环遍历的过程中,前i个元素已经处在了正确的位置上了。

例如如下的数组,前4个已经在正确的位置上了,后4个暂时无序:

当i=l+4时,启动内循环,j=r。先15和16判断,15<16,不用调换位置;然后j--,20和15判断,20>15,调换位置:

再j--,15和22判断;22>15,调换位置:

当再j--时,j与i重合,内部循环结束,15已经“冒”到了前端,此时前5个元素的位置是正确的,后三个无序。


重复上述循环,即可完成冒泡排序。冒泡排序的Java实现代码如下。

public void bubbleSort(int[] items, int l, int r) {for(int i=l; i<r; i++) {for(int j=r; j>i; j--)compexch(items, j-1, j);}}

虽然算法思路简单,但是冒泡排序的执行速度要比插入排序或选择排序要慢,所以在实际应用中并不是非常多。


4 希尔排序

希尔排序又称“缩小增量排序”,它也是属于插入排序类的方法,但是在效率上较插入排序有较大改进。对于直接插入排序,其时间复杂度为O(n^2),但是当待排序记录的顺序正确时,其时间复杂度可提高至O(n)。希尔排序的基本思想是:先将整个待排序记录序列分隔成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

例如如下的数组,一开始时分成以5为间隔划分子序列{R0,R5},{R1,R6},...。然后对每个子序列进行插入排序,得到一趟排序结果。然后在对一趟排序结果以3为间隔划分子序列,并对每个子序列进行插入排序。最后以1为间隔划分子序列(以1为间隔即不划分)进行一次插入排序。

希尔排序的Java实现代码如下。

public void shellSort(int[] items) { //时间复杂度:O(n^1.3)//依次将数组分隔成5个子序列、3个子序列和1个子序列(1个子序列其实就是不划分)int dlka[] = {5, 3, 1};for(int i=0; i<dlka.length; i++)shellInsert(items, dlka[i]); //增量为dlka[i]的希尔插入排序}//对数组items进行一趟希尔插入排序。该排序方式与直接插入排序相比,不同之处在于://前后记录位置的增量不是1,而是dkprivate void shellInsert(int[] items, int dk) {int i, j, t;for(i=dk; i< items.length; i++) {if(items[i] < items[i - dk]) { t = items[i]; //暂存for(j=i-dk; j>=0; j-=dk) {if(items[j] > t)items[j + dk] = items[j]; //记录后移elsebreak;}items[j + dk] = t; //插入}}}
希尔排序的时间复杂度为O(n^1.3)(根据《数据结构(C语言版)》)。需要注意的是,增量dlka中最后一个值必须是1,且所有的增量必须都是质数.


5 各类排序的比较

下面以表格形式从平均时间、最坏情况、辅助存储和稳定性的角度,对各种内部排序方法进行比较。

上表中:

关于快速排序的详解可参见:快速排序算法Java详解

关于堆排序的详解可以参见:堆排序算法Java详解

关于归并排序的详解可参见:归并排序算法Java详解

关于基数排序的详解可参见:基数排序算法Java详解


全文完。转载请注明出处。


参考文献

1. 严蔚敏,数据结构(C语言版),清华大学出版社.

2.Robert Sedgewick,算法:C语言实现,机械工业出版社.


0 0