美文网首页Algorithm
Algorithm -- 排序算法

Algorithm -- 排序算法

作者: 开心wonderful | 来源:发表于2021-12-13 16:01 被阅读0次

    单链表

    • 十大经典排序算法
    • 冒泡排序
    • 选择排序
    • 插入排序
    • 归并排序
    • 快速排序
    • 堆排序
    • 计数排序
    • 桶排序
    图片来源于网络

    1. 十大经典排序算法

    十大经典排序算法对比如下:

    排序算法对比
    • 稳定性的定义
      排序后 2 个相等键值的顺序和排序之前它们的顺序相同。
      例如:在原序列中,A1=A2,且A1A2之前,在排序后的序列中,A1仍在A2之前,则称这种排序算法是稳定的;否则称为不稳定的。

    • 稳定性的意义
      若只是简单的进行数字排序,那么稳定性无意义。
      排序的内容是一个复杂对象的多个数字属性,且原本的初始顺序存在意义,稳定性的算法可以在二次排序的基础上保持原有排序的意义。
      例如:一组原本按价格高低排序的对象,现需要按销量高低排序,使用稳定性算法,可以使相同销量的对象依旧保持着价格高低的排序展现,只有销量不同的才会重新排序。

    2. 冒泡排序

        /**
         * 冒泡排序 平均时间复杂度O(n²),最坏时间复杂度O(n²),最好时间复杂度O(n),空间O(1),稳定
         * 1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
         * 2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
         * 3. 针对所有的元素重复以上的步骤,除了最后一个。
         * 4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
         */
        private void bubbleSort(int[] array) {
            for (int i = 0; i < array.length; i++) {
                for (int j = 0; j < array.length - i - 1; j++) {
                    int temp;
                    if (array[j] > array[j + 1]) {
                        temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
        }
    

    可加个判断优化上面的算法:

        private void bubbleSort(int[] array) {
            for (int i = 0; i < array.length - 1; i++) {
                boolean sorted = true;
                for (int j = 0; j < array.length - i - 1; j++) {
                    int temp;
                    if (array[j] > array[j + 1]) {
                        temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                        sorted = false;
                    }
                }
                if (sorted) break;
            }
        }
    

    保存循环的最后一次交换下标,进一步优化:

        private void bubbleSort(int[] array) {
            int lastExchangeIndex = 0;
            int sortBorder = array.length - 1;
            for (int i = 0; i < array.length - 1; i++) {
                boolean sorted = true;
                for (int j = 0; j < sortBorder; j++) {
                    int temp;
                    if (array[j] > array[j + 1]) {
                        temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                        sorted = false;
                        lastExchangeIndex = j;
                    }
                }
                sortBorder = lastExchangeIndex;
                if (sorted) break;
            }
        }
    

    3. 选择排序

        /**
         * 简单选择排序 平均时间复杂度O(n²),最坏时间复杂度O(n²),最好时间复杂度O(n²),空间O(1),不稳定
         * 1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
         * 2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
         * 3. 重复第二步,直到所有元素均排序完毕。
         */
        private void selectSort(int[] array) {
            int len = array.length;
            int minIndex, temp;
            for (int i = 0; i < len - 1; i++) {
                minIndex = i;
                for (int j = i + 1; j < len; j++) {
                    if (array[j] < array[minIndex]) { // 寻找最小的数
                        minIndex = j; // 将最小数的索引保存
                    }
                }
                temp = array[i];
                array[i] = array[minIndex];
                array[minIndex] = temp;
            }
        }
    

    4. 插入排序

        /**
         * 简单插入排序:平均时间复杂度O(n²),最坏时间复杂度O(n²),最好时间复杂度O(n),空间O(1),稳定
         * 1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
         * 2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
         * (如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
         */
        private void insertSort(int[] array) {
            int len = array.length;
            for (int i = 1; i < len; i++) {
                int temp = array[i]; // 记录要插入的数据
    
                // 从已经排序的序列最右边的开始比较,找到比其小的数
                int j = i;
                while (j > 0 && array[j - 1] > temp) {
                    array[j] = array[j - 1];
                    j--;
                }
                // 存在比其小的数,插入
                if (j != i) {
                    array[j] = temp;
                }
            }
        }
    

    5. 归并排序

        /**
         * 归并排序:采用分治法  平均时间复杂度O(nlogn) ,最坏时间复杂度O(nlogn) ,最好时间复杂度O(nlogn) ,空间O(n),稳定
         * 1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
         * 2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
         * 3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
         * 4.重复步骤 3 直到某一指针达到序列尾;
         * 5.将另一序列剩下的所有元素直接复制到合并序列尾。
         */
        private int[] mergeSort(int[] array) {
            int middle = (int) Math.floor(array.length / 2);
            int[] left = Arrays.copyOfRange(array, 0, middle);
            int[] right = Arrays.copyOfRange(array, middle, array.length);
    
            return merge(mergeSort(left), mergeSort(right));
        }
    
        private int[] merge(int[] left, int[] right) {
            int[] res = new int[left.length + right.length];
            int i = 0;
            while (left.length > 0 && right.length > 0) {
                if (left[0] <= right[0]) {
                    res[i++] = left[0];
                    left = Arrays.copyOfRange(left, 1, left.length);
                } else {
                    res[i++] = right[0];
                    right = Arrays.copyOfRange(right, 1, right.length);
                }
            }
    
            while (left.length > 0) {
                res[i++] = left[0];
                left = Arrays.copyOfRange(left, 1, left.length);
            }
    
            while (right.length > 0) {
                res[i++] = right[0];
                right = Arrays.copyOfRange(right, 1, right.length);
            }
    
            return res;
        }
    

    6. 快速排序

        /**
         * 快速排序:平均时间复杂度O(nlogn) ,最坏时间复杂度O(n²) ,最好时间复杂度O(nlogn) ,空间O(logn),不稳定
         * 1.从数列中挑出一个元素,称为 "基准"(pivot);
         * 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
         * 在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
         * 3.递归地把小于基准值元素的子数列和大于基准值元素的子数列排序;
         */
        private int[] quickSort(int[] array, int left, int right) {
            if (left < right) {
                int partitionIndex = partition(array, left, right);
                quickSort(array, left, partitionIndex - 1);
                quickSort(array, partitionIndex + 1, right);
            }
            return array;
        }
    
        // 分治(单边循环法)确定基准元素
        private int partition(int[] arr, int left, int right) {
            // 设定基准值
            int pivot = left;
            int index = pivot + 1;
            for (int i = index; i <= right; i++) {
                if (arr[i] < arr[pivot]) {
                    swap(arr, i, index);
                    index++;
                }
            }
            swap(arr, pivot, index - 1);
            return index - 1;
        }
    
        private void swap(int[] arr, int i, int j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    

    7. 堆排序

        /**
         * 堆排序:平均时间复杂度O(nlogn) ,最坏时间复杂度O(nlogn) ,最好时间复杂度O(nlogn) ,空间O(1),不稳定
         * <p>
         * 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
         * 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点.
         * <p>
         * 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
         * 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
         * <p>
         * 1.创建一个堆 H[0……n-1];
         * 2.把堆首(最大值)和堆尾互换;
         * 3.把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
         * 4.重复步骤 2,直到堆的尺寸为 1
         */
        private void heapSort(int[] array) {
            int len = array.length;
            buildMapHeap(array, len);
            for (int i = len - 1; i > 0; i--) {
                swap(array, 0, i);
                len--;
                heapify(array, 0, len);
            }
        }
    
        private void buildMapHeap(int[] arr, int len) {
            for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
                heapify(arr, i, len);
            }
        }
    
        // 堆化
        private void heapify(int[] arr, int i, int len) {
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            int largest = i;
    
            if (left < len && arr[left] > arr[largest]) {
                largest = left;
            }
    
            if (right < len && arr[right] > arr[largest]) {
                largest = right;
            }
    
            if (largest != i) {
                swap(arr, i, largest);
                heapify(arr, largest, len);
            }
        }
    

    8. 计数排序

        /**
         * 计数排序:平均时间复杂度O(n+k) ,最坏时间复杂度O(n+k) ,最好时间复杂度O(n+k) ,空间O(n+k),稳定
         * <p>
         * 核心是将输入的数据值转化为键存储在额外开辟的数组空间中。
         * <p>
         * 1.找出待排序的数组中最大和最小的元素
         * 2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
         * 3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
         * 4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
         */
        private void countingSort(int[] array) {
            int maxValue = getMaxValue(array);
    
            int bucketLen = maxValue + 1; // 计数桶长度
            int[] bucket = new int[bucketLen];
            // 初始化计数桶
            for (int value : array) {
                bucket[value]++;
            }
    
            int sortedIndex = 0;
            for (int j = 0; j < bucketLen; j++) {
                while (bucket[j] > 0) {
                    array[sortedIndex++] = j;
                    bucket[j]--;
                }
            }
        }
    
        private int getMaxValue(int[] arr) {
            int maxValue = arr[0];
            for (int value : arr) {
                if (maxValue < value) {
                    maxValue = value;
                }
            }
            return maxValue;
        }
    

    9. 桶排序

        /**
         * 桶排序:平均时间复杂度O(n+k) ,最坏时间复杂度O(n²) ,最好时间复杂度O(n) ,空间O(n+k),稳定
         * <p>
         * 计数排序的升级版。
         * 它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定.
         * <p>
         * 1.在额外空间充足的情况下,尽量增大桶的数量
         * 2.使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
         */
        private void bucketSort(int[] array) {
            int bucketSize = 5;
    
            // 寻找最大最小数
            int minValue = array[0];
            int maxValue = array[0];
            for (int value : array) {
                if (value < minValue) {
                    maxValue = value;
                } else if (value > maxValue) {
                    maxValue = value;
                }
            }
    
            // 初始化桶
            int bucketCount = (int) Math.floor((maxValue - maxValue) / bucketSize) + 1;
            int[][] buckets = new int[bucketCount][0];
    
            // 利用映射函数将数据分配到各个桶中
            for (int i = 0; i < array.length; i++) {
                int index = (int) Math.floor((array[i] - minValue) / bucketSize);
                buckets[index] = arrAppend(buckets[index], array[i]);
            }
    
            int arrIndex = 0;
            for (int[] bucket : buckets) {
                if (bucket.length <= 0) continue;
                // 对每个桶进行排序,这边排序可用前面介绍过的排序算法
                insertSort(bucket);
                for (int value : bucket) {
                    array[arrIndex++] = value;
                }
            }
        }
    
        /**
         * 自动扩容,并保存数据
         */
        private int[] arrAppend(int[] arr, int value) {
            arr = Arrays.copyOf(arr, arr.length + 1);
            arr[arr.length - 1] = value;
            return arr;
        }
    

    参考链接:

    十大经典排序算法

    相关文章

      网友评论

        本文标题:Algorithm -- 排序算法

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