快速排序、归并排序、希尔排序(2023-12-25)

参考文章 十大经典排序算法总结整理_十大排序算法-CSDN博客

推荐文章 算法:归并排序和快排的区别_归并排序和快速排序的区别-CSDN博客

package com.tarena.test.B20;

import java.util.Arrays;
import java.util.StringJoiner;

public class B25 {
    static int num = 20;
    static {
        num = 10;
    }

    public static void main(String[] args) {
        // num从准备到初始化值变化过程 num=0 -> num=20 -> num=10
        // System.out.println(num);//10
        Integer[] arr = new Integer[] { 15, 3, 2, 26, 38, 36, 50, 48, 47, 19, 44, 46, 27, 5, 4 };
        print(arr);
        // 快速排序
        print(quickSort(Arrays.copyOf(arr, arr.length)));
        // 归并排序
        print("归并排序", mergeSort(Arrays.copyOf(arr, arr.length), 0, arr.length - 1));
        // 希尔排序
        print("希尔排序", grepSort(Arrays.copyOf(arr, arr.length)));
    }
    

    
    /**
     * 快速排序 快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,
     * 则可分别对这两部分记录继续进行排序,以达到整个序列有序。
     * 
     * 4.1 算法描述
     * 
     * 快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
     * 
     * 从数列中挑出一个元素(通常选第一个元素),称为 “基准”(pivot);
     * 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
     * 在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 对左右两个分区重复以上步骤直到所有元素都是有序的。
     * 
     * @param arr
     * @return
     */
    public static Integer[] quickSort(Integer[] a) {
        return quickSort(a, 0, a.length - 1);
    }

    public static Integer[] quickSort(Integer[] a, int _left_, int _right_) {
        int left = _left_;
        int right = _right_;
        int temp = a[left]; // 每次把最左边的元素left当做基准,这里必须是left,注意!!!!
        if (left >= right)
            return a;
        // 从左右两边交替扫描,直到left = right
        while (left != right) {
            while (right > left && a[right] >= temp)
                right--; // 从右往左扫描,找到第一个比基准元素小的元素
            a[left] = a[right]; // 找到后直接将a[right]赋值给a[l],赋值完之后a[right],有空位

            while (left < right && a[left] <= temp)
                left++; // 从左往右扫描,找到第一个比基准元素大的元素
            a[right] = a[left]; // 找到这种元素arr[left]后,赋值给arr[right],上面说的空位。
        }
        a[left] = temp;
        // 把基准插入,此时left与right已经相等
        /* 拆分成两个数组 s[0,left-1]、s[left+1,n-1]又开始排序 */
        quickSort(a, _left_, left - 1); // 对基准元素左边的元素进行递归排序
        quickSort(a, right + 1, _right_); // 对基准元素右边的进行递归排序
        return a;
    }

    /**
     * 5、归并排序(Merge Sort) 相关文章:归并排序 - 简书
     * 
     * 和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
     * 
     * 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and
     * Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
     * 
     * 5.1 算法描述
     * 
     * 把长度为n的输入序列分成两个长度为n/2的子序列; 对这两个子序列分别采用归并排序; 将两个排序好的子序列合并成一个最终的排序序列。
     * 
     * 下面一种是比较好理解的,原理如下(假设序列共有n个元素)
     * 
     * 将原始序列从中间分为左、右两个子序列,此时序列数为2 将左序列和右序列再分别从中间分为左、右两个子序列,此时序列数为4
     * 重复以上步骤,直到每个子序列都只有一个元素,可认为每一个子序列都是有序的 最后依次进行归并操作,直到序列数变为1
     * 
     * 5. 4 算法分析
     * 
     * 最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
     * 
     * @param a
     */
    public static Integer[] mergeSort(Integer[] a, int left, int right) {
        if (left >= right)
            return a;

        int center = (left + right) >> 1;
        mergeSort(a, left, center);
        mergeSort(a, center + 1, right);
        merge(a, left, center, right);
        return a;
    }

    public static void merge(Integer[] data, int left, int center, int right) {
        int[] tmpArr = new int[right + 1];
        int mid = center + 1;
        int index = left; // index记录临时数组的索引
        int tmp = left;

        // 从两个数组中取出最小的放入中临时数组
        while (left <= center && mid <= right) {
            tmpArr[index++] = (data[left] <= data[mid]) ? data[left++] : data[mid++];
        }
        // 剩余部分依次放入临时数组
        while (mid <= right) {
            tmpArr[index++] = data[mid++];
        }
        while (left <= center) {
            tmpArr[index++] = data[left++];
        }
        // 将临时数组中的内容复制回原数组
        for (int i = tmp; i <= right; i++) {
            data[i] = tmpArr[i];
        }
        // System.out.println(Arrays.toString(data));
    }

    /**
     * 希尔排序
     

6、希尔排序(Shell Sort)
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

6.1 算法描述
我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
 

     * @param name
     * @param arr
     */

    public static Integer[] grepSort(Integer[] array) {
        int len = array.length;
        if (len < 2) {
            return array;
        }
        // 当前待排序数据,该数据之前的已被排序
        int current;
        // 增量
        int gap = len / 2;
        while (gap > 0) {
            for (int i = gap; i < len; i++) {
                current = array[i];
                // 前面有序序列的索引
                int index = i - gap;
                while (index >= 0 && current < array[index]) {
                    array[index + gap] = array[index];
                    // 有序序列的下一个
                    index -= gap;
                }
                // 插入
                array[index + gap] = current;
            }
            // int相除取整
            gap = gap / 2;
        }
        return array;

    }

    public static void print(String name, Integer[] arr) {
        StringJoiner sj = new StringJoiner("-");
        Arrays.stream(arr).forEach(num -> sj.add(String.valueOf(num)));
        System.out.println(name + ":运行结果:" + sj.toString());
    }

    public static void print(Integer[] arr) {
        StringJoiner sj = new StringJoiner("-");
        Arrays.stream(arr).forEach(num -> sj.add(String.valueOf(num)));
        System.out.println("运行结果:" + sj.toString());
    }
}

了解知识点

1、复习一下三种基本的排序算法。

2、了解一下 static 属性字段的赋值过程。

你可能感兴趣的:(排序算法,算法,数据结构)