7种基本排序方法

TopK问题

 排序

1、排序的稳定性

2、插入排序

/**
     * 插入排序
     *
     * 时间复杂度:
     * 最好情况:O(n) 当 数组已经有序
     * 最坏情况:O(n^2) 当 数据是逆序
     * 平均情况:O(n^2)
     * 对于插入排序,数组越趋于有序,耗时越少
     *
     * 空间复杂度:
     * 最好/平均/最坏:O(1)
     *
     * 稳定性:稳定
     * @param array
     */
    public static void insert2(long[] array) {
        for (int i = 1; i < array.length; i++) {    // 循环 n - 1 次
            // 有序区间: [0, i)
            // 无序区间: [i, array.length)

            long x = array[i];
            int j;
            for (j = i - 1; j >= 0 && array[j] > x; j--) {
                array[j + 1] = array[j];
            }

            array[j + 1] = x;
        }
    }

    public static void insertSort(long[] array) {
        // 要对 array.length 个元素做排序,需要多少次 查找+插入 的过程?
        // array.length - 1

        // 外层的循环:需要进行很多次的 查找+插入
        for (int i = 0; i < array.length - 1; i++) {
            // 有序区间
            // 第一次(i == 0): [0, 0] / [0, 1)
            // 第二次(i == 1): [0, 1] / [0, 2)
            // ...
            // 第 i - 1 次(i): [0, i] / [0, i + 1)

            // 有序区间:[0, i]
            // 无序区间:[i + 1, array.length)
            // 要插入的元素的下标是无序区间的第一个元素(紧挨着有序区间的下一个元素)
            long x = array[i + 1];

            // 查找并插入,遍历整个有序区间: [0, i]
            int j;
            for (j = i; j >= 0 && array[j] > x; j--) {
                array[j + 1] = array[j];
            }
            array[j + 1] = x;
        }
    }

    public static void 查找并插入整理版本(long[] array, int index) {
        // 有序区间: [0, index)
        long x = array[index];

        int i;
        for (i = index - 1; i >= 0 && array[i] > x; i--) {
            array[i + 1] = array[i];
        }
        array[i + 1] = x;
    }

3、冒泡排序

/**
     * 冒泡排序:
     *
     * 时间复杂度:
     * 最好情况:O(n) 当 数组已经有序
     * 平均情况:O(n^2)
     * 最坏情况:O(n^2)  当 数组是逆序的
     *
     * 空间复杂度:
     * 最好/平均/最坏:O(1)
     *
     * 稳定性:具备稳定性
     * @param array
     */
    public static void bubbleSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {    // 需要 n - 1 次冒泡过程
            // 无序区间 [0, array.length - i)
            // 遍历整个无序区间,但是确保相邻的元素两两比较,所以
            // 要遍历的下标范围 [0, array.length - i - 1)

            // 每次冒泡过程时,假设无序区间是有序的
            boolean sorted = true;

            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    // 这里不加等号,加等号就失去稳定性
                    swap(array, j, j + 1);

                    sorted = false;     // 存在 前面的元素 > 后边的元素,说明假设无序区间有序的假设不成立

                    // 冒泡的过程中,一旦发生了交换,说明
                    // 存在:相邻的两个元素之间,前面的元素 > 后边的元素
                    // 反过来说:单次冒泡给过程,如果一次交换都没有发生过
                    // 说明不存在:相邻的两个元素之间,前面的元素 > 后边的元素
                    // 任意相邻的两个元素之间,前面的元素 <= 后边的元素
                    // 冒泡过程只在无序区间进行
                    // 无序区间的任意相邻的两个元素满足:前面的元素 <= 后边的元素
                    // 我们认为的无序区间是有序的
                    // 所以,整个数组已经全部是有序的了
                }
            }

            // 冒泡过程完成之后,如果 sorted == true,说明一次交换没发生过
            if (sorted) {
                return;
            }
        }
    }

4、选择排序

public static void selectSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {    // 一共进行 n - 1 次选择过程
            // [无序区间] [有序区间]
            // 无序区间: [0, array.length - i)
            // 有序区间: [array.length - i, array.length)

            // 1. 遍历整个无序区间,找到最大的元素所在的下标(不需要知道最大的元素是多少,而是它在哪)
            int maxIdx = 0;
            // 在剩余位置,继续找比 array[maxIdx] 元素还大的元素
            for (int j = 1; j < array.length - i; j++) {
                // 期间,如果出现更的元素,则更新最大元素所在下标
                if (array[j] > array[maxIdx]) {
                    maxIdx = j;
                }
            }

            // 最大的元素在 [maxIdx],即 array[maxIdx] 是无序区间的最大元素
            // 把最大的元素放到无序区间的最后一个位置
            // [0, array.length - i)
            // 无序区间的最后一个位置下标是 [array.length - i - 1]
            swap(array, maxIdx, array.length - i - 1);
        }
    }

    /**
     * 选择排序:
     *
     * 时间复杂度:
     * 最好/平均/最坏:O(n^2)
     *
     * 空间复杂度:
     * 最好/平均/最坏:O(1)
     *
     * 稳定性:保证不了稳定性
     * @param array
     */
    public static void selectSort2(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            // 有序区间: [0, i)
            // 无序区间: [i, array.length)

            int minIdx = i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIdx]) {
                    minIdx = j;
                }
            }

            // 交换 [minIdx] 和 [i] 位置的元素
            swap(array, minIdx, i);
        }
    }

5、希尔排序

/**
     * 希尔排序
     *
     * 时间复杂度: O(n ^ 1.25)
     *
     * 空间复杂度: O(1)
     *
     * 稳定性: 不稳定
     * @param array
     */
    public static void shellSort(long[] array) {
        if (array.length == 0) {
            return;
        }
        int gap = array.length / 2;
        while (true) {
            // 带有间隔的分组插排
            for (int i = gap; i < array.length; i++) {
                long x = array[i];
                int j;
                for (j = i - gap; j >= 0 && array[j] > x; j = j - gap) {
                    array[j + gap] = array[j];
                }

                array[j + gap] = x;
            }

            if (gap == 1) {
                // 说明上一次的分组插排其实最终的插排
                break;
            }

            gap = gap / 2;
        }
    }

6、堆排序

public static void adjustDown大堆(long[] array, int size, int index) {
        while (2 * index + 1 < size) {
            int maxIdx = 2 * index + 1;
            if (maxIdx + 1 < size && array[maxIdx + 1] > array[maxIdx]) {
                maxIdx++;
            }

            if (array[index] >= array[maxIdx]) {
                return;
            }

            swap(array, index, maxIdx);

            index = maxIdx;
        }
    }

    public static void createHeap大堆(long[] array, int size) {
        for (int i = (size - 2) / 2; i >= 0; i--) {
            adjustDown大堆(array, size, i);
        }
    }

    /**
     * 堆排序:
     *
     * 时间复杂度:
     * 最好/平均/最坏:O(n * log(n))
     *
     * 空间复杂度:O(1)
     *
     * 稳定性:不稳定(向下调整的过程中,相对位置可能被破坏)
     * @param array
     */
    public static void heapSort(long[] array) {
        // 将整个数组(整个数组都是无序区间)创建成大堆
        createHeap大堆(array, array.length);      // O(n) or O(n * log(n))

        // 循环选择的过程,一共需要 n - 1 次 O(n * log(n))
        for (int i = 0; i < array.length - 1; i++) {    // 循环 n 次
            // 无序区间: [0, array.length - i)
            // 交换最大的元素(下标是 [0])和无序区间的最后一个元素(下标是 array.length - i - 1)
            swap(array, 0, array.length - i - 1);   // O(1)
            // 由于最大的元素已经放到最后了,所以,无序区间:[0, array.length - i - 1)
            // 无序区间的元素个数: array.length - i - 1 - 0 = array.length - i - 1
            // 为了维护无序区间的大堆性质,对无序区间剩余的元素,在 [0] 做向下调整
            adjustDown大堆(array, array.length - i - 1, 0);   // O(log(n))
        }
    }

    public static void main(String[] args) {
        long[] array = {3, 9, 1, 4, 5, 8, 2, 6, 0, 7};
        heapSort(array);

        System.out.println(Arrays.toString(array));
    }
}

7、快速排序

8、hoare法

9、挖坑法

10、前后指针

 

public static void 进阶版Partition(long[] array, int from, int to) {
        long pivot = array[to];
        /*
        [from, b)  小于基准值
        [b, d)     等于基准值
        [d, g]     未比较的元素
        (g, to]    大于基准值
         */

        int b = from;
        int d = from;
        int g = to;

        // [d, g] 这个区间内没有元素时停止; d <= g 说明还有元素
        while (d <= g) {
            if (array[d] == pivot) {
                d++;
            } else if (array[d] < pivot) {
                swap(array, d, b);
                b++;
                d++;
            } else {
                // array[d] > pivot
                swap(array, d, g);
                g--;
            }
        }
    }

11、归并排序

/**
     * 归并排序
     *
     * 时间复杂度:
     * 最好/平均/最坏: O(n * log(n))
     *
     * 空间复杂度:
     * 最好/平均/最坏: O(n)
     *
     * 稳定型:具备稳定性
     * @param array
     */
    public static void mergeSort(long[] array) {
        mergeSortInternal(array, 0, array.length);
    }

    // 待排序区间:[from, to)
    private static void mergeSortInternal(long[] array, int from, int to) {
        if (to <= 1 + from) {
            return;
        }

        // 把待排序区间对半分开
        // 找到中间位置下标
        int mid = from + (to - from) / 2;
        // 整个区间被切割成
        // [from, mid)
        // [mid, to)
        // 按照相同的方式,先把左右两个区间变得有序
        mergeSortInternal(array, from, mid);
        mergeSortInternal(array, mid, to);

        // [from, mid)   有序了
        // [mid, to)     有序了

        merge(array, from, mid, to);
    }

    private static void merge(long[] array, int from, int mid, int to) {
        int size = to - from;
        // 需要的额外空间
        long[] e = new long[size];
        int eIdx = 0;

        int leftIdx = from; // 左边区间要处理的元素的下标
        int rightIdx = mid; // 右边区间要处理的元素的下标
        // 什么条件表示左边区间 [from, mid) 就没有元素了   leftIdx == mid
        // 同理右边区间:rightIdx == to
        // 两个区间都有元素的时候才要比较
        // 左区间有元素:leftIdx < mid
        // 右: rightIdx < to
        // leftIdx < mid && rightIdx < to
        while (leftIdx < mid && rightIdx < to) {
            // 要比较的两个元素 array[leftIdx] 和 array[rightIdx]
            if (array[leftIdx] <= array[rightIdx]) {
                // 取左边的元素放入 e 数组
                e[eIdx] = array[leftIdx];
                eIdx++;
                leftIdx++;
            } else {
                // 取右边的元素放入 e 数组
                e[eIdx] = array[rightIdx];
                eIdx++;
                rightIdx++;
            }
        }

        // 说明有一个区间的元素被全部取完了
        if (leftIdx < mid) {
            // 右边取完了,把左边的所有元素依次放到 e 中
            while (leftIdx < mid) {
                e[eIdx] = array[leftIdx];
                eIdx++;
                leftIdx++;
            }
        } else {
            while (rightIdx < to) {
                e[eIdx] = array[rightIdx];
                eIdx++;
                rightIdx++;
            }
        }

        // e 里是有序的 [0, size)
        // 要把有序的元素从 e 中搬回到 array 中 [from, to)

        for (int i = 0; i < size; i++) {
            array[from + i] = e[i];
        }
    }

12、总结

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值