从0开始的算法(数据结构和算法)基础(八)

      说了这么久的数据结构,理论性比较强,下面我们来进入算法部分,运用之前学的数据结构来实现算法。今天的主体部分是排序,难度不大。

排序

      排序的算法是比较简单实用的算法,也是很多的算法的基础。也分很多种,可以根据时间空间难度不同的,有序数据能够被更高效地查找、分析和处理

选择排序

      选择算法是一个时间复杂度O(n2),空间复杂度是O(1),运行时间比较长。其主要思想是每次从未排序的部分中选择最小(或最大)的元素,将其放在已排序部分的末尾。以下是选择排序的详细步骤:

选择排序的步骤

  1. 初始状态

    • 给定一个待排序的数组或列表 arr,长度为 n
    • 将数组分为两部分:已排序部分和未排序部分。最初,已排序部分为空,未排序部分为整个数组。
  2. 外层循环

    • 遍历数组,从 arr[0]arr[n-2],依次执行以下步骤。
  3. 寻找最小值

    • 在未排序部分中(从当前索引 in-1),找到最小元素的索引 min_index
    • 具体地,从 arr[i] 开始,比较 arr[i]arr[i+1]、…、arr[n-1] 的值,记录下最小值的索引。
  4. 交换元素

    • 找到最小元素后,将其与未排序部分的第一个元素(即 arr[i])交换。
    • 这样,未排序部分的第一个元素变成了已排序部分的最后一个元素。
  5. 重复步骤 2 到 4

    • 对于剩下的未排序部分,重复上述过程,直到数组完全排序(即外层循环到达 n-2)。
  6. 完成排序

    • 当外层循环结束时,数组已被排序。
public class SelectionSort {

    // 方法:选择排序
    public static void selectionSort(int[] arr) {
        int n = arr.length;

        // 外层循环:逐步缩小未排序部分的范围
        for (int i = 0; i < n - 1; i++) {
            // 假设当前元素为未排序部分的最小值
            int minIndex = i;

            // 内层循环:找到未排序部分的最小元素
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }

            // 如果找到的最小元素不是当前元素,则交换
            if (minIndex != i) {
                int temp = arr[minIndex];
                arr[minIndex] = arr[i];
                arr[i] = temp;
            }
        }
    }

    //测试选择排序
    public static void main(String[] args) {
        int[] arr = {64, 25, 12, 22, 11};

        System.out.println("排序前的数组:");
        printArray(arr);

        selectionSort(arr);

        System.out.println("排序后的数组:");
        printArray(arr);
    }

    //打印数组
    public static void printArray(int[] arr) {
        for (int j : arr) {
            System.out.print(j + " ");
        }
        System.out.println();
    }
}

冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法,它的基本思想是通过多次遍历列表,不断地将相邻的元素进行比较并交换,使得每一轮遍历之后,最大的元素会“冒泡”到数组的末尾。这个过程会重复进行,直到整个数组排序完成。下面是对冒泡排序的详细解释:

冒泡排序的工作原理

  1. 初始状态

    • 假设我们有一个未排序的数组或列表,长度为 n
  2. 外层循环

    • 外层循环控制排序的轮数,共需要 n-1 轮,因为在最坏的情况下,所有元素都需要比较 n-1 次才能确定其最终位置。
  3. 内层循环

    • 在每一轮排序中,内层循环负责在未排序的部分中两两比较相邻的元素。
    • 如果当前元素比下一个元素大,则交换它们的位置,这样大的元素逐渐移动到列表的末尾(像气泡一样上升)。
    • 经过一轮比较后,最大的元素会被移动到数组的末尾。
  4. 逐步缩小范围

    • 每一轮排序后,已经排好序的部分不再参与后续的比较和交换,因此内层循环的范围逐渐缩小。
  5. 结束条件

    • 当不再需要交换时,排序结束。虽然算法设计上要求 n-1 轮比较,但一旦发现某一轮没有发生交换,就可以提前终止排序。

冒泡排序的时间复杂度

  • 最坏情况:O(n2) —— 当数组是逆序时,算法需要执行最多次数的比较和交换。

  • 最好情况:O(n) —— 当数组已经排序时,算法只需一次遍历即可结束。

  • 平均情况:O(n^2) —— 大多数情况下,需要执行比较次数与最坏情况相似。

    • 实现简单,代码容易理解。
    • 对于几乎已经排序好的数组,它可以在更少的时间内完成排序。

插入排序

插入排序(Insertion Sort)是一种简单且直观的排序算法,特别适合于小规模数据的排序。它的基本思想是将待排序的元素逐个插入到已经排好序的部分,直到整个序列有序。插入排序的工作方式类似于我们打牌时将新牌插入到已排序的牌中。

插入排序的工作原理

  1. 取第一个为基准值

    • 假设我们有一个未排序的数组,该数组的第一个元素可以被视为已经排好序的部分。
  2. 逐步插入

    • 从第二个元素开始,将其与已经排好序的部分进行比较。
    • 如果当前元素小于已排序部分的元素,就将已排序部分的元素向后移动一位,直到找到当前元素应该插入的位置。
  3. 插入元素

    • 将当前元素插入到找到的位置,继续处理下一个元素,直到所有元素都被插入到已排序部分中。

下面是使用 Java 实现的插入排序的完整代码示例:

public class InsertionSort {

    // 方法:插入排序
    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) {
                arr[j + 1] = arr[j];  // 将大于 key 的元素向后移动
                j--;
            }
            arr[j + 1] = key;  // 插入当前元素到找到的位置
        }
    }

    // 主方法:测试插入排序
    public static void main(String[] args) {
        int[] arr = {5, 2, 9, 1, 5, 6};

        System.out.println("排序前的数组:");
        printArray(arr);

        insertionSort(arr);

        System.out.println("排序后的数组:");
        printArray(arr);
    }

    // 辅助方法:打印数组
    public static void printArray(int[] arr) {
        for (int j : arr) {
            System.out.print(j + " ");
        }
        System.out.println();
    }
}

代码说明

  1. insertionSort 方法

    • 接收一个整数数组 arr,并对其进行排序。
    • 外层循环从 1 开始遍历到数组的最后一个元素,key 变量存储当前待插入的元素。
    • 内层循环向后移动已排序部分的元素,直到找到 key 应该插入的位置。
  2. 数组元素移动

    • 当已排序部分的元素大于 key 时,将该元素向后移动一位,以便为 key 腾出插入的位置。
  3. 插入元素

    • 在内层循环结束后,j + 1 就是 key 应该插入的位置。
  4. printArray 方法

    • 辅助方法,用于打印数组的内容。

插入排序的特点

  • 时间复杂度

    • 最坏情况:O(n^2)(当数组逆序时)。
    • 最好情况:O(n)(当数组已经排序时)。
    • 平均情况:O(n^2)
  • 空间复杂度O(1),因为它是原地排序算法。

  • 稳定性:插入排序是稳定的排序算法,即相等元素的相对顺序不会改变。

插入排序在小规模数据集上表现良好,并且在数据部分有序时效率较高。

快速排序

快速排序(Quicksort)是一种高效的排序算法,最早由英国计算机科学家托尼·霍尔提出。它采用了分治策略,将一个数组分成两个子数组,并递归地对这两个子数组进行排序。快速排序在平均情况下具有非常好的性能,其时间复杂度为 (O(n log n))。分治算法就是将问题进行切片,牺牲空间换取时间。

快速排序的基本思想

快速排序通过以下步骤来对数组进行排序:

  1. 选择基准(Pivot)

    • 从数组中选择一个元素作为基准。基准的选择方法有很多,常见的有选择第一个元素、最后一个元素、中间元素,或随机选择一个元素等。
  2. 分区(Partitioning)

    • 通过一趟排序将数组分为两部分:左边部分的元素都小于等于基准,右边部分的元素都大于等于基准。
  3. 递归排序

    • 就是将这个进行二分树状分割,重复排序分割的部分,最后没有需要排序的就返回(0或1)

快速排序的实现

下面是使用 Java 实现快速排序的代码示例:

public class QuickSort {

    // 方法:快速排序
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 获取分区点(pivot)
            int pi = partition(arr, low, high);

            // 递归排序左半部分
            quickSort(arr, low, pi - 1);

            // 递归排序右半部分
            quickSort(arr, pi + 1, high);
        }
    }

    // 分区方法:返回分区点
    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];  // 选择最右边的元素作为基准
        int i = (low - 1);  // i 指向小于 pivot 的最后一个元素

        for (int j = low; j < high; j++) {
            // 如果当前元素小于或等于 pivot
            if (arr[j] <= pivot) {
                i++;
                // 交换 arr[i] 和 arr[j]
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }

        // 将 pivot 放置到正确的位置
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;

        return i + 1;
    }

    // 主方法:测试快速排序
    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};

        System.out.println("排序前的数组:");
        printArray(arr);

        quickSort(arr, 0, arr.length - 1);

        System.out.println("排序后的数组:");
        printArray(arr);
    }

    // 辅助方法:打印数组
    public static void printArray(int[] arr) {
        for (int j : arr) {
            System.out.print(j + " ");
        }
        System.out.println();
    }
}

代码解析

  1. quickSort 方法

    • 该方法通过递归的方式对数组进行排序。lowhigh 分别代表当前排序范围的起始和结束索引。
  2. partition 方法

    • 这是快速排序的核心步骤。它通过选择一个基准元素(这里选择数组的最后一个元素),然后调整数组元素的位置,使得基准元素的左边都小于或等于它,右边都大于它。最后返回基准元素的位置索引。
  3. 递归

    • quickSort 方法中,排序完成后,基准元素将数组分为两个部分,算法会继续递归地对这两个部分进行快速排序。

快速排序的时间复杂度

  • 最坏时间复杂度:(O(n2))(当数组已经有序,且每次选择的基准是最小或最大的元素时)。
  • 最好时间复杂度:(O(n log n))(当每次分区都恰好将数组平分时)。
  • 平均时间复杂度:(O(n log n))(快速排序的期望复杂度通常是 (log n) 层递归,每层操作需要 (O(n)))。

空间复杂度

  • 空间复杂度:(O(\log n))(主要是由于递归调用栈的深度)。

特点

  • 快速排序通常在实践中非常高效,尤其适用于大型且无序的数组。
  • 它是不稳定的排序算法,即相同元素的相对位置在排序后可能会改变。

快速排序由于其高效的时间复杂度和较低的空间复杂度,通常被认为是最佳的通用排序算法之一,广泛应用于实际中。

大概一般的排序就这些,学一下思想就好了,大多数时候学Java里面的我用的大多数时候都是sort解决。
持续更新中~~

  • 从0开始的算法(数据结构和算法)基础(一)
  • 从0开始的算法(数据结构和算法)基础(二)
  • 从0开始的算法(数据结构和算法)基础(三)
  • 从0开始的算法(数据结构和算法)基础(四)
  • 从0开始的算法(数据结构和算法)基础(五)
  • 从0开始的算法(数据结构和算法)基础(六)
  • 从0开始的算法(数据结构和算法)基础(七)

你可能感兴趣的:(算法,数据结构,排序算法)