0、前言
排序算法我觉得是属于很基础但是又很重要的一部分,在今后的实际工作中肯定会经常遇到,而且排序算法能增加对数据排序的深入理解,自己实现过程中也提高自己动手能力,排序顾名思义就是给串数据给你,需要升序或者降序输出。但是今后工作中的数据会是百万千万级别的,这时候选择合适高效的排序算法是至关重要的。今天先从最基本也是经典的几种排序算法讲起。分别是:
冒泡排序
选择排序
插入排序
1、冒泡排序
冒泡排序:冒泡排序是最早接触,也是印象最深的一种排序方法。对于给定的一串数据,从数据最后面开始遍历,只要小于前面的数,就和前面数交换位置,这样遍历一遍数组后,会把数组中最小的一个数据移动到数组最上面,然后再从最后一个数据开始遍历,再次比较和前面数据大小关系。这样每次遍历数据都会把此次遍历过程中的最小数移动到最前面。示意图如下所示:
每次遍历过程中,都会把最小数上浮到最上面,貌似气泡上浮,故名冒泡排序。对于n个数据,需要遍历(n - 1)次数组,每次都会移动一个数到数组前面,所以平均下来时间复杂度是O(n^2)。代码实现如下所示:
private void bubbleSort(int[] array , int n) {
int len = array.length;
boolean flag = true;
for (int i = 1 ; i <= len && flag ; i ++) {
flag = false;
for (int j = len - 1 ; j >= i; j --) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
flag = true;
}
}
}
}
这里有个优化地方,使用一个标志flag判断余下数组是否已经排好序了,当余下数据已经是升序的时候,直接break跳出循环。排序的稳定性是指对于相等的两个数,排序后的相对前后位置是否会发生变化,相对前后位置没有发生变化即为稳定的。
冒泡排序时间复杂度是O(n^2),排序是稳定的。
2、选择排序
选择排序:遍历一遍数组,从中选择最小的数据,和第一个数据交换位置,然后再遍历余下数组选择最小的数据,再和第二个数交换位置,每次遍历都会选择当前最小的数,然后和前面交换位置,即每次遍历都会选择一个最小的数。时间复杂度也为O(n^2)。示意图如下所示:
每次遍历记录最小值的索引即可,然后与未排序的数组第一个元素交换位置。实现代码如下所示:
下面展示一些 内联代码片
。
private void selectSort(int[] array , int n) {
int len = array.length;
for (int i = 0 ; i < len ; i ++) {
//寻找后面最小的一个数的索引
int minIndex = i ;
for (int j = i ; j < len ; j ++) {
if (array[j] < array[minIndex])
minIndex = j;
}
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
时间复杂度是O(n^2),排序是稳定的。
3、插入排序
插入排序:插入排序即把当前数据插入到前面已经排好的数组中,插入即可。
将3和前面元素比较,只要比前面数据大的就交换位置,因为3肯定就在它的前面,但这样会频繁移动位置,所以可以先把3保存下来,只要前面数比3大,就移位到3位置上来,然后继续和前面数据比较,只要比3大的就移位。最后遇到不大于3数时停止,把3插入。
代码实现:
private void insertSort(int[] array , int n) {
int len = array.length;
for (int i = 1 ; i < len ; i ++) {
//把array[i]插入到前面合适位置
int temp = array[i];
int j;
for (j = i ; j > 0 && array[j - 1] > temp ; j --) {
array[j] = array[j - 1];
}
array[j] = temp;
}
}
遍历了n次数组,每一次都要把数据移动到正确位置,平均时间复杂度是O(n^2)。排序是稳定的。
4、总结
测试:编写一个随机产生n个元素的数组,并且计算三种算法对同一个数组的排序时间,比较各个算法性能。
产生数组和打印数组的方法:
private int[] generateRangeArray(int n , int rangeL , int rangeR) {
int[] res = new int[n];
for (int i = 0 ; i < n ; i ++)
res[i] = (int)(Math.random()*(rangeR - rangeL + 1))+rangeL;
return res;
}
//打印数组
private void printArray(int[] array , int n) {
int row = 0;
for (int i = 0 ; i < n ; i ++) {
System.out.print(array[i] + " ");
row++;
if (row%10 == 0)
System.out.println();
}
}
性能比较:
int n = 100000;
int[] array = sort.generateRangeArray(n , 5 , n*10);
//选择排序
long startTimeSelectSort = System.nanoTime();
sort.selectSort(selectArray, n);
//sort.printArray(res , n);
long endTimeSelectSort = System.nanoTime();
double timeSelectSort = (endTimeSelectSort - startTimeSelectSort)/1000000000.0;
System.out.println("选择排序时间是:" + timeSelectSort + "s");
//插入排序
long startTimeInsertSort = System.nanoTime();
sort.insertSort(insertArray , n);
//sort.printArray(array , n);
long endTimeInsertSort = System.nanoTime();
double timeInsertSort = (endTimeInsertSort - startTimeInsertSort)/1000000000.0;
System.out.println("插入排序时间是:" + timeInsertSort + "s");
//冒泡排序
long startTimeBubbleSort = System.nanoTime();
sort.bubbleSort(bubbleArray, n);
//sort.printArray(array , n);
long endTimeBubbleSort = System.nanoTime();
double timeBubbleSort = (endTimeBubbleSort - startTimeBubbleSort)/1000000000.0;
System.out.println("冒泡排序时间是:" + timeBubbleSort + "s");
测试结果
虽然这三种算法都是O(n^2)级别,在100000个数据测试下,选择排序和冒泡排序慢的很多。选择排序每次都要完全遍历一遍未排序的数组,这样会耗时,而冒泡排序不仅也要遍历数组,还有不断交换位置,所以冒泡排序会更加耗时。但是优化后的插入排序会快很多,使用暂存当前值,找的合适位置再插入的方法,性能上提高很多。所以在O(nlogn)情况下,插入排序效果最好。