常见排序算法

快速排序

简介

快速排序(Quick Sort)是一种高效的分治排序算法,它的核心思想是选择一个基准元素(通常是数组中的一个元素),将数组分割为两个子数组,一个子数组中的元素都小于基准元素,另一个子数组中的元素都大于基准元素,然后递归地对这两个子数组进行排序。快速排序是一种不稳定排序算法,其平均时间复杂度为 O(nlogn),但最坏情况下的时间复杂度为 O(n^2)。

步骤

  • 选择基准:从数组中选择一个基准元素。通常,可以选择第一个元素、最后一个元素、中间元素等。
  • 分割:重新排列数组,将小于基准元素的元素放在基准元素的左边,将大于基准元素的元素放在右边。基准元素此时位于其最终排序位置。
  • 递归:递归地对基准元素左边的子数组和右边的子数组进行排序,重复分割和排序过程。
  • 合并:不需要合并操作,因为排序是原地进行的。

代码

public class QuickSort {
        static int[] quick(Integer[] arr) {
        if (arr.length == 0) return new int[0];
        List<Integer> low = new ArrayList<>();
        List<Integer> high = new ArrayList<>();
        int p = arr[0];
        for (int i = 1; i < arr.length; ++i) {
            if (arr[i] < p) low.add(arr[i]);
            else high.add(arr[i]);
        }
        int[] left = quick(low.toArray(Integer[]::new));
        int[] right = quick(high.toArray(Integer[]::new));
        int[] res = new int[arr.length];
        int t = 0;
        for (int i : left) res[t++] = i;
        res[t++] = p;
        for (int i : right) res[t++] = i;
        return res;
    }

    public static void main(String[] args) {
        Integer[] a = new Integer[]{3,2,7,1,6};
        int[] z = quick(a);
        System.out.println(Arrays.toString(z));
    }
}

归并排序

简介

归并排序(Merge Sort)是一种分治算法,它将一个未排序的数组分成两个子数组,然后递归地对这两个子数组进行排序,最后将它们合并为一个有序的数组。归并排序是一种稳定的排序算法,其时间复杂度为 O(nlogn),适用于各种数据类型的排序。

步骤

  • 分解:将未排序的数组分解为两个子数组,通常是将数组分为相等大小的两部分,但如果数组大小是奇数,则两个子数组的大小会略有差异。
  • 排序:递归地对这两个子数组进行排序。这一步骤会继续分解和排序,直到子数组的大小减小到足够小,可以被认为是有序的。
  • 合并:将两个已排序的子数组合并为一个大的有序数组。在这一步骤中,从两个子数组中选择较小的元素放入结果数组中,重复此过程,直到两个子数组都已合并。
  • 递归:重复上述步骤,直到整个数组已排序。

代码

java

public class MergeSort {
    static int[] merge(int[] a, int[] b) {
        int m = a.length, n = b.length;
        int l = 0, r = 0, p = 0;
        int[] arr = new int[m + n];
        while (l < m && r < n) {
            if (a[l] < b[r]) {
                arr[p++] = a[l++];
            } else {
                arr[p++] = b[r++];
            }
        }
        while (l < m) arr[p++] = a[l++];
        while (r < n) arr[p++] = b[r++];
        return arr;
    }
    static int[] separate(int[] arr) {
        int n = arr.length;
        if (n <= 1) return arr;
        int[] a = separate(Arrays.copyOfRange(arr, 0, n / 2));
        int[] b = separate(Arrays.copyOfRange(arr, n / 2, n));
        int[] res = merge(a, b);
        return res;
    }

    public static void main(String[] args) {
        int[] a = new int[]{3,1,5,4,6};
        int[] res = separate(a);
        System.out.println(Arrays.toString(res));
    }
}

c

#include <stdio.h>
#include <stdlib.h>

void merge(int* a, int m, int* b, int n, int* arr) {
    int l = 0, r = 0, p = 0;
    while (l < m && r < n) {
        if (a[l] < b[r]) {
            arr[p++] = a[l++];
        } else {
            arr[p++] = b[r++];
        }
    }
    while (l < m) arr[p++] = a[l++];
    while (r < n) arr[p++] = b[r++];
}

void separate(int* arr, int n) {
    if (n <= 1) return;
    int m = n / 2;
    int* a = (int*)malloc(m * sizeof(int));
    int* b = (int*)malloc((n - m) * sizeof(int));
    for (int i = 0; i < m; i++) {
        a[i] = arr[i];
    }
    for (int i = m; i < n; i++) {
        b[i - m] = arr[i];
    }
    separate(a, m);
    separate(b, n - m);
    merge(a, m, b, n - m, arr);
    free(a);
    free(b);
}

int main() {
    int a[] = {3, 1, 5, 4, 6};
    int n = sizeof(a) / sizeof(a[0]);
    separate(a, n);
    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}


堆排序

简介

堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法,它是一种不稳定的原地排序算法,其时间复杂度为 O(nlogn)。堆排序的核心思想是将未排序的数组构建成一个二叉堆(通常是最大堆或最小堆),然后逐步将堆顶元素与数组的最后一个元素交换,然后调整堆以恢复堆的性质,重复这个过程,直到整个数组有序。

步骤

  • 构建堆:将未排序的数组构建成一个二叉堆。可以选择最大堆(根节点的值大于或等于其子节点的值)或最小堆(根节点的值小于或等于其子节点的值)。
  • 交换堆顶元素:将堆顶元素与数组的最后一个元素交换。此时,堆顶元素为最大(最小)值。
  • 恢复堆的性质:对剩余元素进行堆调整,以恢复堆的性质。
  • 重复:重复步骤 2 和步骤 3,逐步将堆顶元素移动到数组的末尾,同时堆的大小减小。最终,整个数组就会被排序。

代码

public class HeapSort {
    /**
     * 选择排序-堆排序
     * @param array 待排序数组
     * @return 已排序数组
     */
    public static int[] heapSort(int[] array) {
        //这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            adjustHeap(array, i, array.length);  //调整堆
        }

        // 上述逻辑,建堆结束
        // 下面,开始排序逻辑
        for (int j = array.length - 1; j > 0; j--) {
            // 元素交换,作用是去掉大顶堆
            // 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置
            swap(array, 0, j);
            // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。
            // 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因
            // 而这里,实质上是自上而下,自左向右进行调整的
            adjustHeap(array, 0, j);
        }
        return array;
    }

    /**
     * 整个堆排序最关键的地方
     * @param array 待组堆
     * @param i 起始结点
     * @param length 堆的长度
     */
    public static void adjustHeap(int[] array, int i, int length) {
        // 先把当前元素取出来,因为当前元素可能要一直移动
        int temp = array[i];
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {  //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
            // 让k先指向子节点中最大的节点
            if (k + 1 < length && array[k] < array[k + 1]) {  //如果有右子树,并且右子树大于左子树
                k++;
            }
            //如果发现结点(左右子结点)大于根结点,则进行值的交换
            if (array[k] > temp) {
                swap(array, i, k);
                // 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
                i  =  k;
            } else {  //不用交换,直接终止循环
                break;
            }
        }
    }

    /**
     * 交换元素
     * @param arr
     * @param a 元素的下标
     * @param b 元素的下标
     */
    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{3,1,2,5,9,2,7};
        int[] z = heapSort(arr);
        System.out.println(Arrays.toString(z));
    }
}

基数排序

简介

基数排序(Radix Sort)是一种非比较排序算法,它根据关键字的每个位数(从最低位到最高位)进行排序。基数排序通常用于对整数或字符串进行排序,它通过分配和收集的方式来完成排序。基数排序可以是稳定的排序算法,适用于一定范围内的整数或字符串。

步骤

  • 从最低位开始,按照位数进行排序,可以使用计数排序等稳定的排序算法。
  • 依次按照位数从低到高,重复第一步的排序过程,直到最高位。
  • 最终结果是按位排序后的有序序列。

代码

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

    // 基数排序函数
    static void radixSort(int[] arr) {
        int max = getMax(arr);
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSort(arr, exp);
        }
    }

    // 计数排序函数(用于基数排序)
    static void countingSort(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++) {
            count[(arr[i] / exp) % 10]++;
        }

        // 更新计数数组,计算每个数字应该出现的位置
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        // 根据计数数组,构造输出数组
        for (int i = n - 1; i >= 0; i--) {
            output[count[(arr[i] / exp) % 10] - 1] = arr[i];
            count[(arr[i] / exp) % 10]--;
        }

        // 将输出数组复制到原数组中
        System.arraycopy(output, 0, arr, 0, n);
    }

    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
posted @ 2023-10-24 19:39  岸南  阅读(28)  评论(0)    收藏  举报