常用的几种排序方法Java实现

常用的几种排序方法Java实现

废话不多说,直接上代码

package club.javafan.blog;

import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.IntStream;

/**
 * @author renjiahua3
 * @date 2019/8/8
 */

public class SortTest {
    /**
     * 冒泡排序的流程:
     * (1):和相邻元素进行比较,如果后个比前一个小(从小到大排序),则交换两个元素。
     * (2):冒泡排序一趟之后,最大的元素已经位于数组末尾故第二趟不用比这个数。
     * (3):但是会有个多余趟数的问题,如果数组已经有序,循环还在执行,故加了个标志,如果一趟没有进行交换,则停止循环。
     * 时间复杂度:O(n^2) 稳定 稳不稳定看相同元素顺序发布发生变化
     *
     * @param array
     */
    public static void bubbleSort(int[] array) {
        if (array.length <= 0) {
            return;
        }
        boolean change = false;
        for (int i = 0; i < array.length; i++) {
            for (int j = 1; j < array.length - i; j++) {
                if (array[j - 1] > array[j]) {
                    int temp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = temp;
                    change = true;
                }
            }
            if (!change) {
                break;
            }
        }
        printArray.accept(array);
    }

    /**
     * 快速排序流程:冒泡排序的改进
     * 1.选择一个基准值 pivotKey默认取最低位或者最高位便于操作。
     * 2.假定pivotKey是最小的值,从高位开始和基准值进行对比,找到第一个比pivotKey小的,array[low] = array[high]
     * 3.覆盖完成后,开始从low 进行和pivotKey进行比较 找到第一个比pivotKey大的,array[high] = array[low]
     * 4.覆盖完成之后继续从高位开始比较。直至 low == high
     * 5.将pivotKey的值 覆盖到low == high 的位置。这个位置就是PivotKey的坑位。
     * 6.将数组按照 pivotKey的位置左右进行划分
     * 如序列:49, 38, 65, 97, 76, 13, 27, 49
     * 划分过程为:每个分片为一个元素 有序
     * [27, 38, 13] 49 [76, 97, 65, 49]
     * [13] 27 [38]
     * [49, 65] 76 [97]
     * [] 49 [65]
     * O(nlogn) 不稳定
     *
     * @param array
     * @param low
     * @param high
     */
    public static void quickSort(int[] array, int low, int high) {
        if (low < high) {
            int pivotloc = partition(array, low, high);
            System.out.print("划分为: " + Arrays.toString(Arrays.copyOfRange(array, low, pivotloc)));
            System.err.print(" " + array[pivotloc] + " ");
            System.out.print(Arrays.toString(Arrays.copyOfRange(array, pivotloc + 1, high + 1)) + "\n");
            quickSort(array, low, pivotloc - 1);
            quickSort(array, pivotloc + 1, high);
        }
    }

    /**
     * 快排的划分
     *
     * @param array
     * @param low   传入下标
     * @param high  传入下标
     */
    public static int partition(int[] array, int low, int high) {
        int pivotKey = array[low];
        while (low < high) {
            while (low < high && pivotKey <= array[high]) {
                high--;
            }
            array[low] = array[high];
            while (low < high && pivotKey >= array[low]) {
                low++;
            }
            array[high] = array[low];
        }
        array[low] = pivotKey;
        return low;
    }

    /**
     * 选择排序流程:
     * (1):假定数组的第i个元素为最小的,找到其余元素中最小的记录下标
     * (2):找到最小的之后交换位置。
     * (3):重复,直至结束
     * 时间复杂度O(n^2) 不稳定
     *
     * @param array
     */
    public static void selectSort(int[] array) {
        if (array.length <= 1) {
            return;
        }
        for (int i = 0; i < array.length; i++) {
            int min = array[i];
            int index = i;
            for (int j = i + 1; j < array.length - 1; j++) {
                if (min > array[j]) {
                    min = array[j];
                    index = j;
                }
            }
            swap(array, i, index);
        }
        printArray.accept(array);
    }

    /**
     * 堆排序算法流程:
     * 0.将完全二叉树用数组表示,数组下标就是节点按广度优先遍历的序号
     * 1.建立大顶堆或者小顶堆
     * (1):一般从末尾的一个堆顶开始进行大顶堆创建
     * (2):循环所有的堆顶节点,使堆顶元素均大于左右子节点
     * (3):完成大顶堆的创建
     * 如图:便是一个创建好的大堆
     *       25
     *     /  \
     *   17   16
     *  / \  / \
     * 7  3 11 12
     * 2.排序
     * (1):将堆顶元素和最后一个元素交换,元素个数减一。
     * (2):执行建堆过程,依次类推使其有序。
     * 时间复杂度O(nlogn) 不稳定
     *
     * @param arr
     * @param count
     */
    private static void heapSort(int[] arr, int count) {
        /**
         *  求最后节点的父节点 减一减二都一样
         *  因为 left = 2 * i + 1  right = 2 * i + 2
         */
        for (int i = (count - 2) / 2; i >= 0; i--) {
            shiftDown(arr, count, i);
        }
        System.out.println("构建完成的大顶堆:" + Arrays.toString(arr));

        /**
         *  将大顶堆头结点(最大数)与最后一个元素交换位置,然后除去最后这个最大的元素,
         * 再shiftDown操作维护该(除去最后一个元素的数组)堆为大顶堆。循环直到升序排序完成
         */
        for (int j = count - 1; j > 0; j--) {
            swap(arr, 0, j);
            shiftDown(arr, j, 0);
            System.out.println("去除最大的数,重建堆:" + Arrays.toString(arr));
        }
    }

    /**
     * 建立一个大顶堆的过程
     * 1.判断当前节点是否是叶子节点
     * 2.判断左节点和右节点哪个大
     * 3.大的节点和父节点进行比较,如果比父节点小则进行交换
     * 如下实例:
     *   17        18
     *  / \   =>  / \
     * 5  18     5  17
     *
     * @param arr         数组
     * @param count       元素个数
     * @param currentRoot 当前根节点的下标
     */
    private static void shiftDown(int[] arr, int count, int currentRoot) {
        while (2 * currentRoot + 1 < count) {
            int max = 2 * currentRoot + 1;
            if (max + 1 < count && arr[max + 1] > arr[max]) {
                max += 1;
            }
            if (arr[currentRoot] >= arr[max]) {
                break;
            }
            swap(arr, currentRoot, max);
            currentRoot = max;
        }
    }

    /**
     * 2-路 归并排序流程:原理将长度为n的数组看为n个子序列 然后两两合并。如此重复直至序列有序。
     * 1.将数组分成长度为2或者长度为1的序列
     * 2.然后相邻两个序列比较谁小谁存入temp序列
     * 3.直至归并完使数组有序
     * 如: 初始:[49][38][65][97][76][13][27]
     * 一趟归并:[38 49] [65 97] [13 76] [27]
     * 二趟归并:[38 49 65 97] [13 27 76]
     * 三趟归并 [13 27 38 49 65 76 97]
     *
     * @param arr  原数组
     * @param tmp  临时数组
     * @param low  下标开始位
     * @param high 下标结束
     */
    public static void mergeSort(int[] arr, int[] tmp, int low, int high) {
        if (low < high) {
            int mid = (low + high) / 2;
            mergeSort(arr, tmp, low, mid);
            mergeSort(arr, tmp, mid + 1, high);
            merge(arr, low, mid, high, tmp);
        }
    }

    /**
     * 数组的归并
     * 1.相邻数组进行比较:小的放入temp 下标++
     * 2.如果一边有剩余则将剩余的全部放入temp数组中
     * 3.将temp数组的数据考入进原数组的那段位置
     * @param arr 数组
     * @param low 低位
     * @param mid 相邻数组的分界
     * @param high 高位
     * @param tmp 临时存放的数组
     */
    public static void merge(int[] arr, int low, int mid, int high, int[] tmp) {
        int i = 0;
        int j = low, k = mid + 1;
        while (j <= mid && k <= high) {
            int i1 = arr[j] < arr[k] ? (tmp[i++] = arr[j++]) : (tmp[i++] = arr[k++]);
        }
        while (j <= mid || k <= high ) {
            int i1 = j <= mid ? (tmp[i++] = arr[j++]) : (tmp[i++] = arr[k++]);
        }
        for (int t = 0; t < i; t++) {
            arr[low + t] = tmp[t];
        }
    }

    /**
     * 插入排序本质是移动数组,找到合适的坑将自己栽入进去。
     * 插入排序流程:
     * 1.将第二个数开始设置哨兵
     * 2.和前一个数比较  如果前面有数比后面的数大 则将那个数的数组后移将这个数插入那个空中
     * 3.如果不大 则继续向前 直到结束
     * 4.如果找到前面比后面大的,则赋值到后面。
     * 5.继续设置哨兵
     * 6.重复
     * 原始数据 [49, 38, 65, 97, 76, 13, 27, 49]
     * guard = 38 49>38 将数组的数向后移动直至小于38 然后将38 放进去
     * 执行了一次[38,49,65, 97, 76, 13, 27, 49]
     * guard = 65 不变 ,guard = 97 不变 guard = 76
     * [38,49,65, 97, 76, 13, 27, 49] => [38,49,65,76,97,13, 27, 49]
     * guard = 13   [13,38,49,65,76,97,27,49]
     * guard = 27  [13,27,38,49,65,76,97,49]
     * guard = 49  [13,27,38,49,49,65,76,97]
     * @param array
     */
    public static void insertSort(int[] array){
        if (array.length <= 1){
            return;
        }
        int guard;
        for (int i = 1; i< array.length ;i++){
            guard = array[i];
            if (array[i] < array[i - 1]){
                array[i] = array[i - 1];
                int j;
                for (j = i - 1;j>= 0 && array[j] > guard;j--){
                    array[j + 1] = array[j];
                }
                array[j + 1] = guard;
            }
        }
        printArray.accept(array);
    }

    /**
     *  希尔排序:直接插入排序的改进,又叫做缩小增量排序。
     *  希尔排序流程:
     *  1.确定增量步长的大小:(end - start)/2 。这里为什么除以二?
     *  增量有好多种增量序列,不同的增量序列的时间复杂度也不一样。缩小增量取2 这种序列比较稳定
     *  2.相邻序列的相同步长的元素进行比较
     *  3.比较完成后缩小增量进行新的一轮 直至增量变为1 序列有序
     *  时间复杂度 最好O(n^1.3) 平均O(n^1.5) 不稳定
     *  原始:[49, 38, 65, 97, 76, 13, 27, 49]
     *   [ 49 13 27 49 76 38 65 97 ]
     *  [27 13 49 38 65 49 76 97 ]
     * [13 27 38 49 49 65 76 97 ]
     * @param arrays
     */
    public static void shellSort(int[] arrays) {
        for (int step = arrays.length / 2; step > 0; step /= 2) {
            for (int i = step; i < arrays.length; i++) {
                int j = i;
                int temp = arrays[j];
                while (j - step >= 0 && arrays[j - step] > temp) {
                    arrays[j] = arrays[j - step];
                    j = j - step;
                }
                arrays[j] = temp;
            }
            printArray.accept(arrays);
        }
    }
    /**
     * 数据交换
     * @param arr 数组
     * @param n  下标m
     * @param m  下标n
     *  注意:使用异或交换两个相同的数时 值为0 要避免这种情况。
     */
    public static void swap(int[] arr, int n, int m) {
        if (arr[m] == arr[n]) {
            return;
        }
        arr[n] ^= arr[m];
        arr[m] ^= arr[n];
        arr[n] ^= arr[m];
    }
    /**
     *  创建长度为length的int数组
     */
    private static Function<Integer,int[]> createArray = length ->{
        if (length > 0){
            return new int[length];
        }
        return null;
    } ;
    /**
     *  打印数组
     */
    private static Consumer<int[]> printArray = array -> {
        IntStream.of(array).mapToObj(item -> item + " ").forEach(System.out::print);
        System.out.println();
    };

    public static void main(String[] args) {
        int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
//        mergeSort(array,createArray.apply(array.length), 0, array.length - 1);
//        bubbleSort(array);
//        selectSort(array);
//        quickSort(array, 0, array.length - 1);
//        heapSort(array, array.length);
        shellSort(array);

    }
}

如果有差错,欢迎留言!!!
我的个人微博:不会敲代码的小白,欢迎关注!!!

你可能感兴趣的:(算法)