算法模型从入门到起飞系列——八大排序算法(二)

上篇文章详细的描述了四种简单的排序算法及其优化的一些方案,其实比起基本的排序算法,我觉得学习者更应该掌握优化后的排序算法甚至希望可以在评论区上看到更多不同的解法,只要是自己去深入研究的,都可以放到评论区一起探讨甚至给博主纠正。下面就是要详细刨析另外四种不常见的排序算法,性能更高,但是其实真正的使用场景偏少。

文章目录

  • 一、常见八大排序算法性能对比
  • 二、归并排序 (Merge Sort)
    • 2.1 归并排序核心思想
    • 2.2 归并排序代码展示
    • 2.3 归并排序优化
  • 三、堆排序 (Heap Sort)
    • 3.1 堆排序思想
    • 3.2 堆排序代码展示
  • 四、计数排序 (Counting Sort)
    • 4.1 计数排序思想
    • 4.2 计数排序代码展示
    • 4.3 计数排序优化
  • 五、基数排序 (Radix Sort)
    • 5.1 基数排序思想
    • 5.2 基数排序代码展示
    • 5.3 基数排序优化

一、常见八大排序算法性能对比

特性/算法 冒泡排序 (Bubble Sort) 选择排序 (Selection Sort) 插入排序 (Insertion Sort) 快速排序 (Quick Sort) 归并排序 (Merge Sort) 堆排序 (Heap Sort) 计数排序 (Counting Sort) 基数排序 (Radix Sort)
稳定性 稳定 不稳定 稳定 不稳定 稳定 不稳定 稳定 稳定
时间复杂度 O(n²) O(n²) O(n²) 平均O(n log n) O(n log n) O(n log n) O(n + k) O(n * k)
空间复杂度 O(1) O(1) O(1) O(log n) O(n) O(1) O(k) O(n + k)
适用场景 小规模数据集 小规模数据集 小规模或部分有序数据集 大规模数据集 大规模数据集 大规模数据集 小范围整数数据集 小范围整数数据集
主要操作 比较与交换相邻元素 找最小值并交换 插入到正确位置 分治法 分治法 构建与维护堆 统计频率 按位排序
递归/迭代 迭代 迭代 迭代 递归 递归 迭代 迭代 迭代
优点 实现简单 实现简单 对部分有序数据效果好 高效的平均性能 稳定且高效 原地排序 线性时间复杂度 线性时间复杂度
缺点 效率低 效率低 对大规模数据效率低 最坏情况O(n²) 需要额外空间 不稳定 只适用于小范围整数 只适用于小范围整数

口诀:么(冒)选(选)叉(插),快(快)乖(归)嘚(堆),gg(计基)。接近方言话选衣服的时候不要选了,男生催女生不要选啦,快一点乖乖,不然要gg了。有猜到是哪里的方言嘛?评论区留言,猜对有奖。

二、归并排序 (Merge Sort)

2.1 归并排序核心思想

  1. 基于分治法的高效、稳定的排序算法。它的基本思想是将待排序的序列分成两个大致相等的子序列,分别对这两个子序列进行排序,然后将已排序的子序列合并成一个最终的有序序列。
  2. 分解:将n个元素分成各含n/2个元素的子序列;
  3. 解决:使用归并排序递归地对两个子序列进行排序;
  4. 合并:合并两个已经排序好的子序列以得到排序结果。

2.2 归并排序代码展示

归并排序代码展示:

public class Main {

    // 归并排序方法
    public static void mergeSort(int[] arr, int low, int high) {
        if (low < high) {
            // 找到中间点
            int mid = (low + high) / 2;

            // 递归排序左半部分
            mergeSort(arr, low, mid);

            // 递归排序右半部分
            mergeSort(arr, mid + 1, high);

            // 合并两个已排序的部分
            merge(arr, low, mid, high);
        }
    }

    // 合并方法
    private static void merge(int[] arr, int low, int mid, int high) {
        // 创建临时数组存储合并后的结果
        int[] tempArray = new int[high - low + 1];
        int leftIndex = low;
        int rightIndex = mid + 1;
        int tempIndex = 0;

        // 比较左右两部分的元素,按顺序填入临时数组
        while (leftIndex <= mid && rightIndex <= high) {
            if (arr[leftIndex] <= arr[rightIndex]) {
                tempArray[tempIndex++] = arr[leftIndex++];
            } else {
                tempArray[tempIndex++] = arr[rightIndex++];
            }
        }

        // 如果左边剩余,则直接添加到结果中
        while (leftIndex <= mid) {
            tempArray[tempIndex++] = arr[leftIndex++];
        }

        // 如果右边剩余,则直接添加到结果中
        while (rightIndex <= high) {
            tempArray[tempIndex++] = arr[rightIndex++];
        }

        // 将临时数组中的内容拷贝回原数组
        for (int i = 0; i < tempArray.length; i++) {
            arr[low + i] = tempArray[i];
        }
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("原始数组:");
        printArray(arr);

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

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

代码运行结果展示:

原始数组:
64 34 25 12 22 11 90 
排序后的数组:
11 12 22 25 34 64 90 

2.3 归并排序优化

  1. 小数组使用插入排序。
public class Main {

    private static final int INSERTION_SORT_THRESHOLD = 10;

    public static void mergeSort(int[] arr, int low, int high) {
        if (high - low < INSERTION_SORT_THRESHOLD) {
            insertionSort(arr, low, high);
            return;
        }
        if (low < high) {
            int mid = (low + high) / 2;
            mergeSort(arr, low, mid);
            mergeSort(arr, mid + 1, high);
            merge(arr, low, mid, high);
        }
    }

    private static void insertionSort(int[] arr, int low, int high) {
        for (int i = low + 1; i <= high; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= low && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    private static void merge(int[] arr, int low, int mid, int high) {
        int[] tempArray = new int[high - low + 1];
        int leftIndex = low;
        int rightIndex = mid + 1;
        int tempIndex = 0;

        while (leftIndex <= mid && rightIndex <= high) {
            if (arr[leftIndex] <= arr[rightIndex]) {
                tempArray[tempIndex++] = arr[leftIndex++];
            } else {
                tempArray[tempIndex++] = arr[rightIndex++];
            }
        }

        while (leftIndex <= mid) {
            tempArray[tempIndex++] = arr[leftIndex++];
        }

        while (rightIndex <= high) {
            tempArray[tempIndex++] = arr[rightIndex++];
        }

        for (int i = 0; i < tempArray.length; i++) {
            arr[low + i] = tempArray[i];
        }
    }

    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("原始数组:");
        printArray(arr);

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

        System.out.println("排序后的数组:");
        printArray(arr);
    }
}
  1. 提前终止合并。
  2. 自底向上归并排序。

可以自己尝试一下编写,不会的话私信博主,提供最易懂的代码。

三、堆排序 (Heap Sort)

3.1 堆排序思想

  1. 构建堆
    首先将待排序的数组构建成一个最大堆(对于升序排序)或最小堆(对于降序排序)。最大堆的特点是每个节点的值都大于或等于其子节点的值。
  2. 排序过程
    将堆顶元素(即最大值)与最后一个未排序元素交换位置,并将其从堆中移除。
    对剩余部分重新调整为最大堆,重复上述步骤直到所有元素都被排序。

3.2 堆排序代码展示

堆排序代码展示:

public class Main {

    // 堆排序方法
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 逐一提取堆顶元素并重建堆
        for (int i = n - 1; i > 0; i--) {
            // 将当前最大的元素放到数组末尾
            swap(arr, 0, i);

            // 调整堆大小并重建堆
            heapify(arr, i, 0);
        }
    }

    // 调整以某个节点为根的子树为最大堆
    private static void heapify(int[] arr, int n, int i) {
        int largest = i; // 初始化最大节点为根节点
        int left = 2 * i + 1; // 左子节点索引
        int right = 2 * i + 2; // 右子节点索引

        // 如果左子节点大于根节点,则更新最大节点
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 如果右子节点大于最大节点,则更新最大节点
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大节点不是根节点,则交换并继续调整子树
        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    // 交换数组中的两个元素
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("原始数组:");
        printArray(arr);

        heapSort(arr);

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

代码运行结果展示:

原始数组:
64 34 25 12 22 11 90 
排序后的数组:
11 12 22 25 34 64 90
  1. 使用哨兵节点

参考代码:

public static void heapSortWithSentinel(int[] arr) {
    int n = arr.length;
    int[] extendedArr = new int[n + 1];

    // 插入哨兵节点
    System.arraycopy(arr, 0, extendedArr, 1, n);

    // 构建最大堆
    for (int i = n / 2; i >= 1; i--) {
        heapifyWithSentinel(extendedArr, n, i);
    }

    // 逐一提取堆顶元素并重建堆
    for (int i = n; i > 1; i--) {
        // 将当前最大的元素放到数组末尾
        swap(extendedArr, 1, i);

        // 调整堆大小并重建堆
        heapifyWithSentinel(extendedArr, i - 1, 1);
    }

    // 复制回原数组
    System.arraycopy(extendedArr, 1, arr, 0, n);
}

private static void heapifyWithSentinel(int[] arr, int n, int i) {
    int largest = i;
    int left = 2 * i;
    int right = 2 * i + 1;

    if (left <= n && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right <= n && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapifyWithSentinel(arr, n, largest);
    }
}
  1. 自底向上构建堆

代码参考:

public static void bottomUpHeapSort(int[] arr) {
    int n = arr.length;

    // 自底向上构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }

    // 逐一提取堆顶元素并重建堆
    for (int i = n - 1; i > 0; i--) {
        // 将当前最大的元素放到数组末尾
        swap(arr, 0, i);

        // 调整堆大小并重建堆
        heapify(arr, i, 0);
    }
}

四、计数排序 (Counting Sort)

4.1 计数排序思想

  1. 确定范围:首先找到输入数组中的最小值和最大值,以确定计数数组的大小。
  2. 计数:创建一个计数数组countArray,其大小为max - min + 1。遍历输入数组,统计每个元素出现的次数,并存储在计数数组中。
  3. 累积计数:将计数数组转换为累积计数数组,这样每个元素对应的值表示小于等于该元素的个数。
  4. 构建输出数组:根据累积计数数组,将输入数组中的元素放到正确的位置上。、

4.2 计数排序代码展示

代码展示:

public class Main {

    public static void countingSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // 找到数组中的最小值和最大值
        int min = arr[0];
        int max = arr[0];
        for (int num : arr) {
            if (num < min) {
                min = num;
            }
            if (num > max) {
                max = num;
            }
        }

        // 创建计数数组
        int range = max - min + 1;
        int[] countArray = new int[range];

        // 统计每个元素出现的次数
        for (int num : arr) {
            countArray[num - min]++;
        }

        // 累积计数
        for (int i = 1; i < range; i++) {
            countArray[i] += countArray[i - 1];
        }

        // 构建输出数组
        int[] outputArray = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int num = arr[i];
            outputArray[countArray[num - min] - 1] = num;
            countArray[num - min]--;
        }

        // 将结果复制回原数组
        System.arraycopy(outputArray, 0, arr, 0, arr.length);
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("原始数组:");
        printArray(arr);

        countingSort(arr);

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

代码运行结果展示:

原始数组:
64 34 25 12 22 11 90 
排序后的数组:
11 12 22 25 34 64 90 

4.3 计数排序优化

  1. 减少内存使用
  2. 处理负数
  3. 稳定性和逆序处理

参考代码:

public class OptimizedCountingSort {

    public static void optimizedCountingSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // 找到数组中的最小值和最大值
        int min = arr[0];
        int max = arr[0];
        for (int num : arr) {
            if (num < min) {
                min = num;
            }
            if (num > max) {
                max = num;
            }
        }

        // 创建计数数组
        int range = max - min + 1;
        int[] countArray = new int[range];

        // 统计每个元素出现的次数
        for (int num : arr) {
            countArray[num - min]++;
        }

        // 累积计数
        for (int i = 1; i < range; i++) {
            countArray[i] += countArray[i - 1];
        }

        // 构建输出数组
        int[] outputArray = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int num = arr[i];
            outputArray[countArray[num - min] - 1] = num;
            countArray[num - min]--;
        }

        // 将结果复制回原数组
        System.arraycopy(outputArray, 0, arr, 0, arr.length);
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90, -1, -10, 0};

        System.out.println("原始数组:");
        printArray(arr);

        optimizedCountingSort(arr);

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

其实还可以针对动态调整一下数组大小,希望可以在评论区看到。

五、基数排序 (Radix Sort)

5.1 基数排序思想

  1. 确定最大位数:首先找到输入数组中最大数的位数。
  2. 按位排序:使用稳定的排序算法(如计数排序)对每一位进行排序,从最低有效位开始,逐步处理到最高有效位。
  3. 稳定排序:确保每次按位排序时使用的排序算法是稳定的,以保证相同位数相同的元素在排序后的相对位置不变。

5.2 基数排序代码展示

代码展示:

public class Main {

    // 基数排序方法
    public static void radixSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // 找到数组中的最大值,以确定需要处理的最大位数
        int max = getMax(arr);

        // 对每一位进行排序
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }

    // 获取数组中的最大值
    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }
        return max;
    }

    // 使用计数排序按位排序
    private static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n]; // 输出数组
        int[] count = new int[10]; // 计数数组

        // 统计每个数字出现的次数
        for (int i = 0; i < n; i++) {
            int digit = (arr[i] / exp) % 10;
            count[digit]++;
        }

        // 累积计数
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        // 构建输出数组
        for (int i = n - 1; i >= 0; i--) { // 从右向左遍历以保持稳定性
            int digit = (arr[i] / exp) % 10;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }

        // 将结果复制回原数组
        System.arraycopy(output, 0, arr, 0, n);
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};

        System.out.println("原始数组:");
        printArray(arr);

        radixSort(arr);

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

代码运行结果展示:

原始数组:
64 34 25 12 22 11 90 
排序后的数组:
11 12 22 25 34 64 90

5.3 基数排序优化

  1. getMax方法:找到数组中的最大值,以确定需要处理的最大位数。
  2. countingSortByDigit方法:使用计数排序对每一位进行排序。exp表示当前正在处理的位数(例如个位、十位等)。
  • 首先统计每个数字出现的次数。
  • 然后累积计数,使得每个元素对应的值表示小于等于该元素的个数。
  • 最后构建输出数组,并将其复制回原数组。

参考代码:

public class OptimizedRadixSort {

    public static void optimizedRadixSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // 分离正数和负数
        int positiveCount = 0;
        int negativeCount = 0;
        for (int num : arr) {
            if (num >= 0) {
                positiveCount++;
            } else {
                negativeCount++;
            }
        }

        int[] positiveArr = new int[positiveCount];
        int[] negativeArr = new int[negativeCount];

        int posIndex = 0;
        int negIndex = 0;
        for (int num : arr) {
            if (num >= 0) {
                positiveArr[posIndex++] = num;
            } else {
                negativeArr[negIndex++] = -num; // 转换为正数
            }
        }

        // 对正数和负数分别进行基数排序
        radixSort(positiveArr);
        radixSort(negativeArr);

        // 合并结果
        negIndex = negativeArr.length - 1;
        posIndex = 0;
        int index = 0;

        // 先插入负数(转换回负数)
        while (negIndex >= 0) {
            arr[index++] = -negativeArr[negIndex--];
        }

        // 再插入正数
        while (posIndex < positiveArr.length) {
            arr[index++] = positiveArr[posIndex++];
        }
    }

    // 基数排序方法
    private static void radixSort(int[] arr) {
        int max = getMax(arr);

        // 对每一位进行排序
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }

    // 获取数组中的最大值
    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }
        return max;
    }

    // 使用计数排序按位排序
    private static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n]; // 输出数组
        int[] count = new int[10]; // 计数数组

        // 统计每个数字出现的次数
        for (int i = 0; i < n; i++) {
            int digit = (arr[i] / exp) % 10;
            count[digit]++;
        }

        // 累积计数
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        // 构建输出数组
        for (int i = n - 1; i >= 0; i--) { // 从右向左遍历以保持稳定性
            int digit = (arr[i] / exp) % 10;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }

        // 将结果复制回原数组
        System.arraycopy(output, 0, arr, 0, n);
    }

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

    // 主方法用于测试
    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66, -1, -10, 0};

        System.out.println("原始数组:");
        printArray(arr);

        optimizedRadixSort(arr);

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

你可能感兴趣的:(算法模型,算法,排序算法,java)