本文的网课内容学习自B站左程云老师的算法详解课程,旨在对其中的知识进行整理和分享~
一.选择排序
动图演示
算法原理
- 基本思想是在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾,以此类推,直到所有元素均排序完毕。
算法步骤
- 初始状态
无序区为整个序列,有序区为空。
- 第1趟排序
在无序区中找到最小(大)元素,将其与无序区的第1个元素交换位置,此时有序区有1个元素,无序区有n-1个元素。
- 第2趟排序
在无序区中找到最小(大)元素,将其与无序区的第1个元素交换位置,此时有序区有2个元素,无序区有n-2个元素。
- 第i趟排序
在无序区中找到最小(大)元素,将其与无序区的第1个元素交换位置,此时有序区有i个元素,无序区有n-i个元素。
- 重复上述步骤直到无序区只剩下1个元素,排序完成。
时间复杂度
- 平均情况:时间复杂度为 𝑂 ( 𝑛^2 ) ,其中n是待排序序列的长度。
- 最好情况:时间复杂度为 𝑂 ( 𝑛^2 ) ,即使序列已经有序,也需要进行比较操作。
- 最坏情况:时间复杂度为 𝑂 ( 𝑛^2 ),当序列为逆序时,比较次数最多。
空间复杂度
- 空间复杂度为 𝑂 ( 1 ) ,因为只需要用到常数级别的额外空间。
稳定性
-
选择排序是不稳定的排序算法,例如序列[5(1), 8(2), 5(3), 2(4), 9(5)],经过选择排序后可能会变成[2(4), 5(3), 5(1), 8(2), 9(5)],原本相等的5(1)和5(3)的顺序发生了改变。
代码实现
package sort;
public class SelectSort {
// 数组中交换i和j位置的数
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 选择排序
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
// i ~ n-1,找到最小值,放到i位置
int minIndex = i;
// i ~ n-1范围上最小值的索引
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
public static void main(String[] args) {
int[] testArray = {5, 3, 4, 6, 1, 2};
selectionSort(testArray);
for (int num : testArray) {
System.out.print(num + " ");
}
}
}
二.冒泡排序
动图演示
算法原理
- 基本思想是通过重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来,直到没有相邻元素需要交换,也就是说该元素列已经排序完成
算法步骤
- 第1趟排序
比较第1个和第2个元素,如果第1个元素大于第2个元素,则交换它们的位置。
接着比较第2个和第3个元素,依此类推,直到比较第n - 1个和第n个元素(n是数列的长度)。
经过这一趟排序后,数列中最大的元素就被移动到了最后一个位置。
- 第2趟排序
对前n - 1个元素重复上述操作,经过这一趟排序后,数列中第二大的元素就被移动到了倒数第二个位置。
- 第i趟排序
对前n - i +1个元素进行比较和交换操作,每一趟都会把未排序部分中的最大元素移动到未排序部分的最后一个位置。
时间复杂度
- 平均情况:时间复杂度为 𝑂 ( 𝑛^2 ),其中 𝑛 是待排序数列的长度。因为在平均情况下,大约需要 (𝑛 ^ 2) / 2 次比较和交换操作。
- 最好情况:当数列已经有序时,时间复杂度为 𝑂 ( 𝑛 )。此时只需要进行一趟比较,就可以确定数列已经有序,不需要进行交换操作。
- 最坏情况:当数列是逆序时,时间复杂度为 𝑂 ( 𝑛^2 ) ,需要进行最多的比较和交换操作。
空间复杂度
- 空间复杂度为 𝑂 ( 1 ) ,因为冒泡排序只需要用到常数级别的额外空间来进行元素的交换操作。
稳定性
- 冒泡排序是稳定的排序算法。例如,对于数列 [ 3 ( 1 ) , 3 ( 2 ) , 1 , 2 ] (这里 3 ( 1 ) 和 3 ( 2 ) 表示两个值为 3 的不同元素),在排序过程中,如果两个相等的元素 3 ( 1 ) 和 3 ( 2 )相邻且顺序正确,它们不会被交换,所以排序后它们的相对顺序不会改变。
代码实现
package sort;
import static sort.SelectSort.swap;
public class BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int end = arr.length - 1; end > 0; end--) {
for (int i = 0; i < end; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
public static void main(String[] args) {
int[] testArray = {5, 4, 3, 2, 1};
bubbleSort(testArray);
for (int num : testArray) {
System.out.print(num + " ");
}
}
}
三.插入排序
动图演示
算法原理
- 基本思想是将未排序的数据插入到已排序的序列中,从而逐步构建有序序列。
算法步骤
- 从第二个元素开始,将其视为已排序序列的一部分。
- 取出下一个未排序的元素,将其与已排序序列中的元素从后向前进行比较。
- 如果未排序元素小于已排序元素,则将已排序元素向后移动一位。
- 重复步骤3,直到找到一个已排序元素小于或等于未排序元素,或者已到达已排序序列的开头。
- 将未排序元素插入到该位置。
- 重复步骤2-5,直到所有元素都被插入到已排序序列中。
时间复杂度
- 最好情况:当待排序序列已经有序时,每次插入操作只需比较一次,因此时间复杂度为(O(n))。
- 最坏情况:当待排序序列是逆序时,每次插入操作都需要将待插入元素与已排序序列中的所有元素进行比较,因此时间复杂度为(O(n^2))。
- 平均情况:平均情况下,插入排序的时间复杂度也为(O(n^2))。
空间复杂度
- 插入排序是一种原地排序算法,不需要额外的存储空间,因此空间复杂度为(O(1))。
稳定性
- 插入排序是一种稳定的排序算法。在插入排序过程中,当比较到相等元素时,会将新元素插入到相等元素的后面,从而保持相等元素的相对顺序不变。
代码实现
package sort;
import static sort.SelectSort.swap;
public class InsertSort {
public static void insertSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
for(int i = 1; i < arr.length; i++) {
//0 ~ i-1 已经有序了,新来的数是[i],向左看
for(int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
}