几种排序算法总结

堆排序

堆排序使用了一种称为“堆”的数据结构,堆是一棵完全二叉树,实际中可以通过一个数组来实现,堆的特征可解释为完全二叉树中任意分支结点的值都小于或等于它的左、右儿子节点的值。将根节点最小的堆称为最小堆,根节点最大的堆称为最大堆。

《几种排序算法总结》



如图所示,可以将数组看做是一课完全二叉树从根节点编号为1开始的顺序存储。给定一个结点索引为 i,可以得出它的左孩子的索引为 2i,右孩子为 2i+1,父结点为 i/2。

堆排序的基本思路是,首先将顺序存储的数组变换成一个堆(从有孩子的最大索引的结点开始,依次使以该结点为根的子树变成堆,直到树的根节点,在建堆的过程中,一旦发生交换,则需要在交换处继续调整,直到以调整处为根的子树满足堆的条件),然后提取堆中的第一个元素,将该元素从堆中删除,再对剩下的部分重新建堆,直到剩余一个元素。通常是将当前堆中的最后一个元素和根结点交换位置,同时让堆中元素个数减 1,这样的话就不需要重头开始建堆,只需要从根结点处利用筛选算法继续调整堆(筛选算法是值对于调整处序号为k的调整算法)。
堆排序是不稳定的排序算法,它的时间复杂度为 O(nlogn)。堆排序适合于数据规模较大的场景。
堆排序的最差时间复杂度也为 O(nlogn),因此与快速排序相比,虽然
快速排序的比较次数和交换次数都要比堆排序少(性能更优),然而实际应用中,如果需要保证一定的响应时间的话,就不应该使用快排,因为它最坏情况下是一个O(n2)的算法。
堆排序代码实现:

// 最大堆
public static void heapSort(int[] a) {
    int n = a.length;
    for (int i = (n-1)/2; i >= 0; i--)
        sift(a, i, n);

    for (int j = n-1; j >= 0; j--) {
        int temp = a[j];
        a[j] = a[0];
        a[0] = temp;
        sift(a, 0, j);
    }
}

// 从序号为k处开始调整,n代表堆的大小
private static void sift(int[] a, int k, int n) {
    int sub = 2*k+1; //左子节点

    while (sub < n) {
        if (sub+1 < n && a[sub] < a[sub+1])
            sub++;

        if (a[k] >= a[sub]) break;

        int temp = a[sub];
        a[sub] = a[k];
        a[k] = temp;

        k = sub;
        sub = 2*k+1;
    }
}


快速排序

快速排序是使用分治法来把一个序列分成两个子序列。基本思路是,先从序列中选出一个元素作为基准(pivot),把比这个基准小的元素都放在它的左边,大于或等于基准的元素都放在它的右边,然后再递归重复之前的步骤,直到各区间只有1个元素。
快速排序的速度很快,为不稳定的排序算法,快速排序的时间复杂度为 O(nlogn),它的最差时间复杂度为 O(n2)。
快速排序的代码实现:

public static void quickSort(int[] a, int l, int r) {
    if (l >= r) return;

    int i = l, j = r;
    int pivot = a[i];

    while (i < j) {
        while (i < j && a[j] >= pivot)
            j--;
        if (i < j)
            a[i++] = a[j];

        while (i < j && a[i] < pivot)
            i++;
        if (i < j)
            a[j--] = a[i];
    }
    a[i] = pivot;

    quickSort(a, l, i-1);
    quickSort(a, i + 1, r);
}


归并排序

归并排序是将一个待排序记录构成的文件看做是由多个有序子文件构成,通过对有序子文件使用若干次归并的方法得到一个有序文件。该算法是采用分治法的一个非常典型的应用。

归并排序的基本思路是,首先将待排序文件分为一个个的有序子文件(具体做法是将文件分成两部分,接下来对分出来的两部分各自分成两部分,以此类推,当分出来的部分中只有一个元素时,就可以认为该部分已经有序了),然后依次合并相邻的两个子文件。即先递归分解,然后再合并

归并排序合并两个有序子文件的方法是,分别从两有序子文件依次进行遍历比较,取出较小的元素,然后再进行比较,如果有子文件为空的话,则直接将另一个子文件的元素依次取出即可。

归并排序速度仅次于快速排序,为稳定的排序算法。它的时间复杂度为 O(nlogn),空间复杂度为 O(n),较为消耗空间,一般适用于总体无序而各子项相对有序的文件。

归并排序代码实现:

public static void mergeSort(int[] a, int l, int r) {
    if (l < r) {
        int m = (l + r)/2;
        mergeSort(a, l, m);
        mergeSort(a, m+1, r);
        mergeArray(a, l, m, r);
    }
}

private static void mergeArray(int[] a, int left, int middle, int right) {
    int i = left, m = middle, j = middle+1, end = right;
    int[] temp = new int[right-left+1];
    int k = 0;

    while (i <= m && j <= end) {
        if (a[i] < a[j]) {
            temp[k++] = a[i++];
        } else {
            temp[k++] = a[j++];
        }
    }

    while (i <= m)
        temp[k++] = a[i++];
    while (j <= end)
        temp[k++] = a[j++];

    for (int ii = 0; ii < k; ii++)
        a[left+ii] = temp[ii];
}
点赞