*时间复杂度

排序算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
冒泡排序 O( n ^ 2) O( n ^ 2) O(1)
选择排序 O( n ^ 2) O( n ^ 2) O(1) 不是
插入排序 O( n ^ 2) O( n ^ 2) O(1)
归并排序 O( n log n) O( n log n) O(n)
快速排序 O( n log n) O( n ^ 2) O(log n) 不是
堆排序 O( n log n) O( n log n) O(1) 不是

 

1. 冒泡排序

两两对比,较大的数往后面沉。

    public static void bubbleSort(int[] arr) {
        for(int i = arr.length - 1; i > 0; i--) {
            for(int j = 0; j < i; j++) {
                if(arr[j] > arr[j + 1]) {
                    //两两对比,大的数往后面沉
                    swap(arr, j, j + 1);
                }
            }
        }
    }

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

 

2. 选择排序

最前面的数分别与后面所有数对比,找出最小的,交换。

    public static void selectSort(int[] arr) {
        for(int i = 0; i < arr.length; i++) {
            int min = i;
            for(int j = i + 1; j < arr.length; j++) {
                //从当前位置往后面找,选择一个最小的数
                if(arr[j] < arr[min]) {
                    min = j;
                }
            }
            swap(arr, i, min);
        }
    }

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

 

3. 插入排序

类似于打扑克牌,已经在手上的牌有序,新抽的牌插入到有序的位置。

第二个数与第一个数比较,排序。第三个数与前两个数比较,排序。

  public static void insertSort(int[] arr) {
        for(int i = 1; i < arr.length; i++) {
            for(int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j+ 1);
            }
        }
    }

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

 

4. 希尔排序

是简单插入排序的优化版,与插入排序的不同之处在于,会优先比较距离较远的元素,希尔排序又叫缩小增量排序。

public static void shellSort(int[] arr) {
        int temp = 1;
        while(temp < arr.length / 3)
            temp = temp * 3 + 1;
        while(temp >= 1) {
            for(int i = temp; i < arr.length; i++) {
                if(arr[i] < arr[i - temp]) {
                    swap(arr, i, i - temp);
                }
            }
            temp = temp / 3;
        }
        SelectSort s = new SelectSort();
        s.selectSort(arr);
    }
    
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

 

 

5. 归并排序

分治的思想,不断将数组二分,直到不可再分时(即只剩一个元素时)便有序了,然后在将两部分合并的时候,利用外排将两部分合成有序序列

  public static void mergeSort(int[] arr) {
        if(arr == null || arr.length < 2) return;
        mergeSort(arr, 0, arr.length - 1);
    }

    public static void mergeSort(int[] arr, int left, int right) {
        if(left == right) return;
        int mid = left + ((right - left) >> 1);
        //右移一位时除于2,右移 n 位相当于除于 2 的 n 次方
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
        //合并分开的两部分
    }

    public static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        //辅助数组,空间复杂度O(n)
        int index = 0;
        int p1 = left, p2 = mid + 1;
        //通过外排的方式,按照顺序放入辅助数组中
        while(p1 <= mid && p2 <= right) {
            help[index++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid) {
            help[index++] = arr[p1++];
        }
        while(p2 <= right) {
            help[index++] = arr[p2++];
        }
        //将辅助数组中的元素复制回原素组
        for(int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }

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

 

6. 快速排序

经典快排:选择一个元素作为切分元素,左右两个指针分别向中间遍历,当左遇到大于切分元素,而右小于切分元素时,两者交换,继续遍历,直到左边都小于切分元素,右边都大于切分元素,一趟结束。

性能分析:

  • 快排是原地排序,不需要辅助数组,但是递归调用需要辅助栈,快排最好的情况是每次切分元素都正好将数组对半分,这样递归调用的次数是最少的。
  • 实现方法:1. 随机快排,每趟快排之前随机选取一个元素与原定切分元素交换。 2. 三数取中,最好的情况是每次都能取到数组的中位数作为切分元素,但是计算中位数显然不可取,折中的方法是:每趟快排选取3个元素,取中间的值作为切分元素
  • 三向切分优化:大于有大量重复元素的数组,可以将数组划分为三部分,分别对应小于、等于、大于切分元素。对于大量重复元素的随机数组可以在线性时间内完成排序。
    public static void quickSort(int[] arr) {
        if(arr == null || arr.length < 2) return;
        quickSort(arr, 0, arr.length - 1);
    }

    public static void quickSort(int[] arr, int left, int right) {
        if(left < right) {
            swap(arr, right, left + (int)(Math.random()*(right - left + 1)));
            //随机快排实现:随机生成left-right中的下标,然后与right位置的值交换
            int[] result = partition(arr, left, right);
            //三向切分优化快排,返回等于划分right位置的值的前后下标
            quickSort(arr, left, result[0] - 1);
            quickSort(arr, result[1] + 1, right);
        }
    }

    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right;
        int index = left;
        while(index < more) {
            if(arr[index] < arr[right]) {
                //小于基准值,与小于区域的后一个值交换
                swap(arr, index++, ++less);
            } else if(arr[index] > arr[right]) {
                //大于基准值,与大于区域的前一个值交换
                swap(arr, index, --more);
            } else {
                //等于基准值时,继续遍历下一个数
                index++;
            }
        }
        swap(arr, more, right);
        return new int[]{less + 1, more};
    }

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

 

7.堆排序

堆的底层结构使用数组实现,每个结点的父亲结点为 ( i - 1) / 2 ,孩子结点为 2 * i + 1 和 2 * i + 2。

排序过程:先将数组构建成大顶堆,然后将堆顶元素与最后一个元素交换(最后一个元素不一定最小,但一定会比堆顶元素小),交换之后除掉最后一个元素(即原堆顶元素)重新调整为大顶堆。

    public static void heapSort(int[] arr) {
        if(arr == null || arr.length < 2) return;
        for(int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        int size = arr.length - 1 ;
        swap(arr, 0, size);
        while(size > 0) {
            heapify(arr, 0, size--);
            swap(arr, 0, size);
        }
    }
    //构建大顶堆
    public static void heapInsert(int[] arr, int index) {
        //不断与父亲节点比较,交换
        while(arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //当将堆顶与最后一个数交换后,重建构建大顶堆
    public static void heapify(int[] arr, int index, int size) {

        int left = index * 2 + 1;

        while(left < size) {
            int largeSet = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //选择孩子结点中较大的一个
            if(arr[index] > arr[largeSet]) {
                break;
            }
            swap(arr, index, largeSet);
            index = largeSet;
            left = index * 2 + 1;
        }
    }


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

 

posted on 2019-03-15 16:44  BitingCat  阅读(208)  评论(0编辑  收藏  举报