排序

120 阅读4分钟

冒泡 插入 选择排序

排序 平均时间复杂度 最好 最坏 是否稳定 空间复杂度(是否原地排序)
冒泡 O(n2) O(n) o(n2) 稳定 O(1)
插入 O(n2) O(n) o(n2) 稳定 O(1)
选择 O(n2) O(n) o(n2) 不稳定 O(1)
归并 O(nlog(n)) O(nlog(n)) O(nlog(n)) 稳定 O(n)
快速 O(nlog(n)) O(nlog(n)) o(n2) 不稳定 O(1)或者O(n)
桶排序 O(n) O(n) o(nlog(n))单筒 稳定 O(1)
计数排序 O(n2) O(n) o(nlog(n)) 稳定 O(1)
基数排序排序 O(dn) O(dn) o(dn) 稳定 O(1)

稳定排序算法可以保持金额相同的两个对象,在排序之后的前后顺序不变。

复杂度分析: 近似:有序度 O(n) = 逆序读/满有序度

满有序度:n(n-1)/2 粗略估计交换次数

nlog(n) 复杂度分析:

T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。 T(n) = 2*T(n/2) + n; n>1

T(n) = 2T(n/2) + n = 2(2T(n/4) + n/2) + n = 4T(n/4) + 2n = 4(2T(n/8) + n/4) + 2n = 8T(n/8) + 3n = 8*(2T(n/16) + n/8) + 3n = 16T(n/16) + 4n ...... = 2^k * T(n/2^k) + k * n ......

当 T(n/2^k)=T(1) 时,也就是 n/2^k=1,我们得到 k=log2n

public static int[] bubbleSort(int[] a, int n) {
        if (n <= 1) return a;
        for (int i = 0; i < n; i++) {
            boolean br = false;
            for (int j = 0; j < n - i - 1; j++) {
                if (a[j] > a[j + 1]) {
                    int temp = a[j + 1];
                    a[j + 1] = a[j];
                    a[j] = temp;
                    br = true;
                }
            }
            if (!br) break;
            ;

        }
        return a;
    }

    public static int[] insertSort(int[] a, int n) {
        if (n <= 1) return a;
        for (int i = 1; i < n; i++) {
            int value = a[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (a[j] > value) {
                    a[j + 1] = a[j];
                } else {
                    break;
                }
            }
            a[j + 1] = value;
        }
        return a;
    }

    public static int[] selectSort(int[] a, int n) {
        if (n <= 1) return a;
        for (int h = 0; h < n; h++) {
            int min = 9999;
            int pt = 0;
            int i = h;
            for (; i < n; i++) {
                if (a[i] < min) {
                    min = a[i];
                    pt = i;
                }
            }
            int temp = a[pt];
            a[pt] = a[h];
            a[h] = temp;
        }
        return a;
    }

归并排序

public class MergeSort {
    public void run(int[] a,int n){
        sort(a,0,a.length-1);
    }
    public void sort(int[] a,int left,int right){
        if(left>=right) return;
        int middle = getMiddle(left,right);
        sort(a,left,middle);
        sort(a,middle+1,right);
        merge(a,left,middle,right);
        System.out.println(Arrays.toString(a));
    }

    private int getMiddle(int p ,int r){
        return (p+r)/2;
    }
    public void merge(int[] a,int left,int middle,int right){
        int[] tempA =new int[a.length];
        int i = 0;
        int p = left;
        int q = middle+1;
        while(p<=middle && q<=right){
            if(a[p]<=a[q]){
                tempA[i++] = a[p++];
            }else{
                tempA[i++] = a[q++];
            }
        }
        while (p<=left){
           tempA[i++] = a[p++];
        }

        while (q<=right){
            tempA[i++] = a[q++];
        }
        i=0;
        while (left<=right){
            a[left] = tempA[i++];
            left++;
        }
    }
}
 class Main {
    public static void main(String[] args){
        MergeSort a = new MergeSort();
        int[] arr={3,2,5,1,1,6};
        a.run(arr, arr.length);
    }
}

代码心得:

  1. 临时数组下标和原数组的对应 可以和数组下标保持一致,否则需要从0开始赋值.
  2. 注意(p+q)/2的括号
  3. 不要怀疑递推公式和结束条件,打断点跟踪函数是否出了问题,出现的两个问题都是函数实现有问题,例如merge函数拷贝临时数组到源数组时下标没对上
  4. java数组时对象传递,即引用传递.

快速排序

public class QuickSort {
    public void run(int[] a,int n){
        sort(a,0,a.length-1);
    }

    public void sort(int[] a,int left,int right){
        if(left>=right) return;
        int pivot = partition(a,left,right);
        sort(a,left,pivot-1);
        sort(a,pivot+1,right);
        System.out.println(Arrays.toString(a));
    }

    public int partition(int[] a,int left,int right){
        int pivot = a[right];
        int i=left;
        int j=left;
        while(j<right){
            if(a[j]<=pivot){
                swap(a,i,j);
                i++;
            }
            j++;
        }
        swap(a,i,j);
        return i;
    }

    public void swap(int[] a,int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] =temp;
    }
}

class Main {
    public static void main(String[] args){
        QuickSort a = new QuickSort();
        int[] arr={3,2,5,1,6,4};
        a.run(arr, arr.length);
    }
}

要点:

  1. i和j的区分,i指向小于pivot的值,j做循环

性能分析: 递归树 画出递归树,图转载于time.geekbang.org/column/arti…

设每次分组分别为1/10 和9/10 树每一层交换册数为n 1/10分组最长路径为 1/10n n/10的2次幂 ... n/10的k次幂 ... 1 n/10的k次幂 = 1; 解k = log1/10(n) 同理另一条路径的最长路径为 h = log10/9(n).层数*交还次数得: T(n) = O(nlog(n)