[高频算法题]——排序(Java实现)

NC140:排序

链接: 排序.

1.插入排序

解题思路:

插入排序

代码实现:

    public static void insertSort(int[] arr) {
        int bound = 1;
        // [0, bound) 是已排序区间.
        // [bound, length) 待排序区间.
        for (; bound < arr.length; bound++) {
            int v = arr[bound];
            int cur = bound - 1;
            for (; cur >= 0; cur--) {
                if (arr[cur] > v) {
                    arr[cur + 1] = arr[cur];
                } else {
                    break;
                }
            }
            arr[cur + 1] = v;
        }
    }

时间复杂度 :O(N^2)
空间复杂度 :O(1)
稳定性:稳定
性质:
a)当前数组比较短,排序速度很快
b)当前数组相对有序,排序速度也很快

2.希尔排序

解题思路:

希尔排序

代码实现:

 public static void shellSort(int[] arr) {
        // 指定 gap 序列, len/2, len/4, len/8,...,1
        int gap = arr.length / 2;
        while (gap >= 1) {
            _shellSort(arr, gap);
            gap = gap / 2;
        }
    }

    public static void _shellSort(int[] arr, int gap) {
        // 进行分组插排. 分组依据就是 gap.
        // gap 同时也表示分的组数.
        // 同组的相邻元素, 下标差值就是 gap
        // 下面的代码其实和插入排序是一样的. 尤其是把 gap 设为 1
        int bound = gap;
        for (; bound < arr.length; bound++) {
            int v = arr[bound];
            int cur = bound - gap;
            for (; cur >= 0; cur -= gap) {
                if (arr[cur] > v) {
                    // 进行搬运
                    arr[cur + gap] = arr[cur];
                } else {
                    break;
                }
            }
            arr[cur + gap] = v;
        }
    }

时间复杂度 :O(N^2) --> O(N^1.3)
空间复杂度 :O(1)
稳定性:不稳定

3.选择排序

解题思路:

选择排序

代码实现:

public static void selectSort(int[] arr) {
        // 创建一个变量 bound 表示已排序区间和待排序区间的边界.
        // [0, bound) 已排序区间
        // [bound, length) 待排序区间
        int bound = 0;
        for (; bound < arr.length; bound++) {
            // 里层循环要进行打擂台的过程.
            // 擂台的位置就是 bound 下标的位置
            for (int cur = bound + 1; cur < arr.length; cur++) {
                if (arr[cur] < arr[bound]) {
                    // 如果发现挑战者比擂主小, 就交换两个元素
                    swap(arr, cur, bound);
                }
            }
        }
    }

时间复杂度 :O(N^2)
空间复杂度 :O(1)
稳定性:不稳定

4.堆排序

解题思路:

堆排序

代码实现:

public static void heapSort(int[] arr) {
        // 1. 先进行建堆
        createHeap(arr);
        // 2. 循环进行交换堆顶元素和最后一个元素的过程, 并且删除该元素, 进行向下调整
        int heapSize = arr.length;
        for (int i = 0; i < arr.length; i++) {
            swap(arr, 0, heapSize - 1);
            // 删除最后一个元素
            heapSize--;
            // 从 0 这个位置进行向下调整
            shiftDown(arr, heapSize, 0);
        }
    }

    public static void shiftDown(int[] arr, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;
        while (child < size) {
            if (child + 1 < size && arr[child + 1] > arr[child]) {
                child = child + 1;
            }
            // 经过上面的 if 之后, child 就指向了左右子树中的较大值.
            // 比较 child 和 parent 的大小
            if (arr[parent] < arr[child]) {
                // 不符合大堆要求
                swap(arr, parent, child);
            } else {
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }

    public static void createHeap(int[] arr) {
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(arr, arr.length, i);
        }
    }

    public static void swap(int[] arr, int x, int y) {
        int tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }

时间复杂度 :O(NlogN)
空间复杂度 :O(1)
稳定性:不稳定

5.冒泡排序

解题思路:

冒泡排序

代码实现:

public static void bubbleSort(int[] arr) {
        // [0, bound) 已排序区间
        // [bound, length) 待排序区间
        int bound = 0;
        for (; bound < arr.length; bound++) {
        boolean isSorted = true;
            for (int cur = arr.length - 1; cur > bound; cur--) {
                if (arr[cur] < arr[cur - 1]) {
                    // 不符合升序 , 交换
                    swap(arr, cur, cur - 1);
                    isSorted = false;
                }
            }
            if(isSorted){
            	break;
            }
        }
    }
    public static void swap(int[] arr, int x, int y) {
        int tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }

时间复杂度 :O(N^2)
空间复杂度 :O(1)
稳定性:稳定

6.快速排序

解题思路:

快速排序

代码实现:

  public static void quickSort(int[] arr) {
        // 使用一个辅助方法进行递归.
        // 辅助方法多了两个参数, 用来表示针对数组上的哪个区间
        // 进行整理
       _quickSort(arr, 0, arr.length - 1);
    }

    // [left, right]
    public static void _quickSort(int[] arr, int left, int right) {
        if (left >= right) {
            // 如果区间为空或者区间只有一个元素, 不必排序
            return;
        }
        // 使用 partition 方法来进行刚才描述的整理过程
        // index 就是 left 和 right 重合的位置, 整理之后的基准值的位置
        int index = partition(arr, left, right);
        // 递归处理左半区间
        _quickSort(arr, left, index - 1);
        // 递归处理右半区间
        _quickSort(arr, index + 1, right);
    }

    public static int partition(int[] arr, int left, int right) {
        // 选取基准值
        int v = arr[right];
        int i = left;
        int j = right;
        while (i < j) {
            // 先从左往右找到一个比基准值大的元素
            while (i < j && arr[i] <= v) {
                i++;
            }
            // 再从右往左找到一个比基准值小的元素
            while (i < j && arr[j] >= v) {
                j--;
            }
            swap(arr, i, j);
        }
        // 如果发现 i 和 j 重叠了, 此时就需要把当前基准值元素
        // 和 i j 重叠位置进行交换
        swap(arr, i, right);
        return i;
    }
     public static void swap(int[] arr, int x, int y) {
        int tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }

时间复杂度 :
平均:O(NlogN)
最坏:O(N^2) (这个数组正好是逆序的时候)
空间复杂度 : 取决于递归的深度
平均:O(logN)
最坏:O(N)
稳定性:不稳定
快速排序优化

7.归并排序

解题思路:

归并排序

代码实现:

 public static void mergeSort(int[] arr) {
        // 创建一个新的方法辅助递归. 新方法中多了两个参数
        // 表示是针对当前数组中的哪个部分进行排序
        // 前闭后开区间
        _mergeSort(arr, 0, arr.length);
    }

    // [left, right) 前闭后开区间
    // right - left 区间中的元素个数
    public static void _mergeSort(int[] arr, int left, int right) {
        if (right - left <= 1) {
            // 如果当前待排序的区间里只有 1 个元素或者没有元素
            // 就直接返回, 不需要任何排序动作
            return;
        }
        // 先把当前 [left, right) 区间一分为二
        int mid = (left + right) / 2;
        // 分成了两个区间
        // [left, mid)  [mid, right)
        // 当左侧区间的 _mergeSort 执行完毕后,
        // 就认为 [left, mid) 就已经是有序区间了
        _mergeSort(arr, left, mid);
        // 当右侧区间的 _mergeSort 执行完毕后,
        // 就认为 [mid, right) 就已经是有序区间了
        _mergeSort(arr, mid, right);
        // 接下来把左右两个有序的数组, 合并到一起!!
        merge(arr, left, mid, right);
    }

    // merge 方法本身功能是把两个有序数组合并成一个有序数组.
    // 待合并的两个数组就分别是:
    // [left, mid)
    // [mid, right)
    public static void merge(int[] arr, int left, int mid, int right) {
        if (left >= right) {
            return;
        }
        // 创建一个临时的数组, 用来存放合并结果.
        // 我们是希望这个数组能存下合并后的结果  right - left
        int[] tmp = new int[right - left];
        // 当前要把新的元素放到 tmp 数组的哪个下标上
        int tmpSize = 0;
        int l = left;
        int r = mid;
        while (l < mid && r < right) {
            // 归并排序是稳定排序!!
            // 此处的条件不要写作 arr[l] < arr[r]
            if (arr[l] <= arr[r]) {
                // arr[l] 比较小, 就把这个元素先插入到 tmp 数组末尾
                tmp[tmpSize] = arr[l];
                tmpSize++;
                l++;
            } else {
                // arr[r] 比较小, 就把这个元素插入到 tmp 数组的末尾
                tmp[tmpSize] = arr[r];
                tmpSize++;
                r++;
            }
        }
        // 当其中一个数组遍历完了之后, 就把另外一个数组的剩余部分都拷贝过来
        while (l < mid) {
            // 剩下的是左半边数组
            tmp[tmpSize] = arr[l];
            tmpSize++;
            l++;
        }
        while (r < right) {
            // 剩下的是右半边数组
            tmp[tmpSize] = arr[r];
            tmpSize++;
            r++;
        }
        // 最后一步, 再把临时空间的内容都拷贝回参数数组中.
        // 需要把 tmp 中的内容拷贝回 arr 的 [left, right) 这一段空间里
        // [left, right) 这个空间很可能不是从 0 开始的额.
        for (int i = 0; i < tmp.length; i++) {
            arr[left + i] = tmp[i];
        }
    }

时间复杂度 :O(NlogN)
空间复杂度 :O(N)
稳定性:稳定

总结:

排序算法导图

排序方法最好平均最坏空间复杂度稳定性
插入排序O(n)O(n^2)O(n^2)O(1)稳定
希尔排序O(n^1.3)O(nlog(n))~O(n^2)O(n^2)O(1)不稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
堆排序O(nlog(n))O(nlog(n))O(nlog(n))O(1)不稳定
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
快速排序O(nlog(n))O(nlog(n))O(n^2)O(log(n)~O(n))不稳定
归并排序O(nlog(n))O(nlog(n))O(nlog(n))O(n)稳定
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值