选择排序
基本思想:首先,从数组中找到最小的那个元素,将它和数组中的第一个元素交换位置(如果第一个元素就是最小元素,那么它就自己交换),然后再从剩下的元素中找最小的元素,将它和数组的第二个元素交换位置,如此循环往复,直到整个数组有序。
代码实现:
public static void sort(int [] a) {
int N = a.length;
for(int i = 0; i < N; i++) {
int min = i;
for(int j = i + 1; j < N ; j++)
if(a[j] < a[min])
min = j; // 找到数组中的最小的元素
exch(a, i, min); // 交换两个函数
}
}
private void exch(int[] a, int n, int m){
int temp = a[n];
a[n] = a[m];
a[m] = temp;
}
分析:
-
对于长度为 N 的数组,选择排序需要大约 N的二次方/2 次比较和 N 次交换
-
数据移动最小:每次交换都会改变两个数组元素的值,因此排序算法用了 N 次交换—交换的次数与数组大小是线性关系的。
-
原地排序,但不是稳定性的
-
时间复杂度:O(n^2);
选择排序算法运行时间与输入无关,对于有序的数组或主键全部相等的数组和一个元素随机排列的数组所用的排序时间一样长。
插入排序
基本思想:首先,将数组分为两个区间,一个有序区间与无序区间。刚开始时数组的第一个元素为有序区间,其余元素为无序区间。插入算法的核心思想:取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
代码实现:
public static void sort(int [] a) {
int N = a.length;
int i , j;
for(i = 1; i < N; i ++) {
int temp = a[i];
// temp 跟 a[j] 比较,如果小于a[j],将a[j]往后搬移,腾出位置,让 temp 插入
for(j = i - 1 ; j >= 0 && temp < a[j] ; j -- )
a[j + 1] = a[j]; // 将 a[j] 往后搬移,腾出位置
a[j + 1] = temp;
}
}
分析:
-
插入排序对于一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序进行排序要快的多。
-
时间复杂度:
- 最好的情况下,数组已经有序,只需要从头到尾遍历一遍数组,所以最好的情况下,时间复杂度为O(n)。
- 最坏的情况下数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,需要移动大量的元素,所以时间复杂度是O(n^2)
冒泡排序
基本思想:每次循环只操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
代码实现:
public class BubbleSort {
public static void sort(int [] a) {
int N = a.length;
for(int i = 0; i < N; i++) {
boolean flag = true;
for(int j = 0; j < N - i - 1; j ++) {
if(a[j] > a[j + 1]) {
swap(a, j, j + 1); // 交换两个元素。
flag = false;
}
}
// 没有发生交换,说明已经有序了,提前退出循环。
if(flag) break;
}
}
}
private void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
分析:
- 时间复杂度:
- 最好的情况是数组已经有序,遍历一遍数组就可以结束了,时间复杂度为O(n)。
- 最坏的情况使数组全部逆序,即 { 5,4,3,2,1},时间复杂度为 O(n^2)。
- 平均时间复杂度为 O(n^2);
总结:
对于三大基本算法,时间复杂度都是O(n^2)。并且都是实现其余高级算法的基础,希尔排序是基于插入排序的思想。
参考:
- 极客专栏《数据结构与算法之美》)