Java排序算法总结


序言

排序算法是面试过程中经常会被问到的基础知识,今天,我们来总结一下比较常见的几种排序算法:直接插入排序、希尔排序、冒泡排序、选择排序、快速排序、堆排序、归并排序、桶排序、基数排序

准备知识

在介绍各种排序算法之前,我们先来熟悉一些概念,包括时间复杂度空间复杂度算法稳定性

1. 时间复杂度

时间频度:一个算法执行所消耗的时间,从理论上是不能算出来的,必须在机器上运行才知道。但我们不可能也没必要对每个算法上机测试,只需要知道那个算法花费时间多,那个花费时间少即可。并且算法花费时间与算法语句执行次数成正比。一个算法中语句执行次数成为语句频度或时间频度,用T(n)表示。

时间复杂度:时间频度T(n)中,n是问题的规模,当n不断变化时,T(n)也会发生变化。现在我们想知道这个变化的规律,因此引入时间复杂度概念。常见的时间复杂度有:常数阶O(1)、线性阶O(n)、线性对数阶O(nlog2n)、平方阶O(n²)、立方阶O(n³)、k次方阶O(nk),指数阶O(2n)。随着规模n的增大,上述时间复杂度不断增大,算法执行效率越低。

2. 空间复杂度

一个算法的空间复杂度是指算法所耗费的存储空间,它也是问题规模n的函数,主要包括存储算法本身占用的空间、算法输入输出数据占用的空间、算法运算过程中临时占用的空间

3. 算法稳定性

通俗理解,就是保证两个相等的数,排序前和排序后位置保持不变。形式化表示:Ai == Aj,排序前Ai在Aj前面,那么排序后,Ai仍然在Aj前面。

常见算法

1. 直接插入排序

/**
 * 名称:1. 插入排序
 * 解释:找到当前元素在子数组(包括自身和之前的元素)中的位置。每次把当前元素插入到正确的位置。
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 * 稳定
 */
public static void insertionSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    int i, j, tmp;
    for (i = 1; i < array.length; i++) {
        tmp = array[i];
        for (j = i; j > 0; j--) {
            if (tmp < array[j - 1]) {
                array[j] = array[j - 1];
                continue;
            }
            break;
        }
        array[j] = tmp;
    }
    System.out.println("插入排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

2. 希尔排序

/**
 * 名称:2. 希尔排序
 * 解释:选择步长gap(gap按一定规则减小,最后为1),i从gap开始向后遍历,每个元素都与(i-gap)所在元素进行比较,直至所有元素排序成功
 * 插入排序是希尔排序的特殊情况(gap=1)
 * 时间复杂度:平均O(n^1.3),与gap相关
 * 空间复杂度:O(1)
 * 不稳定
 */
public static void shellSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    int gap, i, j, tmp;
    for (gap = array.length / 2; gap > 0; gap = gap / 2) {
        for (i = gap; i < array.length; i++) {
            tmp = array[i];
            for (j = i; j >= gap; j = j - gap) {
                if (tmp < array[j - gap]) {
                    array[j] = array[j - gap];
                    continue;
                }
                break;
            }
            array[j] = tmp;
        }
    }
    System.out.println("希尔排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

3. 冒泡排序

/**
 * 名称:3. 冒泡排序,相邻两个比较,反序则交换位置,每次会把(n-1-i)位置的数排好,即每次排好最大的元素
 * 与插入排序的区别:插入排序每次把当前元素在它之前的数组找到位置,冒泡排序每次排好最后一个元素
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 * 稳定
 */
public static void bubbleSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    int i, j, tmp;
    for (i = 0; i < array.length - 1; i++) {
        for (j = 0; j < array.length - 1 - i; j++) {
            if (array[j + 1] < array[j]) {
                tmp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = tmp;
            }
        }
    }
    System.out.println("冒泡排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

4. 选择排序

/**
 * 名称:4. 选择排序,从i开始,每次从子数组(i->length)中找到最小的元素,和位置i的元素进行交换,即每次排好最小的元素
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 * 不稳定
 */
public static void selectSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    int i, j, min, tmp;
    for (i = 0; i < array.length - 1; i++) {
        min = array[i];
        for (j = i + 1; j < array.length; j++) {
            if (array[j] < min) {
                tmp = min;
                min = array[j];
                array[j] = tmp;
            }
        }
        array[i] = min;
    }
    System.out.println("选择排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

5. 快速排序

/**
 * 名称:5. 快速排序,以某个元素为锚点,从右边开始找比锚点小的元素,从左边开始找比锚点大的元素,交换二者,递归进行直至排序完成
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(logn),因为递归调用
 * 不稳定
 */
public static void quickSort(int[] array, int left, int right) {
    if (array == null && array.length == 0) {
        return;
    }
    int i, j, anchor, tmp;
    if (left > right) {
        return;
    }
    anchor = array[left];
    i = left;
    j = right;
    while (i < j) {
        //从右边开始找到小于anchor的数(必须从右边开始找,因为最后结束的那个元素需要与锚点交换位置,这个元素必须比锚点小)
        while (array[j] >= anchor && j > i) {
            j--;
        }
        //从左边开始找到大于anchor的数
        while (array[i] <= anchor && i < j) {
            i++;
        }
        //交换两者
        if (i < j) {
            tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }
    //交换锚点和ij相交元素
    array[left] = array[j];
    array[j] = anchor;

    //递归继续排列左右两边的元素
    quickSort(array, left, j - 1);
    quickSort(array, j + 1, right);
}

private static void testQuickSort(int[] array) {
    quickSort(array, 0, array.length - 1);
    System.out.println("快速排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

6. 堆排序

/**
 * 名称:6. 堆排序,把待排序数组当做一个完全二叉树(特点left=parent*2+1 right=parent*2+2),然后对二叉树进行一次遍历排序,使得堆顶元素最大,把最大元素放到最后面,然后重复上述步骤
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(1)
 * 不稳定
 */
public static void heapSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }

    int p, i, tmp, left, right, maxIndex;
    //从末尾开始逐步缩小待排序数组,直至排序完成
    for (p = array.length - 1; p > 0; p--) {
        //对每个节点进行一次排序,达到所有父节点 > 子节点
        for (i = (p + 1) / 2 - 1; i >= 0; i--) {
            left = i * 2 + 1;
            right = i * 2 + 2;
            maxIndex = i;
            if (left <= p && array[left] > array[maxIndex]) {
                maxIndex = left;
            }
            if (right <= p && array[right] > array[maxIndex]) {
                maxIndex = right;
            }
            //把虽大元素交换到父节点
            if (maxIndex != i) {
                tmp = array[i];
                array[i] = array[maxIndex];
                array[maxIndex] = tmp;
            }
        }
        //把堆顶元素放到数组末尾
        tmp = array[0];
        array[0] = array[p];
        array[p] = tmp;
    }

    System.out.println("堆排序结果:");
    for (int k = 0; k < array.length; k++) {
        System.out.println(array[k]);
    }
}

7. 归并排序

/**
 * 名称:7. 归并排序,把一个很长的数组逐步分成小数组排序,然后又合并为大的数组排序
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(n),因为递归调用
 * 稳定
 */
public static void mergeSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    internalSort(array, 0, array.length - 1);
    System.out.println("归并序结果:");
    for (int k = 0; k < array.length; k++) {
        System.out.println(array[k]);
    }
}

private static void internalSort(int[] array, int left, int right) {
    if (left < right) {
        int middle = (left + right) / 2;
        internalSort(array, left, middle);
        internalSort(array, middle + 1, right);
        sort(array, left, middle, right);
    }
}

private static void sort(int[] array, int left, int middle, int right) {
    int i = left;
    int j = middle + 1;
    int[] tmp = new int[array.length];
    int index = left;
    while (i <= middle && j <= right) {
        if (array[i] < array[j]) {
            tmp[index++] = array[i++];
        } else {
            tmp[index++] = array[j++];
        }
    }
    while (i <= middle) {
        tmp[index++] = array[i++];
    }
    while (j <= right) {
        tmp[index++] = array[j++];
    }

    for (int p = left; p <= right; p++) {
        array[p] = tmp[p];
    }

}

8. 桶排序

/**
 * 名称:8. 桶排序,把最大元素和最小元素的差值划分为若干区间,然后把每个数加入到对应的区间,对每个区间排序,最后把各区间连接起来
 * 时间复杂度:O(n+k)
 * 空间复杂度:O(n+k)
 * 依赖桶内排序算法(如果用到桶内排序用到快排,则不稳定)
 */
public static void bucketSort(int[] array) {
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (int i = 0; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
        if (array[i] < min) {
            min = array[i];
        }
    }
    //根据数组元素区间获取桶数量
    int bucketNum = (max - min) / array.length + 1;
    ArrayList> buckets = new ArrayList<>(bucketNum);
    for (int i=0; i());
    }
    //把元素加入到桶中
    for (int i=0; i

9. 基数排序

/**
 * 名称:9. 基数排序,从个位开始(循环到最高位),把每个元素按照取模值放入二维数组对应的位置,一个次循环完毕之后,
 *      把二维数组中的元素又放回到原数组中
 * 时间复杂度:O(kn)
 * 空间复杂度:O(kn)
 * 稳定
 */
public static void radixSort(int[] array) {
    if (array == null && array.length == 0) {
        return;
    }
    //找到数组中的最大值
    int max = array[0];
    int length = array.length;
    int k = 1;  //用来取各个位上的数字
    int index = 0;
    for (int i = 1; i < length; i++) {
        if (max < array[i]) {
            max = array[i];
        }
    }
    int[][] buckets = new int[10][length];
    int[] counts = new int[10];
    int digit;
    while (k <= max) {
        //把元素添加到二维数组中
        for (int i=0; i 0) {
                for (int j=0; j < counts[i]; j++) {
                    array[index++] = buckets[i][j];
                }
            }
            counts[i] = 0;
        }
        k *= 10;
        index = 0;
    }
    System.out.println("基数排序结果:");
    for (int p = 0; p < array.length; p++) {
        System.out.println(array[p]);
    }
}

总结

上面给出了各种排序算法的实现,并且每个算法都给了比较详细的注释,各位同学可以参考注解理解代码,这里给出各算法的一览图:


image.png

你可能感兴趣的:(Java排序算法总结)