常用的排序算法

1. 快速排序

1.1 基本思想:

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

1.2 步骤如下:

  1. 选择基准(Pivot):在数据集之中,选择一个元素作为"基准"(pivot)
  2. 分区(Partitioning):将数组进行分区(partition),将小于基准值的元素移到基准的左边,大于基准值的元素移到基准的右边。分区完成后,基准元素所处的位置就是最终排序后它的位置
  3. 递归排序:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序

快速排序在最坏情况下的时间复杂度为O( n 2 n^2 n2),但这种情况非常少见。它的平均时间复杂度为O( n l o g n n log n nlogn),并且由于排序过程是在原地进行分区,所以它不需要额外的存储空间,空间复杂度为O( l o g n log n logn)

1.3代码实现:

// 快速排序主方法
public static void quickSort(int[] arr, int low, int high) {
    if (low < high) {
        // 分区操作,获取基准元素位置
        int pivotIndex = partition(arr, low, high);
        // 递归排序基准左侧子数组
        quickSort(arr, low, pivotIndex - 1);
        // 递归排序基准右侧子数组
        quickSort(arr, pivotIndex + 1, high);
    }
}

// 分区方法
private static int partition(int[] arr, int low, int high) {
    int pivot = arr[low]; // 选择第一个元素作为基准
    int i = low;
    int j = high;

    while (i < j) {
        // 从右向左找第一个小于基准的元素
        while (i < j && arr[j] >= pivot) {
            j--;
        }
        if (i < j) {
            arr[i++] = arr[j];
        }

        // 从左向右找第一个大于基准的元素
        while (i < j && arr[i] < pivot) {
            i++;
        }
        if (i < j) {
            arr[j--] = arr[i];
        }
    }

    // 将基准元素放到正确位置
    arr[i] = pivot;
    return i;
}

2. 归并排序

2.1 基本思想:

将数组分成若干个小组,先在每个小组内部进行排序,然后将有序的小组合并成更大的有序组,直到整个数组变得有序

2.2 步骤如下:

  1. 分解(Divide):将原始数组切分成较小的数组,直到每个小数组只有一个位置,这时每个小数组都是有序的
  2. 合并(Conquer):将小数组两两合并,保证合并后的数组仍然有序。这一步是通过比较每个小数组中的元素来完成的
  3. 重复:重复上述合并操作,直到最终合并成一个有序的数组

归并排序的时间复杂度是 O( n l o g n n log n nlogn),这是因为数组每次都被分成两半(这是一个 l o g n log n logn的过程),并且在合并过程中需要遍历所有元素(这是一个 n n n 的过程)。此外,归并排序不是原地排序算法,因为它需要额外的存储空间来合并数组。

2.3 代码实现:

// 归并排序主方法(递归实现)
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);
    }
}

// 合并两个有序子数组的方法
private static void merge(int[] arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    // 创建临时数组存储左右子数组
    int[] leftArr = new int[n1];
    int[] rightArr = new int[n2];
    System.arraycopy(arr, left, leftArr, 0, n1);
    System.arraycopy(arr, mid + 1, rightArr, 0, n2);

    // 合并临时数组到原数组
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k++] = leftArr[i++];
        } else {
            arr[k++] = rightArr[j++];
        }
    }

    // 复制左子数组剩余元素
    while (i < n1) {
        arr[k++] = leftArr[i++];
    }

    // 复制右子数组剩余元素
    while (j < n2) {
        arr[k++] = rightArr[j++];
    }
}

3. 插入排序

3.1 基本思想:

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

3.2 步骤如下:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

插入排序的时间复杂度在最坏情况下是 O( n 2 n^2 n2),平均情况也是 O( n 2 n^2 n2),但在数组几乎排序好的情况下,它可以达到 O( n n n)

3.3 代码实现:

public static void insertionSort(int[] array) {
    int n = array.length;
    for (int i = 1; i < n; i++) {
        int key = array[i];
        int j = i - 1;

        // 将array[i]与已排序好的array[0...i-1]中的元素进行比较,找到合适的位置插入
        while (j >= 0 && array[j] > key) {
            array[j + 1] = array[j];
            j = j - 1;
        }
        array[j + 1] = key;
    }
}

4. 冒泡排序

4.1 基本思想:

重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端

4.2 步骤如下:

  1. 比较相邻的元素。如果第一个比第二个大(升序排序),就交换它们两个
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数
  3. 针对所有的元素重复以上的步骤,除了最后一个
  4. 重复步骤1~3,直到排序完成

冒泡排序的时间复杂度是O( n 2 n^2 n2),因为它需要比较所有相邻的元素对,并且在最坏的情况下需要交换所有的元素

4.3 代码实现:

// 基础冒泡排序实现
public static void bubbleSort(int[] arr) {
    if (arr == null || arr.length == 0) {
        return;
    }
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 优化版冒泡排序(带标志位)
public static void optimizedBubbleSort(int[] arr) {
    if (arr == null || arr.length == 0) {
        return;
    }
    int n = arr.length;
    boolean swapped;
    for (int i = 0; i < n - 1; i++) {
        swapped = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;
            }
        }
        // 如果一轮没有交换,提前结束
        if (!swapped) {
            break;
        }
    }
}

5. 选择排序

5.1 基本思想:

首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕

5.2 步骤如下:

  1. 在未排序序列中找到最小(或最大)元素
  2. 将其与未排序序列的第一个元素交换位置
  3. 在剩余未排序元素中重复上述步骤,直到整个序列排序完成

选择排序的时间复杂度无论是最好、平均还是最坏情况都是 O( n 2 n^2 n2)

5.3 代码实现:

// 选择排序主方法
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[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

以上就是常用的一些排序算法,虽然Java中有排序的方法,但是这些方法还是需要了解和掌握。

你可能感兴趣的:(算法刷题记录,排序算法,算法,java,学习)