冒泡排序
- 理解:在水里小泡泡往上面浮,大泡泡往水下沉,所以冒泡排序其实就是前一个和后一个比较,小的放前面,大的放后面.
- 原理:以
int[] arr = {2,3,4,1,9,7,5}
为例.
- 第一圈:
- 先数组中第一个元素(下标为0)和数组中第二个元素(下标为1)比较大小.
- arr[0] > arr[1],两者交换.
- arr[0] <= arr[1],两者不交换
- 然后比较数组第一个元素(下标为1)和数组第二个元素(下标为2)的大小.
- arr[1] > arr[2],两者交换.
- arr[1] <= arr[2],两者不交换
- …以此类推
- 第一圈结束后,数组中最后一个元素为当前数组最大值.
- 第二圈(不管第N-1个数):
- 先数组中第一个元素(下标为0)和数组中第二个元素(下标为1)比较大小.
- arr[0] > arr[1],两者交换.
- arr[0] <= arr[1],两者不交换
- 然后比较数组第一个元素(下标为1)和数组第二个元素(下标为2)的大小.
- arr[1] > arr[2],两者交换.
- arr[1] <= arr[2],两者不交换
- …以此类推
- 第二圈结束后,数组中倒数第二个为当前数组第二个最大值.
- …以此类推,共执行N次
代码
public class BubbleSort {
public static void main(String[] args) {
int[] array = new int[]{2,1,6,9,13,3,4,2};
System.out.println(array.length);
System.out.println(Arrays.toString(array));
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
private static void bubbleSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
for (int end = array.length - 1; end > 0; end--) {
for (int j = 0; j < end; j++) {
if (array[j] > array[j + 1]) {
swap(j, j + 1, array);
}
}
}
}
private static void swap(int i, int j, int[] array) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
- 分析:
- 数组中两两交换和数组寻址都是常数时间O(1)的.
- 第一个for一共执行
N
次,第二个for一共执行N-1
次,所以一共要执行N^2 + N
次. - 不要高阶项系数,不要低阶项,所以我们就可以评估冒泡排序的时间复杂度为
O(N^2)
. - 其空间复杂度为O(1),因为只用了一个变量没有开辟多余空间.
选择排序
- 理解:和冒泡排序相反,选择排序是第一个元素依次和后面的每一个元素相比,比后面的小就交换,所以结束第一圈后最小的数就放在了最前面…以此类推,就可以将数组从小到大排序好.
代码
public class SelectionSort {
public static void main(String[] args) {
int[] array = new int[]{7, 6, 8, 9, 1, 2, 7, 6, 4};
//排序前
System.out.println(Arrays.toString(array));
selectionSort(array);
//排序后
System.out.println(Arrays.toString(array));
}
//选择排序代码
private static void selectionSort(int[] array) {
//首先判断数组对象是否为空以及是否为单个元素数组
if (array == null || array.length < 2){
return;
}
//控制循环次数
for (int start = 0; start < array.length; start++) {
//注意这里的i只能取到倒数第二个数,防止i+1越界
for (int i = star t; i < array.length - 1; i++) {
//如果前一个比后面的大,就交换
if (array[start] > array[i + 1]) {
swap(start,i + 1, array);
}
}
}
}
private static void swap(int start, int i, int[] array) {
int temp = array[i];
array[i] = array[start];
array[start] = temp;
}
}
- 分析:
- 数组中两两交换和数组寻址都是常数时间O(1)的.
- 第一个for一共执行
N
次,第二个for一共执行N-1
次,所以一共要执行N^2 + N
次. - 不要高阶项系数,不要低阶项,所以我们就可以评估选择排序的时间复杂度为
O(N^2)
. - 其空间复杂度为O(1),因为只用了一个变量没有开辟多余空间.
插入排序(重点)
- 理解:以
int[] arr = {3,1,4,1,6}
为例,可以理解为斗地主嘛,你手上的牌都从小到大排好序了,拿到一张新的牌可不就得和前面已经拿了的牌一个一个对比嘛,小的放前面,大的放后面,对吧?
- 首先,第一个位置的数(下标为0)不动,去和第二个位置的数(下标为1)比较(即拿
3
和1
比较).
- 此时发现
3>1
,所以arr[0]和arr[1]的值交换. - 此时数组为[1,3,4,1,6].
- 然后用第二个位置的数(下标为1)去和第三个位置的数(下标为2)比较,(即拿
3
和4
比较)
- 此时发现
3 < 4
,所以不交换. - 此时数组为[1,3,4,1,6].
- 然后用第三个位置的数(下标为2)去和第四个位置的数(下标为3)比较,(即拿
4
和1
比较)
- 此时发现
4 > 1
,所以4
和1
交换位置,得到[1,3,1,4,6].
- 再将
3
和1
比较,发现3 > 1
,两者交换,得到[1,1,3,4,6]. - 再将
1
和1
比较,发现1 == 1
,所以不交换.
- …以此类推
- 当到数组末尾时结束,得到排序好的数组[1,1,3,4,6].
代码
public static void main(String[] args) {
int[] array = new int[]{3, 1, 5, 2, 7, 6, 1};
System.out.println(Arrays.toString(array));
insertionSort(array);
System.out.println(Arrays.toString(array));
}
private static void insertionSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
for (int i = 1; i < array.length; i++) {
for (int j = i - 1; j >= 0; j--) {
if (array[j] > array[j + 1]) {
swap(array, j);
}
}
}
}
private static void swap(int[] array, int j) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
- 分析:
- 首先数组寻址,交换位置这些时间复杂度都是
O(1)
这是没有问题的. - 和选择排序和冒泡排序不同的点是:前两个时间复杂度一定是
O(N^2)
,因为不管你数组有序或者无序,你都是要按照流程一个一个比较的,跟数据状况没关系的,但是插入排序不一样,插入排序和数据状况有关系,要分情况.
- 最好情况下的时间复杂度:
- 当数组本来就有序时,时间复杂度为
O(N)
,因为没有交换,就和遍历一次数组一样.
- 最差情况下的时间复杂度
- 当数组是逆序,而我要的是正序时(比如我要的是[1,2,3,4,5],但是你给的是[5,4,3,2,1]),时间复杂度为
O(N^2)
,每一次都要和前面所有的交换,(例如5
要交换4
次,4
要交换3
次这样子),这就形成了一个等差数列(联想到冒泡排序怎么形成等差数列的),所以评估其时间复杂度为O(N^2)
.
- 一般情况下的时间复杂度:
- 一般情况我们一律按照最差情况估计,所以我们一般说插入排序的时间复杂度为
O(N^2)
.