java7种常见的排序算法:选择排序、冒泡排序、直接插入排序、快速排序、希尔排序、归并排序、堆排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ljh_learn_from_base/article/details/82875562

链接:java 7 种算法的完整实例及测试

一、选择排序

/**
     * 选择排序原理:挨个比较
     * 外层 循环长度-1次,内层循环每次从第二个开始
     * 将外层循环中的值挨个与内层循环中的元素作比较
     * 时间复杂度为:O(N^2)
     *
     * @param array
     * @return
     */
    public static int[] selectSort(int[] array) {
        //根据原理分析,使用两层循环即可实现
        for (int i = 0; i < array.length - 1; i++) {//第一层
            for (int j = i + 1; j < array.length; j++) {    //第二层
                if (array[i] > array[j]) {    //数据互换。将小的放前面
                    int t = array[i];
                    array[i] = array[j];
                    array[j] = t;
                }
            }
        }
        return array;
    }

二、冒泡排序

/**
     * 冒泡排序原理:相邻的两个元素之间的比较
     * 时间复杂度为:O(N^2)
     *
     * @param array
     * @return
     */
    public static int[] bubbleSort(int[] array) {
        //根据原理分析,使用两层循环即可实现
        for (int i = 0; i < array.length - 1; i++) {//外层循环长度-1
            for (int j = 0; j < array.length - 1 - i; j++) {    //内层循环环长度-1-i
                if (array[j] > array[j + 1]) {    //数据互换。将小的放前面
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
        return array;
    }

三、直接插入排序

/**
     * 直接插入排序原理:
     * 给定的一组记录,将其分为两个序列组,一个为有序序列(按照顺序从小到大或者从大到小),
     * 一个为无序序列,初始时,将记录中的第一个数当成有序序列组中的一个数据,
     * 剩下其他所有数都当做是无序序列组中的数据。然后从无序序列组中的数据中(也就是从记录中的第二个数据开始)
     * 依次与有序序列中的记录进行比较,然后插入到有序序列组中合适的位置,
     * 直到无序序列组中的最后一个数据插入到有序序列组中为止。
     * [21], 2, 33, 4, 55, 6, 77
     * 无序数组中的第一个元素同有序数组中的最后一个元素(即最大值,也即无序数组第一个元素的前一个元素)比较,
     * 比最大值还大,不执行操作,比最大值小则继续比较
     * 时间复杂度为:O(N^2)
     *
     * @param array
     * @return
     */
    public static int[] insertSort(int[] array) {
        //temp:记录较小值,将j提取到for循环外
        int j, temp;
        //循环无序数组
        for (int i = 1; i < array.length - 1; i++) {//[2, 33, 4, 55, 6, 77]
            if (array[i - 1] > array[i]) {//比最大值还大,不执行操作,比最大值小则继续比较
                temp = array[i];//temp:记录较小值
                //倒序循环有序数组
                for (j = i - 1; j >= 0 && array[j] > temp; j--) {//[21]
                    array[j + 1] = array[j];//将大的值往后移动
                }
                //内层循环结束,因为j--的原因,j 有可能为-1
                array[j + 1] = temp;
            }
            System.out.println("循环第" + i + "次:" + Arrays.toString(array));
        }
        return array;
    }
四、快速排序
/**
     * 快速排序是算法中最快的算法,时间复杂度为:O(N*logN)
     * 但最差的情况,运行时间也会很慢
     * 快速排序的原理:
     * 一:划分方法
     * 使用左右扫描指针同时从左右两端进行扫描,若满足条件,一个++,一个--
     * 当连个子循环都停下来时交换数据,当两个指针相遇时,程序结束
     * 当外层循环结束时,把最后一个元素(枢纽)放到指定位置(leftPar处),
     * 因为此时两个指针相遇leftPar=rightPar,也即交换两元素位置swap(array,leftPar,right);
     * 二:划分方法结束后返回枢纽下标,也就是分界线的下标 ,然后递归调用枢纽的左边和右边
     *
     * @param array 数组
     * @param left  起始点
     * @param right 结束点
     * @return
     */
    public static int[] quickSort(int[] array, int left, int right) {
        // int left = 0;
        // int right = array.length - 1;
        if (left < right) {
            int pivot = array[right];//使用数组最后一个元素作为枢纽
            //这一步是分成左右两组:一组大,一组小,但是两组都无序
            int partition = partitionIn(array, left, right, pivot);
            //递归调用,使小的一组排好序
            quickSort(array, left, partition - 1);//递归 枢纽的左边
            //递归调用,使大的一组排好序
            quickSort(array, partition + 1, right);//递归枢纽的右边
        }
        return array;
    }

    /**
     * 划分方法
     *
     * @param array 数组
     * @param left  起始点
     * @param right 结束点
     * @param pivot 枢纽(默认为数组最后一个元素)
     * @return
     */
    private static int partitionIn(int[] array, int left, int right, int pivot) {
        int leftPar = left - 1;//左扫描指针
        int rightPar = right;//右扫描指针,排除数组最后一个元素
        while (true) {
            while (array[++leftPar] < pivot) ;//因为使用数组最后一个元素作为枢纽,不用担心下标越界
            while (rightPar > 0 && array[--rightPar] > pivot) ;//使用rightPar>0,防止下标越界
            if (leftPar >= rightPar)//当两个扫描指针相遇,程序结束
                break;
            else {
                swap(array, leftPar, rightPar);//否则交换
            }
        }
        swap(array, leftPar, right);
        return leftPar;
    }

    /**
     * 交换两个元素
     *
     * @param one
     * @param another
     */
    private static void swap(int[] array, int one, int another) {
        int temp = array[one];
        array[one] = array[another];
        array[another] = temp;
    }

五、希尔排序

/**
     * 希尔排序顾名思义就是因为计算机科学家Donald L.Shell而得名
     * 他在1959年创造了该算法,希尔排序是基于插入排序的,
     * 但是增加了一个新特性 (n减增量),不断减少增量的间隔,最后使得增量为1
     * 大大提高了插入排序的执行效率,希尔排序比其它简单排序算法要快,它在最坏的情况下,执行
     * 效率也不会和平均效率相差很多,但是像快速排序虽然执行效率高,但最坏的时候会效率会很低
     * 理解:所谓创造就是头脑中原有的知识的重新排列组合,所以学得越多,你创造的可能性就越大!
     * 时间复杂度为O(N*(logN)^2)
     * <p>
     * 希尔排序的原理:
     * 在插入排序的基础上通过增量不断进行分组、插入排序的过程
     *
     * @param array
     */
    public static int[] shellSort(int[] array) {
        // Donald Ervin Knuth 唐纳德·欧文·克努斯(Knuth发音为/knuːθ/)
        int inner/*内部循环变量*/, outer/*外部循环变量*/, temp, h = 1;/*Knuth增量*/
        //增量的选择:使用Knuth增量间隔序列 h = h * 3 + 1;
        while (h <= array.length / 3) {
            h = h * 3 + 1;
        }
        while (h > 0) {
            for (outer = h; outer < array.length; outer++) {//outer会变化
                inner = outer;
                temp = array[outer];
                for (; inner - h >= 0 && array[inner - h] >= temp; inner -= h) {//inner会变化
                    array[inner] = array[inner - h];//要交换的两个元素相差一个增量
                }
                //inner 已经变化
                array[inner] = temp;
            }
            //增量倒推公式: h = (h - 1) / 3;
            h = (h - 1) / 3;
        }
        return array;
    }

六、归并排序

/**
     * 归并排序:将两个有序子数组归并为一个大数组
     * 归并排序的原理:
     * 将一个无序的数组不断的二分成若干个子数组,通过递归的方式
     * 使得两两子数组有序且归并,最终使得整个数组有序
     *
     * @param array
     * @return
     */
    public static int[] mergeSort(int[] array) {
        int[] workSpace = new int[array.length];//创建一个与原数组相同大小的新数组
        recMergeSort(array, workSpace, 0, array.length - 1);
        return array;
    }

    /**
     * rec 表示recursion 递归
     *
     * @param array      原数组
     * @param workSpace  工作数组
     * @param lowerBound 下限
     * @param upperBound 上限
     */
    private static void recMergeSort(int[] array, int[] workSpace, int lowerBound, int upperBound) {
        if (lowerBound == upperBound)
            return;
        else {
            int mid = (lowerBound + upperBound) >> 1;// 左移1相当于除以2
            recMergeSort(array, workSpace, lowerBound, mid);//归并数组左边部分,使之变为有序
            recMergeSort(array, workSpace, mid + 1, upperBound);//归并数组右边部分,使之变为有序
            //归并两个有序数组
            merge(array, workSpace, lowerBound, mid + 1, upperBound);
        }
    }

    /**
     * 归并数组的方法
     * 将一个数组分为两个子数组
     * 并构造出四限(左上限,右上限,右下限,右上限)
     *
     * @param array      原数组
     * @param workSpace  工作数组
     * @param lowPtr     左边子数组下限
     * @param highPtr    右边子数组下限(highPtr-1左边子数组上限)
     * @param upperBound 右边子数组上限
     */
    private static void merge(int[] array, int[] workSpace, int lowPtr, int highPtr, int upperBound) {
        int index = 0;//workSpace 的下标
        int lowerBound = lowPtr;
        int mid = highPtr - 1;
        int n = upperBound - lowerBound + 1;
        //将数组划分为两个子数组,以mid 为分界线 workSpace[index]记录比较中较小的一个
        while (lowPtr <= mid && highPtr <= upperBound) {
            workSpace[index++] = (array[lowPtr] < array[highPtr]) ?
                    array[lowPtr++] : array[highPtr++];
        }
        //第一个循环结束,如果lowPtr还是 <=mid,复制剩下的元素到workSpace中
        while (lowPtr <= mid)
            workSpace[index++] = array[lowPtr++];
        //第二个循环结束,如果highPtr还是 <=upperBound,复制剩下的元素到workSpace中
        while (highPtr <= upperBound)
            workSpace[index++] = array[highPtr++];
        //将workSpace中的元素复制回原数组
        for (index = 0; index < n; index++) {
            array[lowerBound + index] = workSpace[index];
        }
        System.out.println("第" + (i++) + "趟递归结果:" + Arrays.toString(array));
    }

七、堆排序

图解
图解
/**
     *堆排序:在完全二叉树的基础上,利用大顶堆或小顶堆来完成的排序
     * 要知道什么是堆这种数据结构:区别于java和C++中的堆
     *         堆是一颗完全二叉树。分为大顶堆和小顶堆,
     *         大顶堆,每个父结点都比自己的子节点大,也就是根结点为最大
     *         小顶堆,每个父结点都比自己的子节点小,也就是根结点最小。
     * 按照大顶堆和小顶堆这种特点,将一个无序的n个记录的序列构建成大顶堆,将根节点上的数与最后一个
     * 结点n进行交换,然后在对n-1个记录进行构建大顶堆,继续把根节点与最后一个结点(n-1)互换,继续上面的操作。
     *         从小到大排序,则使用大顶堆
     *         从大到小排序,则使用小顶堆
     *
     * 从小到大
     *     通过讲解原理:堆排序分为三步
     *   1、构建大顶堆或小顶堆
     *    2、循环
    *       根节点和末尾结点进行互换,
   *       构建大顶堆或小顶堆 
     *   3、排序完成
     */
    public static int[] heapSort(int[] array) {
        //第一步:将数组构建成一个大顶堆
        int length = array.length;
        //找到完全二叉树中的最后一个拥有子结点父结点的位置length/2,
        // 也就是最后一个父节点是在完全二叉树的第length/2的位置上,
        // 但是在数组中的位置是 (length/2)-1,它代表父节点在数组中的位置
        //依次遍历每一个父节点,比如最后一个父节点的值是33,那么它前面所有结点都是父节点,都需要进行构建
        for (int i = length / 2 - 1; i >= 0; i--) {
            //无序序列,所以需要从下往上全部进行构建。该方法做的事情就是,比较找到父节点,
            // 和两个子节点中最大的数放置到父节点的位置上。
            adjustMaxHeap(array, i, length);
            System.out.println("array = " + Arrays.toString(array));
        }

        //第二步:构建好了大顶堆后,将第一个数与最后一个进行互换,互换后继续调整大顶堆,
        for (int maxIndex = length - 1; maxIndex > 0; maxIndex--) {
            //互换数据,提取出来了。互换数据后,就已经不在是大顶堆了,需要重新进行构建
            swap(array, 0, maxIndex);
            System.out.println(" after swap array = " + Arrays.toString(array));
            //从上往下,因为基本上都已经有序了,没必要在从下往上重新进行构建堆了,
            // 这就利用了前面比较的结果,减少了很多次比较。
            adjustMaxHeap(array, 0, maxIndex);
            System.out.println(" after adjust array = " + Arrays.toString(array));
        }
        return array;
    }


    /**
     * 构建大顶堆的操作,
     * 父节点和其左右子节点的大小比较和互换,每次将父结点的位置和数组传进来,
     * 就能构建出大顶堆了。
     *
     * @param array  排序数组
     * @param pIndex 当前所指父节点在数组中位置(下标)
     * @param length 数组的长度。用来判断父节点的两个子节点是否存在。
     *               父节点和左子结点的关系:2pIndex+1
     *               父结点和右子结点的关系: 2pIndex+2
     */
    private static void adjustMaxHeap(int[] array, int pIndex, int length) {
        int temp = array[pIndex];//临时记录父节点的值
        int child; //记录较大的子节点的下标
        //根据逻辑关系,父节点的下标为pIndex,则左子节点的下标为2pIndex+1
        //child <= length-1 说明肯定有子节点,如果child=length-1,说明只有左结点
        for (child = pIndex * 2 + 1; child <= length - 1; child = child * 2 + 1) {
            //child<length-1,就说明肯定右子结点,将其进行比较,找出大的一方的数组下标
            if (child < length - 1 && array[child] < array[child + 1]) {
                //变成右子节点所在数组中的下标,找到那个较大的子节点
                child++;
            }
            //将较大的子节点与父节点进行比较,
            // 若子节点大,互换父子节点的值
            if (array[child] > temp) {
                array[pIndex] = array[child];
                array[child] = temp;
                //因为子节点的值变了,那么就不知道这个子节点在他自己的两个子节点中是否还是最大,
                // 所以需要将该子节点的下标给pIndex,去重新检测一遍。只有当父节点为最大时,才会执行break退出循环。
                pIndex = child;
            } else {   //否则结束循环。
                break;
            }

        }
    }

猜你喜欢

转载自blog.csdn.net/ljh_learn_from_base/article/details/82875562