排序算法


前言

在生活中,我们离不开排序,在数据结构中,排序算法也是很重要很经典的知识,更是每个程序员都必须得掌握的,在本篇我将总结一下常用的排序算法,便于自己以后复习。

冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。其算法思想是从要排序的数列的一端开始向另一端冒泡,也就是把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相邻元素时,位置不变。

img

原始冒泡排序

基本过程
  • 相邻的元素两两比较,如果第一个比第二个大,就交换它们两个,这样在最后的元素将会是最大的数。
  • 两层嵌套循环,外层冒泡轮数,里层依次比较,最后排序完成。
代码实现
/**
     * 原始冒泡排序
     * @param array
     */
    public void bubbleSort(int[] array) {
        int tmp;
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - i - 1; j++) {
                // 相邻元素两两比较
                if (array[j] > array[j + 1]) {
                    // 元素交换
                    tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                }
            }
        }
    }

冒泡排序优化1-有序标记

冒泡排序算法的前提是无论数列是否有序无序,都会进行双层循环,但是如果数列在几轮排序之后已经有序了,没必要继续循环,浪费资源,在这种情况下,如果能判断出数列已经有序,并做出标记,那么剩下的几轮排序就不必执行了。

代码实现
/**
     * 冒泡排序优化-有序标记
     * @param array
     */
    public void bubbleSortIsSort(int[] array) {
        int tmp;
        for (int i = 0; i < array.length - 1; i++) {
            //有序标记,每一轮的初始值都是true
            boolean isSorted = true;
            for (int j = 0; j < array.length - i - 1; j++) {
                // 相邻元素两两比较
                if (array[j] > array[j + 1]) {
                    // 元素交换
                    tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                    //因为有元素进行交换,所以不是有序的,标记变为false
                    isSorted = false;
                }
            }
            // 如果当前标记为true,则说明已是有序数组,直接跳出
            if (isSorted) {
                break;
            }
        }
    }

冒泡排序优化2-有序区界定

在上一步优化的基础上还可以继续优化,如果数列已经有一大部分原始是排好序的,比如数组{3,2,5,7,9},可以看到后边{5,7,9}已经是排好序的,那我们就没必要继续进行比较了,那么,该如何避免这种情况呢?我们可以在每一轮排序后,记录下来最后一次元素交换的位置,该位置即为无序数列的边界,再往后就是有序区了。

代码实现
/**
 * 冒泡排序优化-有序区界定
 * @param array
 */
public void bubbleSortBorder(int[] array) {
    int tmp;
    // 最后一次交换的位置
    int lastExchangeIndex = 0;
    //无序数列的边界
    int sortBorder = array.length - 1;
    for (int i = 0; i < array.length - 1; i++) {
        //有序标记,每一轮的初始值都是true
        boolean isSorted = true;
        for (int j = 0; j < sortBorder; j++) {
            // 相邻元素两两比较
            if (array[j] > array[j + 1]) {
                // 元素交换
                tmp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = tmp;
                //因为有元素进行交换,所以不是有序的,标记变为false
                isSorted = false;
                // 更新为最后一次交换元素的位置
                lastExchangeIndex = j;
            }
        }
        sortBorder = lastExchangeIndex;
        // 如果当前标记为true,则说明已是有序数组,直接跳出
        if (isSorted) {
            break;
        }
    }
}

鸡尾酒排序

鸡尾酒排序算法的思想

在无规律排放的数组中,先从左到右冒泡排序,则最大的元素去到最右端;再先从右到左冒泡排序,则最大的元素去到最左端。以此类推,直到完成排序。

考虑这样的一个序列:{2,3,4,5,1} 。如果使用鸡尾酒排序,一个来回就可以搞定;而冒泡排序则需要跑四趟。

img

代码实现
/**
     * 冒泡排序优化-鸡尾酒排序(适用于大部分元素已经有序的情况)
     * @param array
     */
    public void bubbleSortTwo(int[] array) {
        int tmp;
        for (int i = 0; i < array.length / 2; i++) {
            // 有序标记
            boolean isSorted = true;
            //奇数轮,从左向右比较和交换
            for (int j = 0; j < array.length - i - 1; j++) {
                // 相邻元素两两比较
                if (array[j] > array[j + 1]) {
                    tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                    isSorted = false;
                }
            }
            if (isSorted) {
                break;
            }

            // 在偶数轮之前,将isSorted重新标记为true
            isSorted = true;
            // 偶数轮,从右向左比较和交换
            for (int j = array.length - i - 1; j > i; j--) {
                // 相邻元素两两比较
                if (array[j] < array[j - 1]) {
                    tmp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = tmp;
                    isSorted = false;
                }
            }
            if (isSorted) {
                break;
            }
        }
    }

鸡尾酒排序优化

同冒泡排序的有序区界定优化类似,我们可以在每一轮排序后,记录下来最后一次元素交换的位置,该位置即为无序数列的边界,再往后就是有序区了。因为鸡尾酒是两边循环交替,因此需要设置两个边界值来判断。

代码实现
/**
     * 鸡尾酒排序优化-有序区界定(适用于大部分元素已经有序的情况)
     * @param array
     */
    public void bubbleSortTwoBorder(int[] array) {
        int tmp;
        // 左侧最后一次交换的位置
        int leftExchangeIndex = 0;
        // 右侧最后一次交换的位置
        int rightExchangeIndex = 0;
        // 无序数组的左边界
        int leftSortBorder = 0;
        // 无序数组的右边界
        int rightSortBorder = array.length - 1;
        for (int i = 0; i < array.length / 2; i++) {
            // 有序标记
            boolean isSorted = true;
            //奇数轮
            for (int j = leftSortBorder; j < rightSortBorder; j++) {
                // 相邻元素两两比较
                if (array[j] > array[j + 1]) {
                    tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                    isSorted = false;
                    rightExchangeIndex = j;
                }
            }
            rightSortBorder = rightExchangeIndex;
            if (isSorted) {
                break;
            }

            // 偶数轮
            isSorted = true;
            for (int j = rightSortBorder; j > leftSortBorder; j--) {
                // 相邻元素两两比较
                if (array[j] < array[j - 1]) {
                    tmp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = tmp;
                    isSorted = false;
                    leftExchangeIndex = j;
                }
            }
            leftSortBorder = leftExchangeIndex;
            if (isSorted) {
                break;
            }
        }
    }

性质

时间复杂度 O(n)
空间复杂度 O(1)
稳定性 稳定

面经:

https://www.nowcoder.com/discuss/411580?type=2

https://www.nowcoder.com/discuss/406201


评论
评论
  目录