经典排序算法总结

1 初级排序算法

通用接口:

    //比较大小
    private static boolean less(Comparable v,Comparable w){
        return v.compareTo(w)<0;
    }

    //交换
    private static void exch(Comparable[] a,int i,int j){
        Comparable t=a[i];
        a[i]=a[j];
        a[j]=t;
    }

    //打印
    private static void show(Comparable[] a){
        for(int i=0;i<a.length;i++)
            System.out.print(a[i]+" ");
        System.out.println();
    }

    //检测
    public static boolean isSorted(Comparable[] a){
        for(int i=1;i<a.length;i++)
            if(less(a[i],a[i-1]))
                return false;
        return true;
    }

1.1 选择排序
遍历数组,每次选择最小的元素插入到对应的索引。

特点:
1.与原始数组的状态无关。
2.数据移动最少,每次交换都会改变两个数组元素的值,排序用了N次交换,交换次数和数组大小是线性关系。

时间复杂度:平均:O(n2),最好:O(n2),最坏:O(n2)
空间复杂度:O(1)

代码:

public static void sort(Comparable[] a){
        int length = a.length;
        for (int i = 0; i < length; i++) {
            int min = i;
            for (int j = i+1; j < length; j++) {
                if (less(a[j], a[min]))
                    min = j;
            }
            exch(a, i, min);
        }
    }

1.2 插入排序
分成左右两个数组,左边有序,右边无序,右边数组的第一个元素与左边数组的右端元素挨个比较,移动其到正确的位置。

特点:针对部分有序数组效率高。
典型部分有序数组:
1.数组中每个元素距离它的最终位置都不远;
2.一个有序的大数组接一个小数组;
3.数组中只有几个元素的位置不正确。
时间复杂度:平均:O(n2),最好:O(n),最坏:O(n2)
空间复杂度:O(1)

代码:

public static void sort(Comparable[] a){
        int len = a.length;
        for (int i = 1; i < len; i++) {
            for (int j = i; j > 0 && less(a[j], a[j-1]); j--)
                exch(a, j-1, j);
        }
    }

1.3 希尔排序
插入排序的变形,数组中任意间隔为h的元素是有序的,不断缩小h,直到全部有序。
特点:
数组越大,优势越大。
时间复杂度:平均:O(nlog2n),最好:O(n),最坏:O(n2)
空间复杂度:O(1)

代码:

public static void sort(Comparable[] a){
        int len = a.length;
        int h = 1;
        while (h < len/3) h = 3 * h + 1;
        while (h >= 1) {
            for (int i = h; i < len; i++) {
                for (int j = i; j >= h && less(a[j], a[j-h]); j-=h)
                    exch(a, j, j-h);
            }
            h = h / 3;
        }

2.高级排序算法

2.1 归并排序
特点:归并已经有序的两个数组;
时间复杂度:平均:O(nlog2n),最好:O(nlog2n),最坏:O(nlog2n)
空间复杂度:O(n)

归并函数:

public static void merge(Comparable[] a,int lo,int mid,int hi){
        //将数组分为左右两个,定义i,j两个指针
        int i = lo, j = mid + 1;
        //定义一个临时数组
        Comparable[] aux = new Comparable[hi+1];
        for (int k = lo; k <= hi; k++)
            aux[k] = a[k];
        for (int k = lo; k <= hi; k++) {
            if (i > mid) a[k] = aux[j++];
            else if (j > hi) a[k] = aux[i++];
            else if (less(a[i], a[j])) a[k] = aux[i++];
            else a[k] = aux[j++];
        }
    }

自顶向下的归并排序:
(分治法)将子数组归并,再通过归并两个子数组将整个数组归并。

public static void sort(Comparable[] a, int lo, int hi) {
        if (lo >= hi) return;
        int mid = lo + (hi - lo) / 2;
        //左边排序
        sort(a, lo, mid);
        //右边排序
        sort(a, mid + 1, hi);
        //归并
        merge(a, lo, mid, hi);
    }

自底向上的归并排序:
将数组划分为n个子数组,两两归并,直到将整个数组整合到一起。

public static void sort(Comparable[] a){
        int len = a.length;
        for (int sz = 1; sz < len; sz += sz) //子数组大小
        //lo是子数组的第一个元素的索引
            for (int lo = 0; lo < len - sz; lo = sz + sz) //子数组索引
                merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, len - 1));
    }

2.2 快速排序
快速排序是一种分治的排序算法。他将一个数组分成两个子数组,将两部分独立的排序。快速排序和归并排序是互补的:归并排序是将数组分成两个子数组分别排序,并将有序的子数组归并已将整个数组排序;而快速排序将数组排序的方式是当两个子数组都有序时整个数组也就自然有序了。

时间复杂度:平均:O(nlog2n),最好:O(nlog2n),最坏:O(n2)
空间复杂度:O(nlog2n)

代码:

private static int partition(Comparable[] a,int lo,int hi){
        int i = lo, j = hi + 1;
        Comparable v = a[lo];
        while (true) {
            while (less(a[++i], v)) if (i == hi) break;
            while (less(v, a[--j])) if (j == lo) break;
            if (i >= j) break;
            exch(a, i, j);
        }
        //因为j指针此时已经走过临界位置,所以j指向的元素一定小于a[lo]
        exch(a, lo, j);
        return j;
    }
    public static void sort(Comparable[] a, int lo, int hi) {
        if(hi <= lo) return;//判断递归是否结束
        int j = partition(a,lo,hi);//切分数组
        sort(a,lo,j-1);//左侧递归排序
        sort(a,j+1,hi);//右侧递归排序
    }

2.3 堆排序
二叉堆:
最大堆:父节点的键值总是大于或等于任何一个子节点的键值。
最小堆:。。。。。。。。小于或等于。。。。。。。。。。。

时间复杂度:平均:O(nlog2n),最好:O(nlog2n),最坏:O(n2)
空间复杂度:O(1)

初始化堆:
1.上浮:

private void swim(int k) {
    //k不是根节点并且父节点的值小于子节点
    while (k > 1 && less(a[k/2], a[k])) {
        exch(k/2, k);
        k = k/2;
    }
}

2.下沉:

private void sink(int k) {
    while (2*k <= N) {
        int j = 2*k;
        //选择左右子节点中较大的元素
        if (j < N && less(j, j+1)) j++;
        //如果父节点比子节点中较大的值大,则跳出循环
        if (!less(k, j)) break;
        exch(k, j);
        k = j;
    }
}

排序:

public static void sort(Comparable[] a) {
    int N = a.length;
    //初始化堆
    for (int k = N/2; k >= 1; k--)
        sink(a, k, N);
    //排序
    while (N > 1) {
        exch(a, 1, N--);
        sink(a, 1, N);
    }
}
点赞