1 前言
排序再算法中是一个很重要又很基础的工作,在对数据的处理过程中,排序往往是第一步。排序算法很多,评价一个排序算法好不好也有很多标准。例如时间复杂度,空间复杂度,稳定性等。今天先来介绍三种比较基础的排序算法,它也是很多高级排序算法的基础。他们是选择排序,冒泡排序,插入排序。
选择排序,冒泡排序,插入排序的时间复杂度都是O(n^2),空间复杂度都是O(1)
2 选择排序
选择排序就是每次遍历时从余下的数组中选择一个最小的数组来排在前面
例如,第一轮从0…n-1选择一个最小的排在a[0],第二轮从1…n-1选择一个最小的排在a[1]处,依次类推。代码如下:
/**
* @author Created by qiyei2015 on 2018/3/14.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 选择排序
*/
public class SelectionSort extends BaseSort{
/**
* 依次从数组中选择最小的数排在最前面
* @param array
*/
@Override
public void sort(Comparable[] array) {
for (int i = 0 ; i < array.length ; i++){
int min = i;
//比较a[i]与之后的数谁更小,更新最小的值
for (int j = i + 1 ; j < array.length ; j++){
//找到本次循环最小的数 [i+1,length-1]中最小的数
if (less(array[j],array[min])){
min = j;
}
}
exch(array,i,min);
}
}
}
由于a[i]与a[i]本身不需要比较,因此j从i+1开始
选择排序要进行O(n^2)次比较和O(n)次交换。
优化点:
在交换前,判断如果i == min则不进行交换。
3 冒泡排序
冒泡排序就是从a[0]到a[n-2]依次比较相邻的两对元素,如果后者比前者小,就交换位置。这样第一轮就会把最大的元素排在最后面。第二轮会把第二大的元素排在倒数第二的位置上。
代码如下:
/**
* @author Created by qiyei2015 on 2018/3/19.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 冒泡排序,依次比较两个元素,最大的就慢慢靠后
*/
public class BubbleSort extends BaseSort{
/**
* 比较相邻的元素。如果第一个比第二个大,就交换他们两个
* @param array
*/
@Override
public void sort(Comparable[] array) {
for (int i = 0 ; i < array.length ; i++){
//每一次会排好一个数,因此需要减i
for (int j = 0; j < array.length - i - 1;j++){
if (less(array[j+1],array[j])){
exch(array,j,j+1);
}
}
}
}
}
每一对数是a[j] a[j+1],j从0 开始,没一轮会排好一个数,因此j < length-1 - i。将较大的数放在后面。
也可以从后往前,如果后者比前者小,就把两者交换。
/**
* 从后往前扫描,每次把最大的元素排好序
* @param array
*/
public void sortBackToFace(Comparable[] array) {
for (int i = array.length - 1 ; i >= 0 ; i--){
//每一次会排好一个数,因此需要减i
for (int j = 0; j <= i - 1;j++){
if (less(array[j+1],array[j])){
exch(array,j,j+1);
}
}
}
}
这是从后往前扫描,比较相邻的两对元素,如果后者比前者小,就交换两者,这样每次总能把最大的元素排在最后面。
4 插入排序
插入排序是一种很经典的排序,它的思想就是依次遍历,对每一个数插入到前面已经排好序的数组中。例如a[0..i-1]已经有序,那么只需要把a[i]插入到前面已经有序的数组中即可。代码实现如下:
/**
* 插入排序,将a[i] 插入到a[0] ---- a[i-1]合适的位置中
* @param array
*/
@Override
public void sort(Comparable[] array) {
int length = array.length;
for (int i = 1 ; i < length ; i++){
//处理a[0] ---- a[i-1]数组,找到a[i]可以插入的位置
for (int j = i ; j > 0 && less(array[j],array[j-1]); j--){
exch(array,j,j-1);
}
}
}
因为对于1个元素,我们默认它就是有序的,这样遍历就可以从1开始遍历,内循环,依次比较a[i] a[i-1] 和a[i-1] a[i-2]这几组。如果后者较小,就依次交换。有序前面数组已经是有序的,这样就能找到a[i]合适的位置。
优化点:
1 优化交换过程,这是因为交换的过程往往比较耗时,我们可以直接找到a[i]在a[0..i-1]处合适的位置,然后将数组后移一个位置,在将a[i]放到合适的位置,实现如下:
/**
* 插入排序优化 少交换,不用每次都从j-1到i处交换,只用在前面找到合适的位置,最后赋值
* @param array
*/
public void sortOpt(Comparable[] array) {
int length = array.length;
for (int i = 1 ; i < length ; i++){
int j = i;
Comparable temp = array[i];
//不用每次都交换,直接找到合适的位置,然后交换
for (; j > 0 && less(temp,array[j-1]); j--){
//后移一个位置
array[j] = array[j-1];
}
//找到a[i]合适的位置j了
array[j] = temp;
}
}
首先用一个临时变量来存储a[j],然后比较a[j]与a[j-1],如果a[j]更小,则将a[j-1]后移一位赋值给a[j]。这样循环下去,最终会找到a[j]合适的j。最后只需要将a[j]赋值回去即可。
插入排序是一种稳定的排序,插入排序也是很多高级排序的最后一步。