排序就是将一组对象按照某种逻辑顺序重新排列的过程。排序在很多地方可以用到,比如在淘宝购物时,完全可以根据自己的需要对搜索到的商品进行排序。在计算时代早期,大家普遍认为30%的计算周期都用在了排序上,但今天这个比例降低了,原因之一就是如今的排序算法更加高效,而并非排序的重要性降低了,因此熟悉一些排序算法是很重要的。最近几天我开始学习排序算法,并用java语言实现了一遍,并且比较了冒泡、选择、直接插入三种排序算法的性能。
注:下面所列出的需要排序的数组元素都实现了Comparable接口,可以重新实现compareTo方法从而选择所需要排序的关键字。
一、冒泡排序
冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录之间关键字,如果反序则交换,直到没有反序的记录为止。具体实现代码如下,里面有详细注释。
public class Bubble {
public static void sort(Comparable[] a) {
int N = a.length;
for (int i = 0; i < N; i++) {
for (int j = N - 1; j > i; j--) { //注意j是从后往前循环
if (comp(a[j], a[j - 1]))
exch(a, j, j - 1); //如果后者小于前者,则需要交换位置(默认是升序)
}
}
}
//比较两个元素的大小,若是v小于w返回true,否则返回false
public static boolean comp(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
//交换两个元素的位置
public static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//打印排序好的数组元素
public static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
二、选择排序
选择排序算法的思想是:首先,找到数组中最小的那个元素;其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换);再次,在剩下的元素中找到最小的元素。将它与第二个元素交换位置。如此往复,直到将整个数组排序。具体实现代码如下:
public class Selection {
public static void sort(Comparable[] a){
int N = a.length;
for(int i = 0; i < N; i++){
int min = i; //记录最小的位置(一开始认为此元素最小)
for(int j = i+1; j < N; j++){
if(comp(a[j], a[min])) //循环寻找最小的元素,并记录其位置
min = j;
}
exch(a, i, min); //将其放到i的位置
}
}
public static boolean comp(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
public static void exch(Comparable[] a,int i, int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void show(Comparable[] a){
for(int i = 0; i < a.length; i++){
System.out.print(a[i] + " ");
}
}
}
三、直接插入排序
直接插入排序的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表包含一个元素,无序表中包含n-1个元素。排序过程就是每次从无序表中取出一个元素,将其插入到有序表中的合适位置,使之成为新的有序表,重复n-1次即可完成排序过程。排序过程类似打扑克牌时整理手中牌的过程。具体实现代码如下:
public class Insertion {
public static void sort(Comparable[] a){
int N = a.length;
for(int i = 1; i < N; i++){
Comparable temp = a[i]; //从底牌抽出的牌准备往手里面放牌(按一定顺序)
int j = i; //j表示移动索引
if(comp(temp, a[j-1])){
while(j >= 1 && comp(temp, a[j-1])){
a[j] = a[j-1]; //元素往后移动一格
j--; //索引往前走,准备下一次比较
}
}
a[j] = temp; //此时temp比之前的元素都大,因此其索引为j
}
}
public static boolean comp(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
public static void exch(Comparable[] a,int i, int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void show(Comparable[] a){
for(int i = 0; i < a.length; i++){
System.out.print(a[i] + " ");
}
}
}
四、性能比较
Java语言中Double类型实现了comparable接口,所以我选择随机产生10000个Double类型的数据,分别采用三种算法进行排序。为了不受输入数据的影响,每个排序算法总共做100次这样的排序,然后统计三种算法的平均排序时间。下面附上截图:
根据上图可以看出直接插入排序是最快的,冒泡排序是最慢的。下面来看一下三种算法的时间复杂度:
(1)冒泡排序:O(n^2)
(2)选择排序:O(n^2)
(3)直接插入排序:O(n^2)
虽然时间复杂度都是平方级别的,但是冒泡排序一直在交换元素,选择排序对于部分有序的数组来说是不利的,因为它每一个循环都是和数组中所有的元素比较从而选择出最小的元素。所以直接插入排序最快,选择排序次之,冒泡排序最差。