美文网首页
排序算法

排序算法

作者: ZoranLee | 来源:发表于2021-05-28 12:21 被阅读0次

    排序就是将一组对象按照某种逻辑顺序重新排列的过程。


    排序有十大算法:
    包括冒泡排序,
    简单选择排序,
    简单插入排序,
    归并排序,
    堆排序,
    快速排序、
    希尔排序、
    计数排序、
    基数排序、
    桶排序。

    冒泡排序冒

    泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。

    基本思路:
    1、比较相邻的元素。如果第一个比第二个大(小),就交换它们两个;
    2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大(小)的数;
    3、针对所有的元素重复以上的步骤,除了最后一个;重复步骤1~2,直到排序完成。

    image.png image.png image.png image.png
    image.png
    image.png
    public class BubbleSort {
        public static int[] sort(int[] array) {
            if (array.length == 0)
                return array;
            /*循环数组长度的次数*/
            for (int i = 0; i < array.length; i++){
                /*从第0个元素开始,依次和后面的元素进行比较
                 * j < array.length - 1 - i表示第[array.length - 1 - i]
                 * 个元素已经冒泡到了合适的位置,无需进行比较,可以减少比较次数*/
                for (int j = 0; j < array.length - 1 - i; j++){
                    /*如果第j个元素比后面的第j+1元素大,交换两者的位置*/
                    if (array[j + 1] < array[j]) {
                        int temp = array[j + 1];
                        array[j + 1] = array[j];
                        array[j] = temp;
                    }
                    PrintArray.print(array);
                }
            }
    
            return array;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = BubbleSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    }
    
    

    简单选择排序

    选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面。

    但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。

    举个例子,对5,3,8,6,4这个无序序列进行简单选择排序,首先要选择5以外的最小数来和5交换,也就是选择3和5交换,一次排序后就变成了3,5,8,6,4.对剩下的序列继续进行选择和交换,最终就会得到一个有序序列。
    其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。

    具体步骤:

    • 首先,找到数组中最大(小)的那个元素;
    • 其次,将它和数组的第一个元素交换位置(如果第一个元素就是最大(小)元素那么它就和自己交换);
    • 再次,在剩下的元素中找到最大(小)的元素,将它与数组的第二个元素交换位置。
      如此往复,直到将整个数组排序。
    public class ChoiceSort {
        public static int[] sort(int[] array) {
            if (array.length == 0)
                return array;
            for (int i = 0; i < array.length; i++) {
                int minIndex=i;/*最小数的下标,每个循环开始总是假设第一个数最小*/
                for (int j = i; j < array.length; j++) {
                    if (array[j] < array[minIndex]) /*找到最小的数*/
                        minIndex = j; /*将最小数的索引保存*/
                }
                System.out.println("最小数为:"+array[minIndex]);
                /*交换最小数和i当前所指的元素*/
                int temp = array[minIndex];
                array[minIndex] = array[i];
                array[i] = temp;
                PrintArray.print(array);
          
            }
            return array;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = ChoiceSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    }
    

    简单插入排序

    插入排序不是通过交换位置,而是通过比较,找到合适的位置插入元素来达到排序的目的的。

    相信大家都有过打扑克牌的经历,特别是牌数较大的。在分牌时可能要整理自己的牌,牌多的时候怎么整理呢?

    就是拿到一张牌,找到一个合适的位置插入。这个原理其实和插入排序是一样的。
    举个例子,我们将要收到5,3,4,,8,6这几张牌,我们先收到5这张牌,毫无疑问,这张牌的位置是正确的,没必要整理。接着收到了牌3,然后3要插到5前面,把5后移一位,变成3,5。接着又收到了牌4,现在我们会怎么做?把4插入到5前面,把5后移一位。

    具体步骤:

    • 对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 - 为了给要插入的元素腾出空间,我们需要将插入位置之后的已排序元素在都向右移动一位。 插入排序所需的时间取决于输入中元素的初始顺序。

    例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。 总的来说,插入排序对于部分有序的数组十分高效,也很适合小规模数组。

    public class InsertionSort {
        public static int[] sort(int[] array) {
            if (array.length == 0)
                return array;
            int currentValue;/*当前待排序数据,该元素之前的元素均已被排序过*/
            for (int i = 0; i < array.length - 1; i++) {
                int preIndex = i;/*已被排序数据的索引*/
                currentValue = array[preIndex + 1];
                System.out.println("待排序元素索引:"+(i + 1)+",值为:" +currentValue+
                        ",已被排序数据的索引:"+preIndex);
    
                /*在已被排序过数据中倒序寻找合适的位置,如果当前待排序数据比比较的元素要小,
                将比较的元素元素后移一位*/
                while (preIndex >= 0 && currentValue < array[preIndex]) {
                    //将当前元素后移一位
                    array[preIndex + 1] = array[preIndex];
                    preIndex--;
                    PrintArray.print(array);
                }
                /*while循环结束时,说明已经找到了当前待排序数据的合适位置,插入*/
                array[preIndex + 1] = currentValue;
                System.out.println("本轮被插入排序后的数组");
                PrintArray.print(array);
                System.out.println("--------------------");
            }
            return array;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = InsertionSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    

    希尔排序

    一种基于插入排序的快速的排序算法(请先学习插入排序,了解基本的插入排序的思想。对于大规模乱序数组插入排序很慢,因为元素只能一点一点地从数组的一端移动到另一端。

    例如,如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要N-1 次移动。

    希尔排序为了加快速度简单地改进了插入排序,也称为缩小增量排序,同时该算法是冲破O(n^2)的第一批算法之一。

    希尔排序是把待排序数组按一定增量的分组,对每组使用直接插入排序算法排序;

    然后缩小增量继续分组排序,随着增量逐渐减少,每组包含的元素越来越多,当增量减至 1 时,整个数组恰被分成一组,排序便完成了。这个不断缩小的增量,就构成了一个增量序列。

    image.png
    public class ShellSort {
        public static int[] sort(int[] array) {
            int len = array.length;
            /*按增量分组后,每个分组中,temp代表当前待排序数据,该元素之前的元素均已被排序过*/
            /*gap指用来分组的增量,会依次递减*/
            int currentValue, gap = len / 2;
            while (gap > 0) {
                for (int i = gap; i < len; i++) {
                    currentValue = array[i];
                    /*组内已被排序数据的索引*/
                    int preIndex = i - gap;
                    /*在组内已被排序过数据中倒序寻找合适的位置,如果当前待排序数据比比较的元素要小,
                    并将比较的元素元素在组内后移一位*/
                    while (preIndex >= 0 && array[preIndex] > currentValue) {
                        array[preIndex + gap] = array[preIndex];
                        preIndex -= gap;
                    }
                    /*while循环结束时,说明已经找到了当前待排序数据的合适位置,插入*/
                    array[preIndex + gap] = currentValue;
                }
                System.out.println("本轮增量【"+gap+"】排序后的数组");
                PrintArray.print(array);
     
                gap /= 2;
            }
            return array;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
    
            int[] dest = ShellSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    }
    

    希尔排序中的增量序列

    在先前较大的增量下每个子序列的规模都不大,用直接插入排序效率都较高,尽管在随后的增量递减分组中子序列越来越大,由于整个序列的有序性也越来越明显,则排序效率依然较高。

    从理论上说,只要一个数组是递减的,并且最后一个值是1,都可以作为增量序列使用。

    有没有一个步长序列,使得排序过程中所需的比较和移动次数相对较少,并且无论待排序列记录数有多少,算法的时间复杂度都能渐近最佳呢?但是目前从数学上来说,无法证明某个序列是“最好的”。

    常用的增量序列有:

    • 希尔增量序列 :{N/2, (N / 2)/2, ..., 1},其中N为原始数组的长度,这是最常用的序列,但却不是最好的
    • Hibbard序列:{2^k-1, ..., 3,1}
    • Sedgewick序列:{... , 109 , 41 , 19 , 5,1} 表达式为


      image.png

    归并排序

    归并排序是建立在归并操作上的一种有效的排序算法。
    该算法是采用分治法的一个非常典型的应用。
    将已有序的子序列合并,得到完全有序的序列;
    即先使每个子序列有序,再使子序列段间有序。

    若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。

    对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。
    为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。

    public class MergeSort {
        public static int[] sort(int[] array) {
            if (array.length < 2) return array;
            /*切分数组,然后递归调用*/
            int mid = array.length / 2;
            int[] left = Arrays.copyOfRange(array, 0, mid);
            int[] right = Arrays.copyOfRange(array, mid, array.length);
            return merge(sort(left), sort(right));
        }
        /**
         * 归并排序——将两段排序好的数组结合成一个排序数组
         *
         * @param left
         * @param right
         * @return
         */
        public static int[] merge(int[] left, int[] right) {
            int[] result = new int[left.length + right.length];
            for (int index = 0, i = 0, j = 0; index < result.length; index++) {
                if (i >= left.length)/*左边数组已经取完,完全取右边数组的值即可*/
                    result[index] = right[j++];
                else if (j >= right.length)/*右边数组已经取完,完全取左边数组的值即可*/
                    result[index] = left[i++];
                else if (left[i] > right[j])/*左边数组的元素值大于右边数组,取右边数组的值*/
                    result[index] = right[j++];
                else/*右边数组的元素值大于左边数组,取左边数组的值*/
                    result[index] = left[i++];
            }
            System.out.print("左子数组:");
            PrintArray.print(left);
            System.out.print("右子数组:");
            PrintArray.print(right);
            System.out.print("合并后数组:");
            PrintArray.print(result);
    
            return result;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = MergeSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    

    快速排序

    快速排序被誉为20 世纪科学和工程领域的十大算法之一。

    快速排序(Quicksort)是对冒泡排序的一种改进,也是采用分治法的一个典型的应用。
    首先任意选取一个数据(比如数组的第一个数)作为关键数据,我们称为基准数,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序,也称为分区(partition)操作。

    在实际实现时,一般会在原数组上直接操作。 通过一趟快速排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    为了提升性能,有时我们在分割后独立的两部分的个数小于某个数(比如15)的情况下,会采用其他排序算法,比如插入排序。

    public class QuickSort {
        public static int[] sort(int[] array, int start, int end) {
            if (array.length < 1 || start < 0 || end >= array.length || start > end)
                return null;
            /*数据分割成独立的两部分时,从哪儿分区的指示器*/
            int zoneIndex = partition(array, start, end);
            if (zoneIndex > start)
                sort(array, start, zoneIndex - 1);
            if (zoneIndex < end)
                sort(array, zoneIndex + 1, end);
            System.out.println("本轮排序后的数组");
            PrintArray.printIndex(array,start,end);
            System.out.println("--------------------");
            return array;
        }
        /**
         * 快速排序算法——partition
         * @param array
         * @param start
         * @param end
         * @return
         */
        public static int partition(int[] array, int start, int end) {
            int pivot = (int) (start + Math.random() * (end - start + 1));
            System.out.println("开始下标:"+start+",结束下标:"+end+",基准数下标:"
                    +pivot+",元素值:"+array[pivot]);
            /*zoneIndex是分割指示器
            从业务上来说:比基准数小的,放到指示器的左边,比基准数大的,放到指示器的右边,
            * 但在实际实现时,通过移动比基准数小的元素和分割指示器本身也可以达到一样的效果*/
            int zoneIndex = start - 1;
            swap(array, pivot, end);/*将基准数和数组尾元素交换位置*/
            for (int i = start; i <= end; i++){
                if (array[i] <= array[end]) {/*当前元素小于等于基准数*/
                    zoneIndex++;/*首先分割指示器累加*/
                    if (i > zoneIndex)/*当前元素在分割指示器的右边时,交换当前元素和分割指示器元素*/
                        swap(array, i, zoneIndex);
                }
                System.out.println("zoneIndex:"+zoneIndex+",i:"+i);
                PrintArray.printIndex(array,start,end);
            }
            System.out.println("---------------");
            return zoneIndex;
        }
    
        /**
         * 交换数组内两个元素
         * @param array
         * @param i
         * @param j
         */
        public static void swap(int[] array, int i, int j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = QuickSort.sort(PrintArray.SRC,0,PrintArray.SRC.length-1);
            PrintArray.print(dest);
        }
    }
    

    快速排序中的基准数

    基准的选取:最优的情况是基准值刚好取在无序区的中间,这样能够最大效率地让两边排序,同时最大地减少递归划分的次数,但是一般很难做到最优。

    基准的选取一般有三种方式,
    选取数组的第一个元素,
    选取数组的最后一个元素,
    以及选取第一个、最后一个以及中间的元素的中位数(如4 5 6 7, 第一个4, 最后一个7, 中间的为5, 这三个数的中位数为5, 所以选择5作为基准)。

    Dual-Pivot快排:两个基准数的快速排序算法,其实就是用两个基准数, 把整个数组分成三份来进行快速排序,在这种新的算法下面,比经典快排从实验来看节省了10%的时间。

    堆排序

    许多应用程序都需要处理有序的元素,但不一定要求他们全部有序,或者不一定要一次就将他们排序,很多时候,我们每次只需要操作数据中的最大元素(最小元素),那么有一种基于二叉堆的数据结构可以提供支持。所谓二叉堆,是一个完全二叉树的结构,同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。在一个二叉堆中,根节点总是最大(或者最小)节点。
    堆排序算法就是抓住了这一特点,每次都取堆顶的元素,然后将剩余的元素重新调整为最大(最小)堆,依次类推,最终得到排序的序列。

    image.png
    public class HeapSort {
        //声明全局变量,用于记录数组array的长度;
        private static int len;
    
        public static int[] sort(int[] array) {
            len = array.length;
            if (len < 1) return array;
            //1.构建一个最大堆
            buildMaxHeap(array);
            //2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
            while (len > 0) {
                swap(array, 0, len - 1);
                len--;
                adjustHeap(array, 0);
                PrintArray.print(array);
                System.out.println("--------------------");
            }
            return array;
        }
        /**
         * 建立最大堆
         *
         * @param array
         */
        public static void buildMaxHeap(int[] array) {
            //从最后一个非叶子节点开始向上构造最大堆
            for (int i = (len/2-1); i >= 0; i--) {
                adjustHeap(array, i);
            }
            System.out.println("构造完成最大堆");
            PrintArray.print(array);
            System.out.println("============================================");
        }
        /**
         * 调整使之成为最大堆
         *
         * @param array
         * @param i
         */
        public static void adjustHeap(int[] array, int i) {
            int maxIndex = i;
            int left = 2*i+1;
            int right = 2*(i+1);
            //如果有左子树,且左子树大于父节点,则将最大指针指向左子树
            if (left < len && array[left] > array[maxIndex])
                maxIndex = left;
            //如果有右子树,且右子树大于父节点,则将最大指针指向右子树
            if (right < len && array[right] > array[maxIndex])
                maxIndex = right;
            //如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
            if (maxIndex != i) {
                swap(array, maxIndex, i);
                adjustHeap(array, maxIndex);
            }
        }
    
        /**
         * 交换数组内两个元素
         * @param array
         * @param i
         * @param j
         */
        public static void swap(int[] array, int i, int j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = HeapSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    }
    
    image.png
    public class CountingSort {
        public static int[] sort(int[] array) {
            if (array.length == 0) return array;
            /*寻找数组中最大值,最小值
            * bias:偏移量,用以定位原始数组每个元素在计数数组中的下标位置*/
            int bias, min = array[0], max = array[0];
            for (int i = 1; i < array.length; i++) {
                if (array[i] > max)
                    max = array[i];
                if (array[i] < min)
                    min = array[i];
            }
            bias = 0 - min;
            /*获得计数数组的容量*/
            int[] counterArray = new int[max - min + 1];
            Arrays.fill(counterArray, 0);
            /*遍历整个原始数组,将原始数组中每个元素值转化为计数数组下标,
            并将计数数组下标对应的元素值大小进行累加*/
            for (int i = 0; i < array.length; i++) {
                counterArray[array[i] + bias]++;
            }
            System.out.println("计数数组为:");
            PrintArray.print(counterArray);
            System.out.println("============================================");
            int index = 0;/*访问原始数组时的下标计数器*/
            int i = 0;/*访问计数数组时的下标计数器*/
            /*访问计数数组,将计数数组中的元素转换后,重新写回原始数组*/
            while (index < array.length) {
                /*只要计数数组中当前下标元素的值不为0,就将计数数组中的元素转换后,重新写回原始数组*/
                if (counterArray[i] != 0) {
                    array[index] = i - bias;
                    counterArray[i]--;
                    index++;
                } else
                    i++;
                PrintArray.print(counterArray);
                PrintArray.print(array);
     
            }
            return array;
        }
    
        final static int[] src = {5,4,5,0,3,6,2,0,2,4,3,3};
    
        public static void main(String[] args) {
    
            PrintArray.print(src);
            int[] dest = CountingSort.sort(src);
            PrintArray.print(dest);
        }
    }
    
    image.png
    public class BucketSort {
        /**
         *
         * @param array
         * @param bucketSize BucketSize,作为每个桶所能放置多少个不同数值
         *                   (例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,
         *                   但是容量不限,即可以存放100个3);
         * @return
         */
        public static ArrayList<Integer> sort(ArrayList<Integer> array, int bucketSize) {
            if (array == null || array.size() < 2)
                return array;
            int max = array.get(0), min = array.get(0);
            // 找到最大值最小值
            for (int i = 0; i < array.size(); i++) {
                if (array.get(i) > max)
                    max = array.get(i);
                if (array.get(i) < min)
                    min = array.get(i);
            }
            /*获得桶的数量*/
            int bucketCount = (max - min) / bucketSize + 1;
            /*构建桶*/
            ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
            ArrayList<Integer> resultArr = new ArrayList<>();
            for (int i = 0; i < bucketCount; i++) {
                bucketArr.add(new ArrayList<Integer>());
            }
            /*将原始数组中的数据分配到桶中*/
            for (int i = 0; i < array.size(); i++) {
                bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
            }
            /*看看桶中数据的分布*/
            for (int i = 0; i < bucketArr.size(); i++) {
                System.out.print("第"+i+"个桶包含数据:");
                PrintArray.printObject(bucketArr.get(i));
            }
            for (int i = 0; i < bucketCount; i++) {
                if (bucketSize == 1) {
                    for (int j = 0; j < bucketArr.get(i).size(); j++)
                        resultArr.add(bucketArr.get(i).get(j));
                } else {
                    if (bucketCount == 1)
                        bucketSize--;
                    /*对桶中的数据再次用桶进行排序*/
                    ArrayList<Integer> temp = sort(bucketArr.get(i), bucketSize);
                    for (int j = 0; j < temp.size(); j++)
                        resultArr.add(temp.get(j));
                }
            }
            return resultArr;
        }
    
        public static void main(String[] args) {
            ArrayList<Integer> array = new ArrayList<>();
            array.add(86);
            array.add(11);
            array.add(77);
            array.add(23);
            array.add(32);
            array.add(45);
            array.add(58);
            array.add(63);
            array.add(93);
            array.add(4);
            array.add(37);
            array.add(22);
            PrintArray.printObject(array);
    
            ArrayList<Integer> dest = BucketSort.sort(array,2);
            PrintArray.printObject(dest);
        }
    }
    
    image.png
    public class RadixSort {
        public static int[] sort(int[] array) {
            if (array == null || array.length < 2)
                return array;
            /*找出最大数*/
            int max = array[0];
            for (int i = 1; i < array.length; i++) {
                max = Math.max(max, array[i]);
            }
    
            /*先算出最大数的位数*/
            int maxDigit = 0;
            while (max != 0) {
                max /= 10;
                maxDigit++;
            }
            int mod = 10, div = 1;
            /*构建桶*/
            ArrayList<ArrayList<Integer>> bucketList = new ArrayList<ArrayList<Integer>>();
            for (int i = 0; i < 10; i++)
                bucketList.add(new ArrayList<Integer>());
            /*按照从右往左的顺序,依次将每一位都当做一次关键字,然后按照该关键字对数组排序,
            每一轮排序都基于上轮排序后的结果*/
            for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
                /*遍历原始数组,投入桶中*/
                for (int j = 0; j < array.length; j++) {
                    int num = (array[j] % mod) / div;
                    bucketList.get(num).add(array[j]);
                }
                /*桶中的数据写回原始数组,清除桶,准备下一轮的排序*/
                int index = 0;
                for (int j = 0; j < bucketList.size(); j++) {
                    for (int k = 0; k < bucketList.get(j).size(); k++)
                        array[index++] = bucketList.get(j).get(k);
                    bucketList.get(j).clear();
                }
            }
            return array;
        }
    
        public static void main(String[] args) {
            PrintArray.print(PrintArray.SRC);
            int[] dest = RadixSort.sort(PrintArray.SRC);
            PrintArray.print(dest);
        }
    }
    
    image.png

    相关文章

      网友评论

          本文标题:排序算法

          本文链接:https://www.haomeiwen.com/subject/ngtlsltx.html