目录
排序算法是一种用于对元素进行排序的方法。不同的排序算法具有不同的特点和适用场景。本篇博客将介绍常见的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序,并讨论它们的使用场景和时间复杂度。
常见排序算法可以分为两大类:

常见算法的复杂度分析
参考这里:所谓稳定性是指待排序的序列中有两元素相等,排序之后它们的先后顺序不变.假如为A1,A2.它们的索引分别为1,2.则排序之后A1,A2的索引仍然是1和2.(相同的记录在排序前后相对次序不发生改变,那么就是稳定的排序)
对于不稳定的 排序算法 ,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是, 排序算法是否为稳定的是由具体算法决定的 ,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。例如,对于如下冒泡排序算法,原本是稳定的排序算法,如果将记录交换的条件改成a[j]>=a[j+1],则两个相等的记录就会交换位置。
稳定性的意义:
1、如果只是简单的进行数字的排序,那么稳定性将毫无意义。
2、如果排序的内容仅仅是一个复杂对象的某一个数字属性,那么稳定性依旧将毫无意义(所谓的交换操作的开销已经算在算法的开销内了,如果嫌弃这种开销,不如换算法好了?)
3、如果要排序的内容是一个复杂对象的多个数字属性,但是其原本的初始顺序毫无意义,那么稳定性依旧将毫无意义。
4、除非要排序的内容是一个复杂对象的多个数字属性,且其原本的初始顺序存在意义,那么我们需要在二次排序的基础上保持原有排序的意义,才需要使用到稳定性的算法,例如要排序的内容是一组原本按照价格高低排序的对象,如今需要按照销量高低排序,使用稳定性算法,可以使得想同销量的对象依旧保持着价格高低的排序展现,只有销量不同的才会重新排序。(当然,如果需求不需要保持初始的排序意义,那么使用稳定性算法依旧将毫无意义)。
复杂度分析:
为什么进行复杂度分析?如何分析?
大O时间复杂度表示法:表示代码执行时间随数据规模增长的变化趋势,又称渐进时间复杂度。
大O空间复杂度表示法:表示代码执行所占的内存空间随数据规模增长的变化趋势,又称渐进空间复杂度 ps:给出随着增长规模的下界,具体流程:
算法的执行效率,粗略地讲就是算法的执行时间。下面的代码是求1,2,3...n累加的和。
cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum += i; // 两步操作
}
return sum;
}
从CPU的角度,这段代码的操作是,读数据 -> 运算 -> 写数据,如果每一个操作都是unit_time,第二行和第三行是一个unit_time,而第四行和第五行的for循环是2n个unit_time,加上return操作。时间复杂度就是2n+3,一般计算的时候会把常量省略,所以这个程序的时间复杂度就是n。所以就可以推断出,所以代码的执行时间T(n)与每行代码的的执行次数成正比。
引出重要概念:所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比,即T(n) = O(f(n))
ps:区分平均时间复杂度和均摊时间复杂度
排序算法是一种用于对元素进行排序的方法。不同的排序算法具有不同的特点和适用场景。本篇博客将介绍常见的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序,并讨论它们的使用场景和时间复杂度。
- 使用场景:适用于小型数据集的排序,或者用于教学目的。
- 时间复杂度:最好情况O(n),平均情况O(n^2),最差情况O(n^2)。
- public class BubbleSort {
- /**
- * 冒泡排序算法
- *
- * @param arr 待排序的数组
- */
- public static void bubbleSort(int[] arr) {
- int n = arr.length;
- for (int i = 0; i < n - 1; i++) { // 进行n-1轮冒泡
- for (int j = 0; j < n - i - 1; j++) { // 每轮从头到尾比较相邻元素并交换
- if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,则交换位置
- int temp = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = temp;
- }
- }
- }
- }
- }
- 使用场景:适用于小型数据集的排序。
- 时间复杂度:最好情况O(n^2),平均情况O(n^2),最差情况O(n^2)
- public class SelectionSort {
- /**
- * 选择排序算法
- *
- * @param arr 待排序的数组
- */
- public static void selectionSort(int[] arr) {
- int n = arr.length;
- for (int i = 0; i < n - 1; i++) { // 进行n-1轮选择
- int minIdx = i;
- for (int j = i + 1; j < n; j++) { // 在剩余的元素中找到最小值的索引
- if (arr[j] < arr[minIdx]) {
- minIdx = j;
- }
- }
- int temp = arr[minIdx]; // 将最小值与当前位置的元素交换
- arr[minIdx] = arr[i];
- arr[i] = temp;
- }
- }
- }
- 使用场景:适用于小型数据集的排序或部分有序的数据集。
- 时间复杂度:最好情况O(n),平均情况O(n^2),最差情况O(n^2)。
- public class InsertionSort {
- /**
- * 插入排序算法
- *
- * @param arr 待排序的数组
- */
- public static void insertionSort(int[] arr) {
- int n = arr.length;
- for (int i = 1; i < n; i++) { // 从第二个元素开始,将其插入到已排序部分的正确位置
- int key = arr[i];
- int j = i - 1;
- while (j >= 0 && arr[j] > key) { // 将大于key的元素向后移动
- arr[j + 1] = arr[j];
- j--;
- }
- arr[j + 1] = key; // 插入key到正确的位置
- }
- }
- }
- 使用场景:适用于大型数据集的排序。
- 时间复杂度:最好情况O(nlogn),平均情况O(nlogn),最差情况O(n^2)。
- public class QuickSort {
- /**
- * 快速排序算法
- *
- * @param arr 待排序的数组
- * @param low 数组的起始下标
- * @param high 数组的结束下标
- */
- public static void quickSort(int[] arr, int low, int high) {
- if (low < high) {
- int pi = partition(arr, low, high); // 找到分区点
- quickSort(arr, low, pi - 1); // 对分区点左侧进行快速排序
- quickSort(arr, pi + 1, high); // 对分区点右侧进行快速排序
- }
- }
-
- /**
- * 划分数组并找到分区点
- *
- * @param arr 待划分的数组
- * @param low 数组的起始下标
- * @param high 数组的结束下标
- * @return 分区点的索引
- */
- private static int partition(int[] arr, int low, int high) {
- int pivot = arr[high]; // 选取最后一个元素作为基准值
- int i = low - 1; // 初始化i为分区点的前一个元素
- for (int j = low; j < high; j++) {
- if (arr[j] < pivot) { // 如果当前元素小于基准值,则将其放入左侧分区
- i++;
- int temp = arr[i];
- arr[i] = arr[j];
- arr[j] = temp;
- }
- }
- int temp = arr[i + 1]; // 将基准值放入正确的位置
- arr[i + 1] = arr[high];
- arr[high] = temp;
- return i + 1; // 返回分区点的索引
- }
- }
- 使用场景:适用于大型数据集的排序。
- 时间复杂度:最好情况O(nlogn),平均情况O(nlogn),最差情况O(nlogn)。
- public class MergeSort {
- /**
- * 归并排序算法
- *
- * @param arr 待排序的数组
- * @param left 数组的起始下标
- * @param right 数组的结束下标
- */
- public static void mergeSort(int[] arr, int left, int right) {
- if (left < right) {
- int mid = (left + right) / 2; // 计算中间元素的索引
- mergeSort(arr, left, mid); // 对左侧子数组进行归并排序
- mergeSort(arr, mid + 1, right); // 对右侧子数组进行归并排序
- merge(arr, left, mid, right); // 合并两个已排序的子数组
- }
- }
-
- /**
- * 合并两个已排序的子数组
- *
- * @param arr 待合并的数组
- * @param left 左侧子数组的起始下标
- * @param mid 中间元素的下标
- * @param right 右侧子数组的结束下标
- */
- private static void merge(int[] arr, int left, int mid, int right) {
- int n1 = mid - left + 1; // 计算左侧子数组的长度
- int n2 = right - mid; // 计算右侧子数组的长度
-
- int[] L = new int[n1];
- int[] R = new int[n2];
-
- System.arraycopy(arr, left, L, 0, n1); // 将元素复制到临时数组L和R中
- System.arraycopy(arr, mid + 1, R, 0, n2);
-
- int i = 0, j = 0;
- int k = left;
-
- while (i < n1 && j < n2) { // 逐个比较并合并元素
- if (L[i] <= R[j]) {
- arr[k] = L[i];
- i++;
- } else {
- arr[k] = R[j];
- j++;
- }
- k++;
- }
-
- while (i < n1) { // 将剩余的元素复制到原数组中
- arr[k] = L[i];
- i++;
- k++;
- }
-
- while (j < n2) {
- arr[k] = R[j];
- j++;
- k++;
- }
- }
- }