数组中的算法比较多,而排序算法又是我们工作中最常用到的算法之一了。我主要来分享冒泡排序、选择排序及插入排序这三种经典的排序方法。
冒泡排序算法应该算是排序算法中最简单、也最容易理解的算法了。冒泡排序由双层循环来遍历,外层循环用来控制排序的轮数,轮数一般为数组长度减1(即:array.length-1);而内层循环主要用于对比数组中每对相邻元素的大小,以确定是否交换位置,对比、交换相邻元素的次数随着排序轮数而减少。
int[] bubbleSort(int[] array) {
System.out.println("原数组是:" + Arrays.toString(array));
int tmp = 0; //临时变量
long startTime = System.currentTimeMillis(); //获取排序开始的时间
//最后一次循环只剩一个元素,不需要大小比较,因此比较的轮次是(array.length-1)
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - 1 - i; j++) { //每比较一轮后,对比 、交换的次数少一次
// 若后一元素小于前一元素则交换两者位置
if (array[j] > array[j + 1]) {
tmp = array[j]; // 把前一元素保存至临时变量中
array[j] = array[j + 1]; //把后一元素赋值给前一元素
array[j + 1] = tmp; // 把临时变量(即前一元素)赋值给后一元素
}
}
}
long endTime = System.currentTimeMillis(); //获取排序结束的时间
System.out.println("数组冒泡排序用时:" + ((endTime - startTime) * 0.001) + "秒。");
System.out.println("排序后数组是:" + Arrays.toString(array));
return array;
}
选择排序的基本思想是将指定排序位置(如最后一位、倒数第二位、倒数第三位…)与其他数组元素分别对比,如果满足条件就交换元素值(不是交换相邻元素,与冒泡排序有所区别)。与冒泡排序相比,选择排序的交换次数要少得多,排序速度上要快一些。
int[] selectSort(int[] array) {
System.out.println("原数组是:" + Arrays.toString(array));
int index=0; //指定位置的索引值
int tmp = 0; //临时变量
long startTime = System.currentTimeMillis(); //获取排序开始的时间
for (int i = 1; i < array.length; i++) {
index = 0; //将获得的上一轮最大值的索引后归零
for (int j = 1; j <= array.length - i; j++) {
if (array[j] > array[index]) {
index = j;
}
}
/**交换在array.length-i与index(当前一轮最大值的索引)位置上的两个元素
***/
tmp = array[array.length - i];
array[array.length - i] = array[index];
array[index] = tmp;
}
long endTime = System.currentTimeMillis(); //获取排序结束的时间
System.out.println("数组选择排序用时:" + ((endTime - startTime) * 0.001) + "秒。");
System.out.println("排序后数组是:" + Arrays.toString(array));
return array;
}
插入排序基本操作是将一个记录分为有序数列、待插入数列,然后将待插入数列依次插入到有序数列中对应的位置,最终得到一个有序的数列。
若这个待插入数列是的插入位置是有序数列的边界附近,且有序数列的长度已经比较长了,那么只单单为了插入这一个数列就将耗费较长的时间。因此这种排序方法较适用于数组元素较少的排序。
int[] insertSort(int[] array) {
System.out.println("原数组是:" + Arrays.toString(array));
long startTime = System.currentTimeMillis(); //获取排序开始的时间
int temp=0; //临时变量
for (int i = 1; i < array.length; i++) {
/**判断待插入元素是加入到当前有序数列后是否有序,
*如果无序则将待插入元素和当前有序数列合在一起重新排序。
如果有序则检测下一个待插入元素是加入到当前有序数列后是否有序
**/
for (int j = i; j > 0; j--) {
if (array[j] < array[j - 1]) { //将待插入元素和当前有序数列合在一起重新排序
temp = array[j - 1];
array[j - 1] = array[j];
array[j] = temp;
} else {
//跳出内层循环,准备检测下一个待插入元素是加入到当前有序数列后是否有序
break;
}
}
}
long endTime = System.currentTimeMillis(); //获取排序结束的时间
System.out.println("数组插入排序用时:" + ((endTime - startTime) * 0.001) + "秒。");
System.out.println("插入排序后数组是:" + Arrays.toString(array));
return array;
}
为了更好地对比以上三种排序算法的耗时长短,我先用含10000个随机数的数组来进行排序耗时测试。
//定义数组
int nums1[] =new int[10000];
int nums2[] =new int[10000];
int nums3[] =new int[10000];
public static void main(String[] cmds) {
Test test = new Test();
test.initArrays(); //调用生成随机数组的方法
test.bubbleSort(test.nums1); //调用冒泡排序方法
test.selectSort(test.nums2); //调用选择排序方法
test.insertSort(test.nums3); //调用插入排序方法
}
//生成随机数组的方法
public void initArrays(){
Random rd=new Random();
for(int i=0 ; i< nums1.length;i++){
nums1[i] =rd.nextInt(10000);
}
nums2= Arrays.copyOf(nums1, nums1.length);
nums3= Arrays.copyOf(nums1, nums1.length);
}
此条件下,从控制台的输出信息可以看出,冒泡排序耗时>插入排序耗时>选择排序耗时。
我再用int nums[]={10000,9999,9998…1}的数组来进行排序耗时测试。
//定义数组
int nums1[] =new int[10000];
int nums2[] =new int[10000];
int nums3[] =new int[10000];
public static void main(String[] cmds) {
Test test = new Test();
test.initArrays(); //调用生成随机数组的方法
test.bubbleSort(test.nums1); //调用冒泡排序方法
test.selectSort(test.nums2); //调用选择排序方法
test.insertSort(test.nums3); //调用插入排序方法
}
//生成随机数组的方法
public void initArrays(){
Random rd=new Random();
// for(int i=0 ; i< nums1.length;i++){
// nums1[i] =rd.nextInt(10000);
// }
for(int i =0; i<nums1.length;i++){
nums1[i]=10000-i;
}
nums2= Arrays.copyOf(nums1, nums1.length);
nums3= Arrays.copyOf(nums1, nums1.length);
}
此条件下,从控制台的输出信息可以看出,插入排序耗时>选择排序耗时>冒泡排序耗时。
从两种情况下的结果来看,数组元素的原始排列情况会影响排序的耗时(效率)。第一种情况与现实的常遇到的状况相符,一般的数组的原排列大多是无序随机的。
而第二种情况是最极端的情况,此时数组的原排列是完全反序,每个元素都需要交换位置。因此第一种情况的结果更具有参考性,选择排序法在这3种方法中具有较高的效率(即使在最极端的情况下—所有元素反序排列,选择排序法的效率也要优于插入排序法)。