排序算法汇总

1,155 阅读2分钟

排序算法汇总

排序算分分析.png

冒泡排序,选泽排序,插入排序(时间复杂度O(N^2) , 空间复杂度 O(1))

三种算法都是Inplace不占据额外空间

选择排序

从未排序部分选择最小值按顺序放入数组前端有序部分

// java 
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len - 1; i++) {
            int minIndex = i;
            for (int j = i+1; j < len; j++) {
                if (nums[j] < nums[minIndex]) {
                    minIndex = j;
                }
            }
            swap(nums, i, minIndex);
        }
        return nums;
    }

选择排序使用贪心思想,当前最优换取全局最优。

优点 : 交换次数最少,交换的成本高可以选择

插入排序

将目标元素插入有序数组中

    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for (int i = 1; i < len; i++) {
            int temp = nums[i];
            int j = i;
            while (j > 0 && nums[j-1] > temp) {
                nums[j] = nums[j-1];
                j --;
            }
            nums[j] = temp;
        }
        return nums;
    }

优点:插入排序可以提前结束循环当前部数组已经有序的时候,可以实现O(N)的时间复杂度。适用于数组规模小的数组。

冒泡排序

两两比较交换位置,每一循环确定最后一位最大值

暴力解法

    public static void bubbleSort(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < n-i; j++) {
                if (nums[j] < nums[j-1])
                    swap(nums, j, j-1)
            }
        }
        return nums;
    }

优化解法: 暴力解法中如果后面为已经有序数组,没有必要继续循环比较,可以设置标志位提前跳出循环

    public int[] void bubbleSort(int[] nums) {
    int k = nums.length;
    boolean flag = true;//发生了交换就为true, 没发生就为false,第一次判断时必须标志位true。
    while (flag){
        flag=false;//每次开始排序前,都设置flag为未排序过
        for(int j=1; j < k; j++){
            if (nums[j-1] > nums[j]){//前面的数字大于后面的数字就交换
                swap(nums, j, j-1);
                //表示交换过数据;
                flag = true;
            }
        }
        k--;//减小一次排序的尾边界
    }//end while
    return nums
}

更加优化: 对于已经排序过的部分不再进行遍历,减少问题规模

    public int[] bubbleSort(int[] nums) {
        int k = nums.length;
        int flag = k;
        while (flag > 0) {
            k = flag;
            flag = 0;
            
            for (int j = 1; j < k; j++) {
                if (nums[j-1] > nums[j]) {
                    swap(nums, j, j-1);
                    
                    flag = j; // 记录最后一次交换的边界位置
                }
            }
        }
        return nums;
    }

优点:实现简单,针对数据规模较小的情况下比较合适。

归并排序,快速排序 Average O(NlogN)

归并排序

归并排序需要额外的空间O(n)

借助额外空间,合并两个有序数组,得到更长的有序数组,使用分治的思想,可以实现并行化。

    public int[] sortArray(int[] nums) {
        int len = nums.length;
        int[] temp = new int[len];
        mergeSort(nums, 0, len-1, temp);
        return nums;
    }
     /**
     * 对数组 nums 的子区间 [left, right] 进行归并排序
     * 优化,当区间范围小于7时可以使用插入排序代替归并
     * @param nums
     * @param left
     * @param right
     * @param temp  用于合并两个有序数组的辅助数组,全局使用一份,避免多次创建和销毁
     */
    void mergeSort(int[] nums, int left, int right, int[] temp) {
        int mid = left + (right - left) / 2;
        // int mid = (left + right) >> 1;
        
        mergeSort(nums, left, mid, temp);
        mergeSort(nums, mid + 1, right, temp);
        // 如果2个区间已经有序,不需要递归继续分裂
        if (nums[mid] <= nums[mid + 1]) {
            return;
        }
        mergeTwoSortedArray(nums, left, mid, right, temp);
    }
    
        /**
     * 合并两个有序数组:先把值复制到临时数组,再合并回去
     *
     * @param nums
     * @param left
     * @param mid   [left, mid] 有序,[mid + 1, right] 有序
     * @param right
     * @param temp  全局使用的临时数组
     */
     
    void mergeTwoSortedArray(int[] nums, int left, int mid, int right, int temp) {
        System.arraycopy(nums, left, temp, left, right-left+1);
        int i = left; //左序列index
        int j = mid + 1; // 有序列指针
        int t = 0; // temp数组index
        
        while (i <= mid +1 && j <= right) {
            if (nums[i] <= nums[j]) {
                temp[t] = nums[i];
                t++;
            } else {
                temp[t] = nums[j];
                j++;
            }
            t++;
        }
        while (i <= mid) {
            temp[t] = nums[i];
            t++;
            i++;
        }
        while (j <= right) {
            temp[t] = nums[j];
            t++;
            j++;
        }
        t = 0;
        
        // 将temp数组考回nums
        while (left <= right) {
            nums[left] = temp[t];
            left++;
            t++;
        }
    }

优点:借助额外空间,实现了稳定排序。

快速排序

平均时间复杂度O(NlogN), 最差情况复杂度为O(N ^ 2)

核心思想类似整理扑克牌,每一次循环确定pivot的具体位置。随机选择pivot可以防止递归子数组大小平均,防止出现最差情况。

    public int[] sortArray(int[] nums) {
        if (nums == null || nums.length == 0)
            return nums;

        quickSort(nums, 0, nums.length-1);
        return nums;
    }

    void quickSort (int[] array, int low, int high){
        if (low >= high) {
            return;
        }
        int i = low, j = high, index = array[i]; // 取最左边的数作为基准数
        while (i < j) {
            while (i < j && array[j] >= index) { // 向左寻找第一个小于index的数
                j--;
            }
            if (i < j) {
                array[i++] = array[j]; // 将array[j]填入array[i],并将i向右移动
            }
            while (i < j && array[i] < index) {// 向右寻找第一个大于index的数
                i++;
            }
            if (i < j) {
                array[j--] = array[i]; // 将array[i]填入array[j],并将j向左移动
            }
        }
        array[i] = index; // 将基准数填入最后的坑
        quickSort(array, low, i - 1); // 递归调用,分治
        quickSort(array, i + 1, high); // 递归调用,分治
    }

优点:平均效率高,对比归并排序不需要额外内存空间,在原地交换。

堆排序 Average O(NlogN)

使用堆进行存储元素,不断对堆进行堆化,然后逐一选出堆顶元素。堆本身就是一个完全二叉树的顺序存储结构,建堆得过程为O(N),调整堆的时间复杂度O(logN)

    public int[] buildHeap(int[] nums) {
        int lastIndex = (nums.length - 2) / 2;
        for (int i = 0; i < lastIndex; i ++) {
            heapify(nums, i, nums.length);
        }
        return nums;
    }
    
    public void heapify(int[] nums, int index, int length) {
        int temp = nums[index];
        for (int i = 2 * index + 1; i < length - 1; i = 2*i+1) {
            // 可以选择直接同事比较三个数的大小
            if (i < length && nums[i] < nums[i+1]) {
                i++;
            }
            if (temp >= nums[i]) {
                break;  // 根节点 >=左右子女中关键字较大者,调整结束
            } else {
                nums[index] = nums[i];
                index = i;
            }
        }
        nums[index] = temp;
    }

    public int[] heapSort(int[] nums) {
        nums = buildHeap(nums);
        for (it i = nums.length-1; i > 1; i--) {
            int temp = nums[0];
            nums[0] = nums[i];
            nums[i] = temp;
            heapfiy(nums, 0, i);
        }
        return nums;
    }

优点:堆排序利用大值或者小值永远优先于子,可用于解决TOPk问题,可以分批读取原数组,防止内存爆满。