【数据结构与算法】排序算法总结:冒泡 / 快排 / 直接插入 / 希尔 / 简单选择 / 堆排序 / 归并排序

news2024/12/27 5:53:37

1 排序

1.1 冒泡

内排序的交换排序类别

1.1.1 普通实现

public class BubbleSort {

    /**
     * 基本的 冒泡排序
     */
    public static void bubbleSort(int[] srcArray) {
        int i,j; // 用于存放数组下标
        int temp = 0; // 用于交换数值时临时存放值

        for(i=0;i<srcArray.length-1;i++){
            // j 从后往前循环
            for(j=srcArray.length-2;j>=i;j--){
                // 若前者>后者,则交换位置
                if(srcArray[j]>srcArray[j+1]){
                    temp=srcArray[j];
                    srcArray[j]=srcArray[j+1];
                    srcArray[j+1]=temp;
                }
            }
        }

        // 输出排序后的序列
        for(int a =0;a<srcArray.length;a++)
            System.out.println(srcArray[a]);
    }

    /**
     * 执行 冒泡排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{9, 1, 5, 8, 3, 7, 4, 2, 6};
        // 输出结果
        bubbleSort(src);
    }
}

1.1.2 代码优化

  • 从j比到i,从0-i已经全部有序,i之前的不用再比较
  • flag = true:代表存在数据交换,即序列仍需排序,需继续循环
public class BubbleSort {
    /**
     * 优化的 冒泡排序
     */
    public static void bubbleSortOpti(int[] srcArray) {

        int i,j; // 用于存放数组下标
        int temp = 0; // 用于交换数值时临时存放值

        // 标记位
        // flag = true:代表存在数据交换,即序列仍需排序,需继续循环
        // flag = false:代表不存在数据交换,即序列不需排序,已经是有序序列了,可停止循环
        Boolean flag = true;
        // 若flag = false时退出循环
        for(i=0;i<srcArray.length-1 && flag;i++){

            flag = false; // 初始化为false

            // j 从后往前循环
            for(j=srcArray.length-2;j>=i;j--){
                // 若前者>后者,则交换位置
                if(srcArray[j]>srcArray[j+1]){
                    temp=srcArray[j];
                    srcArray[j]=srcArray[j+1];
                    srcArray[j+1]=temp;
                    flag = true; // 若有数据交换,则说明序列仍未无序
                }
            }
        }

        // 输出排序后的序列
        for(int a =0;a<srcArray.length;a++)
            System.out.println(srcArray[a]);
    }

    /**
     * 执行 优化后的冒泡排序
     */
    public static void main(String[] args) {
        // 定义待排序数列
        int[] src = new int[]{2, 1, 3, 4, 5, 6, 7, 8, 9};
        // 输出结果
        bubbleSortOpti(src);
    }
}

1.1.3 性能分析

在这里插入图片描述

1.2 快排

冒泡的升级,内排序的交换排序类别

1. 步骤1:将待排序列 分割成独立的2个子序列

  • 在待排序 序列中选择1个基准数据元素(第1个 / 最后1个,称为:枢轴)
  • 通过比较 基准数据元素 与 序列其余元素 大小,将待排序列分成2部分:(右序列)1部分 > 基准元素、(左序列)1部分 < 基准元素

2. 步骤2:通过递归,分别对这2个子序列 进行快速排序
通过步骤2的方式,最终达到整个序列有序的目的

1.2.1 普通实现

  • 步骤1:通过分区函数Partition()将序列分割成2个独立子序列(高、低)
  • 步骤2:对上述2个子序列使用快速排序方法进行递归
public class QuickSort {

    /**
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static void quickSort(int[] srcArray, int low, int high) {

        if (low < high) {

            // 1. 将待排序列 根据所选的枢纽位置,分割成独立的2个子序列
            // 最终返回的是枢纽位置
            int privot = Partition(srcArray, low, high);

            // 2. 分别对这2个子序列 进行排序
            // a. 通过递归 对低字表进行排序
            quickSort(srcArray, low, privot - 1);
            // b. 通过递归 对高字表进行排序
            quickSort(srcArray, privot + 1, high);
        }
	}
    /**
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        // 1. 将子表的第1个个记录作为枢纽
        int tmp = srcArray[low];

        while (low < high) {

            // 2. 比较高位元素 & 枢纽元素
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }

            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;

            // 3. 比较低位元素 & 枢纽元素
            while (low < high && srcArray[low] <= tmp) {
                low++;
            }
            int temp1 = srcArray[high];
            srcArray[high] = srcArray[low];
            srcArray[low] = temp1;
        }

        // 4. 最终低位、高位都会指向枢纽位置,返回
        return low;
    }

 /**
     * 执行 快速排序
     */
    public static void main(String[] args) {
        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };
        // 输出结果
        quickSort(src,0,src.length-1);
        // 输出 排序后的序列
        for (int a = 0; a < src.length; a++) {
            System.out.println(src[a]);
        }
    }
}

1.2.2 优化枢轴的选择

有随机数法、三数取中法、九数取中法,此处用三数取中法:

主要修改处:在分区函数Partition()中将枢纽的选择方式改为三数取中,具体原理是:

  1. 找出中间元素
  2. 比较左、右端数据元素,保证左端较小(若左>右,就交换位置)
  3. 比较中、右端数据元素,保证中端较小(若中>右,就交换位置)
  4. 比较中、左端数据元素,保证中端较小(若中>左,就交换位置)
public class QuickSort {

    /**
     * 快速排序算法实现(基础实现)
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static void quickSort(int[] srcArray, int low, int high) {

        if (low < high) {

            // 1. 将待排序列 根据所选的枢纽位置,分割成独立的2个子序列
            // 最终返回的是枢纽位置(主要优化在取取枢纽值里)
            int privot = Partition(srcArray, low, high);

            // 2. 分别对这2个子序列 进行排序
            // a. 通过递归 对低字表进行排序
            quickSort(srcArray, low, privot - 1);
            // b. 通过递归 对高字表进行排序
            quickSort(srcArray, privot + 1, high);
        }
        
    }

    /**
     * 作用:将待排序列 根据所选的枢纽位置,分割成独立的2个子序列(优化 = 选取枢轴 = 三数取中)
     * 返回值:所选的枢纽位置
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        /**
         * 三数取中的方式
         */
        // 1. 找出中间元素
        // 不是使用(low+high)/2的原因:容易溢出
        // 右移1位 = 除以2,但右移的运算速度更快
        int m = low + ( (high-low)>>1 );

        // 2. 比较左、右端数据元素,保证左端较小
        // 若左>右,就交换位置
        if(srcArray[low]>srcArray[high]) {
            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;
        }

        // 3. 比较中、右端数据元素,保证中端较小
        // 若中>右,就交换位置
        if(srcArray[m]>srcArray[high]) {
            int temp1 = srcArray[m];
            srcArray[m] = srcArray[high];
            srcArray[high] = temp1;
        }

        // 4. 比较中、左端数据元素,保证中端较小
        if(srcArray[m]>srcArray[low]) {
            // 若中>左,就交换位置
            int temp2 = srcArray[m];
            srcArray[m] = srcArray[low];
            srcArray[low] = temp2;
        }

        // 此时,最低位 = srcArray[low] = 三数的中间数(即 最低位、最高位 & 中间数的中间值)

        // 将上述值作为枢纽
        int tmp = srcArray[low];
        System.out.println("枢轴位置 =" + srcArray[low]);

        
        /**
         * 下面代码类似未优化前(即,基础实现)
         */
        while (low < high) {

            // 比较高位元素 & 枢纽元素
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }

            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;

            // 比较低位元素 & 枢纽元素
            while (low < high && srcArray[low] <= tmp) {
                low++;
            }
            int temp1 = srcArray[high];
            srcArray[high] = srcArray[low];
            srcArray[low] = temp1;
        }

        // 最终低位、高位都会指向枢纽位置,返回
        return low;
    }


    /**
     * 执行 快速排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 输出结果
        quickSort(src,0,src.length-1);

        // 输出 排序后的序列
        for (int a = 0; a < src.length; a++) {
            System.out.println(src[a]);
        }

    }
}

1.2.3 减少不必要的交换

主要修改点在分区函数Partition()中

   /**
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        // 1. 将子表的第1个个记录作为枢纽
        int tmp = srcArray[low];

        while (low < high) {
            // 2. 比较高位元素 & 枢纽元素
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }

            // 采用 替换操作 换掉之前的 交换操作
            srcArray[low] = srcArray[high];
            // 之前的交换操作
            // int temp = srcArray[low];
            // srcArray[low] = srcArray[high];
            // srcArray[high] = temp;
            
            // 3. 比较低位元素 & 枢纽元素

            while (low < high && srcArray[low] <= tmp) {
                low++;
            }

            // 采用 替换操作 换掉之前的 交换操作
            srcArray[high] = srcArray[low];
            // 之前的交换操作
            // int temp1 = srcArray[high];
            // srcArray[high] = srcArray[low];
            // srcArray[low] = temp1;

        }
        // 将枢轴元素替换到当前低位指针指向的元素 & 返回
        srcArray[low] = tmp;
        return low;
    }

1.2.4 优化数据量较小序列的排序方案

  • 对于数据量较大的序列:采用快速排序

资料显示,当序列的数据量>7时,视为大数据量序列

  • 对于数据量较小的序列:采用 直接插入排序
    a. 直接插入排序是简单排序算法中性能最好的
    b. 优化主要在quickSort()中
public class QuickSort {

    /**
     * 快速排序算法实现(优化 = 优化数据量较小序列的排序方案)
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static void quickSort(int[] srcArray, int low, int high) {

        // 当序列的数据量>7时,视为大数据量序列,此时采用 快速排序
        if (high-low > 7) {

            if (low < high) {
                System.out.println("采用快排");
                int privot = Partition(srcArray, low, high);
                quickSort(srcArray, low, privot - 1);
                quickSort(srcArray, privot + 1, high);
            }
        }

        else{
            // 当序列的数据量<7时,视为小数据量序列,此时采用 直接插入排序
            insertSort(srcArray);
            System.out.println("采用直接插入排序");
        };


    }

    /**
     * 作用:将待排序列 根据所选的枢纽位置,分割成独立的2个子序列(优化 = 优化数据量较小序列的排序方案)
     * 返回值:所选的枢纽位置
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        // 1. 将子表的第1个个记录作为枢纽
        int tmp = srcArray[low];

        while (low < high) {

            // 2. 比较高位元素 & 枢纽元素
            // 若高位元素 > 枢纽元素,则继续比较前1个高位元素
            // 若高位元素 < 枢纽元素,则交换当前高位元素 与 低位元素 位置、开始比较低位元素 与 枢纽元素
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }

            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;


            // 3. 比较低位元素 & 枢纽元素
            // 若低位元素 < 基准元素,则继续比较下1个低位元素
            // 若低位元素 > 枢纽元素,就交换当前低位元素 与 高位元素 位置;重新开始比较高位元素 与 枢纽元素
            while (low < high && srcArray[low] <= tmp) {
                low++;
            }
            int temp1 = srcArray[high];
            srcArray[high] = srcArray[low];
            srcArray[low] = temp1;
        }

        // 最终低位、高位都会指向枢纽位置,返回
        return low;
    }

    /**
     * 直接插入排序 算法实现
     */
    public static void insertSort(int[] srcArray) {

        int i; // 用于存放当前插入数据记录的数组下标
        int j; // 用于存放需要比较记录的下标
        int temp; // 用于交换数据

        // 从第1个数据记录 开始,该元素可以认为已经被排序
        for(i = 0 ; i < srcArray.length ; i++)
        {
            temp = srcArray[i];

            // 取出下一个数据记录,在已经排序的序列中从后向前扫描
            // 将 当前数据记录 与 前面排序好的值进行比较
            for(j = i ; j > 0 && temp < srcArray[j-1] ; j --)
            {
                // 按照顺序小 -> 大 将 当前需要插入的数据记录插入到合适位置 = 后移已排序好的元素 + 插入新的数据记录
                // a. 后移已排序好的元素
                srcArray[j] = srcArray[j-1];
            }

            // 插入新的数据记录
            srcArray[j] = temp;
        }
    }


    /**
     * 执行 快速排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 输出结果
        quickSort(src,0,src.length-1);

        // 输出 排序后的序列
        for (int a = 0; a < src.length; a++) {
            System.out.println(src[a]);
        }

    }
}

特别注意:此处的排序方式选择不只是第一次排序,而是贯穿 整个快速排序过程,即 由于快速排序过程 = 逐步划分成半子表的过程,所以最后几次的排序一定会使用 直接插入排序

1.2.5 优化递归操作

将快速排序算法最后的 尾部递归操作 改成 迭代操作

  • 可有效缩减所需的堆栈深度,从而有效提高性能
  • 主要在主要算法的quickSort()中
public class QuickSort {

    /**
     * 快速排序算法实现(优化 = 优化递归 = 采用 迭代操作 代替 递归操作)
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static void quickSort(int[] srcArray, int low, int high) {

        // 将if 改成 While,原来操作为:if (low < high)
        while (low < high) {

            int privot = Partition(srcArray, low, high);
            quickSort(srcArray, low, privot - 1);
            
            low = privot +1 ;
            // 将 尾递归中对高字表的排序 改成 low = privot +1,原来操作为:quickSort(srcArray, privot + 1, high);
            // 原因:在第1次循环后,后1次循环中的Partition(srcArray, low, high) = quickSort(srcArray, privot + 1, high)
            // 故可删去该递归
        }

    }

    /**
     * 作用:将待排序列 根据所选的枢纽位置,分割成独立的2个子序列(优化 = 优化递归 = 采用 迭代操作 代替 递归操作)
     * 返回值:所选的枢纽位置
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        // 1. 将子表的第1个个记录作为枢纽
        int tmp = srcArray[low];

        while (low < high) {

            // 2. 比较高位元素 & 枢纽元素
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }

            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;

            // 3. 比较低位元素 & 枢纽元素
            while (low < high && srcArray[low] <= tmp) {
                low++;
            }
            int temp1 = srcArray[high];
            srcArray[high] = srcArray[low];
            srcArray[low] = temp1;
        }

        // 最终低位、高位都会指向枢纽位置,返回
        return low;
    }


    /**
     * 执行 快速排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 输出结果
        quickSort(src,0,src.length-1);

        // 输出 排序后的序列
        for (int a = 0; a < src.length; a++) {
            System.out.println(src[a]);
        }

    }
}

1.2.6 终极优化

结合上述4种优化的终极优化版:

public class QuickSort {
   /**
     * 快速排序算法实现(优化 = 选取枢轴、减少不必要的交换次数、优化数据量较小序列的排序方案、将尾递归操作->迭代操作)
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static void quickSort(int[] srcArray, int low, int high) {

        /**
         * 优化3:优化数据量较小序列的排序方案 = 步骤8、9
         */
        if (high-low > 7) {
            // 8. 当序列的数据量>7时,视为大数据量序列,此时采用 快速排序
            System.out.println("采用快排");

            /**
             * 优化4:将尾递归操作->迭代操作 = 步骤10、11
             */
            // 10. 将if 改成 While,原来操作为:if (low < high)
            while (low < high) {
            int privot = Partition(srcArray, low, high);
            quickSort(srcArray, low, privot - 1);
            // 11. // 将 尾递归中对高字表的排序 改成 low = privot +1,原来操作为:quickSort(srcArray, privot + 1, high);
            // quickSort_RecursionOp(srcArray, middle + 1, high);
            low = privot +1 ;
        }
    }
        else{
            // 9. 当序列的数据量<7时,视为小数据量序列,此时采用 直接插入排序
            insertSort(srcArray);
            System.out.println("采用直接插入排序");
        }
    }

    /**
     * 快速排序算法中寻找中间元素 实现(优化 = 选取枢轴、减少不必要的交换次数、优化数据量较小序列的排序方案、将尾递归操作->迭代操作)
     * 参数说明:
     * @param srcArray = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */
    public static int Partition(int[] srcArray, int low, int high) {

        /**
         * 优化1:三数取中选取枢轴 = 步骤1、2、3、4、5
         */
        // 1. 找出中间元素
        int m = low + (high - low) /2;

        // 2. 比较左、右端数据元素,保证左端较小
        // 若左>右,就交换位置
        if(srcArray[low]>srcArray[high]) {
            int temp = srcArray[low];
            srcArray[low] = srcArray[high];
            srcArray[high] = temp;
        }

        // 3. 比较中、右端数据元素,保证中端较小
        // 若中>右,就交换位置
        if(srcArray[m]>srcArray[high]) {
            int temp1 = srcArray[m];
            srcArray[m] = srcArray[high];
            srcArray[high] = temp1;
        }

        // 4. 比较中、左端数据元素,保证中端较小
        if(srcArray[m]>srcArray[low]) {
            // 若中>左,就交换位置
            int temp2 = srcArray[m];
            srcArray[m] = srcArray[low];
            srcArray[low] = temp2;
        }

        // 此时,最低位 = srcArray[low] = 三数的中间数(即 最低位、最高位 & 中间数的中间值)
        // 将上述值作为枢纽
        int tmp = srcArray[low];
        System.out.println("枢轴位置 =" + srcArray[low]);

     /**
       * 优化2:减少不必要的交换次数 = 步骤5.6.7
       */
        while (low < high) {
            while (low < high && srcArray[high] >= tmp) {
                high--;
            }
            // 5. 采用 替换操作 换掉之前的 交换操作
            srcArray[low] = srcArray[high];
            // 之前的交换操作
            // int temp = srcArray[low];
            // srcArray[low] = srcArray[high];
            // srcArray[high] = temp;
            while (low < high && srcArray[low] <= tmp) {
                low++;
            }
            // 6. 采用 替换操作 换掉之前的 交换操作
            srcArray[high] = srcArray[low];
            // 之前的交换操作
            // int temp1 = srcArray[high];
            // srcArray[high] = srcArray[low];
            // srcArray[low] = temp1;
        }
        // 7. 将枢轴元素替换到当前低位指针指向的元素 & 返回
        srcArray[low] = tmp;
        return low;
    }

    /**
     * 直接插入排序 算法实现
     */
    public static void insertSort(int[] srcArray) {

        int i; // 用于存放当前插入数据记录的数组下标
        int j; // 用于存放需要比较记录的下标
        int temp; // 用于交换数据

        // 从第1个数据记录 开始,该元素可以认为已经被排序
        for(i = 0 ; i < srcArray.length ; i++)
        {
            temp = srcArray[i];
            // 取出下一个数据记录,在已经排序的序列中从后向前扫描
            // 将 当前数据记录 与 前面排序好的值进行比较
            for(j = i ; j > 0 && temp < srcArray[j-1] ; j --)
            {
                // 按照顺序小 -> 大 将 当前需要插入的数据记录插入到合适位置 = 后移已排序好的元素 + 插入新的数据记录
                // a. 后移已排序好的元素
                srcArray[j] = srcArray[j-1];
            }
            // 插入新的数据记录
            srcArray[j] = temp;
        }
    }

    /**
     * 执行 快速排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };
        // 输出结果
        quickSort(src,0,src.length-1);
        // 输出 排序后的序列
        for (int a = 0; a < src.length; a++) {
            System.out.println(src[a]);
        }
    }
}

1.2.7 性能分析

在这里插入图片描述

1.3 直接插入排序

属于 内排序算法中 的 插入排序类别

  • 将 1个待排序的数据 按顺序大小 插入到 1个已排序的序列中
  • 重复上述步骤,直到全部插入 & 排序完为止

1.3.1 代码实现

public class InsertSort {
    /**
     * 简单选择排序
     */
    public static void insertSort(int[] srcArray) {

        int i; // 用于存放当前插入数据记录的数组下标
        int j; // 用于存放需要比较记录的下标
        int temp; // 用于交换数据

        // 1. 从第1个数据记录 开始,该元素可以认为已经被排序
        for(i = 0 ; i < srcArray.length ; i++)
            {
                temp = srcArray[i];

                // 2. 取出下一个数据记录,在已经排序的序列中从后向前扫描
                // 3. 将 当前数据记录 与 前面排序好的值进行比较
                for(j = i ; j > 0 && temp < srcArray[j-1] ; j --)
                {
                    // 4. 按照顺序小 -> 大 将 当前需要插入的数据记录插入到合适位置 = 后移已排序好的元素 + 插入新的数据记录
                     // a. 后移已排序好的元素
                    srcArray[j] = srcArray[j-1];
                }

                // b. 插入新的数据记录
                srcArray[j] = temp;
            }

        // 5. 输出排序后的序列
        for(int a =0;a<srcArray.length;a++)
            System.out.println(srcArray[a]);
    }

    /**
     * 执行 直接插入排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{0, 5, 3, 4, 6, 2};

        // 输出结果
        insertSort(src);

    }

}

1.3.2 性能分析

在这里插入图片描述

1.4 希尔排序

  • 也叫 缩小增量 排序,属于 内排序算法中 的 插入排序类别
  • 是对 直接插入排序算法 的优化和升级

核心思想:跳跃分割

  • 将相隔某个增量的记录组成一个子序列
  • 实现跳跃式移动,使得排序效率提高
  • 不是随便分组后各自排序

1.4.1 代码实现

public class ShellSort {
    /**
     * 希尔排序
     */
    public static void shellSort(int[] srcArray) {

        int j = 0;
        int temp = 0;

        // 增量序列值 计算公式 = 前1个增量序列值 / 2,直到增量序列值 = 1为止
        // 第1个值 = 初始值 = 序列长度 / 2
        for (int increment = srcArray.length / 2; increment > 0; increment /= 2) {

            // 根据增量值选取子序列
            for (int i = increment; i < srcArray.length; i++) {

                temp = srcArray[i];

                // 对子序列执行直接插入排序,即 循环两两比较子序列的值
                for (j = i - increment; j >= 0; j -= increment) {

                    if (temp < srcArray[j]) {

                        // 将小的元素放到前面、大的元素放到后面
                        srcArray[j + increment] = srcArray[j];
                    } else {
                        break;
                    }
                }
                srcArray[j + increment] = temp;
            }


            // 输出 根据增量值排序后的序列
            System.out.println("增量值为:" + increment + ",排序结果如下:");
            for (int a = 0; a < srcArray.length; a++) {
                System.out.println(srcArray[a]);
            }
        }
    }

    /**
     * 执行 希尔排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 4, 3, 6, 2, 7, 1, 5, 8 };

        // 输出结果
        shellSort(src);
    }
}

1.4.2 性能分析

在这里插入图片描述

1.5 简单选择排序

n = 表长,i = 当前位置

  1. 比较 第 i 个记录 & 剩余的 (n-i)个记录
  2. 在(n - i +1)个记录中,选择最小的记录
  3. 将最小的记录 与 第 i 个记录进行交换

重复上述过程,直到 i 到序列最后1个元素比较后 结束。

1.5.1 代码实现

public class ChooseSort {

    /**
     * 简单选择排序
     */
    public static void chooseSort(int[] srcArray) {

        int i; // 用于存放当前数组下标
        int j; // 用于存放需要比较记录的下标

            for(i=0;i<srcArray.length;i++){

                // 将当前记录 与 后面记录进行比较
                for(j=i+1;j<srcArray.length;j++){

                    // 若 当前记录 < 后面记录,则交换位置
                    if(srcArray[i] > srcArray[j]){
                        int temp=srcArray [i];
                        srcArray[i] = srcArray[j];
                        srcArray[j] = temp;
                    }
                }
            }


        // 输出排序后的序列
        for(int a =0;a<srcArray.length;a++)
            System.out.println(srcArray[a]);
    }



    /**
     * 执行 简单选择排序
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{9, 1, 5, 8, 3, 7, 4, 2, 6};

        // 输出结果
        chooseSort(src);

    }

}

1.5.2 性能分析

在这里插入图片描述

1.6 堆排序

大顶堆 是 根节点值 > 左右孩子的值的 完全二叉树

利用堆(大 / 小顶堆) 进行排序 的方法

  • 充分利用了完全二叉树深度 = [log2n] + 1的特性
  • 是 简单选择排序 的优化 & 改进

1.6.1 代码实现

public class HeapSort {


    /**
     * 执行 堆排序 算法
     */
    public static void main(String[] args) {

        // 定义待排序数列
        int[] src = new int[]{ 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 输出结果
        heapSort(src);

    }
    

    /**
     * 堆排序算法
     */
    private static void heapSort(int[] arr) {
        // 步骤1:将待排序的序列构建成一个大顶堆
        for (int i = arr.length / 2; i >= 0; i--){
            // i = i / 2 :求出非终端结点(即,具备孩子的结点)
            // 逐渐递减: i = 4-3-2-1
            heapAdjust(arr, i, arr.length);
        }

        
        for (int i = arr.length - 1; i > 0; i--) {
            // 步骤2:交换 根节点 与 末尾元素
            swap(arr, 0, i);
            // 步骤3:将序列剩余的(n-1)个记录重新构造成大顶堆
            heapAdjust(arr, 0, i);
            
            // 循环步骤2 、3,直到整个序列有序
        }

        // 输出排序后的序列
        for(int a =0;a<arr.length;a++)
            System.out.println(arr[a]);

    }

    /**
     * 构建堆的过程
     * 参数说明:
     * @param arr = 需排序的数组
     * @param i = 需要构建堆的根节点的序号
     * @param n = 数组的长度
     */
    private static void heapAdjust(int[] arr, int i, int n) {
        int child;
        int father;
        for (father = arr[i]; leftChild(i) < n; i = child) {
            child = leftChild(i);

            // 若左子树<右子树,则比较右子树和父节点
            if (child != n - 1 && arr[child] < arr[child + 1]) {
                child++; // 序号增1,指向右子树
            }

            // 若父节点<孩子结点,则需要交换
            if (father < arr[child]) {
                arr[i] = arr[child];
            } else {

                // 大顶堆结构未被破坏,不需要调整
                break;
            }
        }
        arr[i] = father;
    }

    // 获取左孩子结点 位置
    private static int leftChild(int i) {
        return 2 * i + 1;
    }


    // 交换元素位置
    private static void swap(int[] arr, int index1, int index2) {
        int tmp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = tmp;
    }
}

1.6.2 性能分析

在这里插入图片描述
不适合待排序序列个数较少的情况,原因 = 初始构建堆的比较次数较多

1.7 归并排序

分治 + 归并

有2种实现方式:递归 & 非递归方式

1.7.1 递归实现

public class MergeSort {

    /**
     * 归并排序算法实现
     * 参数说明:
     * @param arr = 需排序的数组序列
     * @param low = 数组第1个元素下标
     * @param high = 数组最后1个元素下标
     */

    public static void mergeSort(int[] arr, int low, int high) {

        // 1. 计算出序列中间元素下标
        // 使用(low+high)/2 求中间位置容易溢出
        // 右移1位,相当于除以2,但右移的运算速度更快
        int mid = low + ( (high-low)>>1 );

        if (low < high) {
            // 2. (分治) 将初始序列 逐步对半划分成半子表,直到初始序列 = n个子序列
            // 通过 递归 实现
              // a. 左一半(第1个元素 - 中间元素)
              mergeSort(arr, low, mid);
              // b. 右一半( 中间元素后1位 - 最后1个元素)
              mergeSort(arr, mid + 1, high);

            // 3. (归并)将划分的子序列 有序的两两合并,最终合并成1个长度 = n 的有序序列
            merge(arr, low, mid, high);
        }

    }

    /**
     * 归并排序算法中的有序合并序列 实现
     * 参数说明:
     * @param arr = 需排序的数组序列
     * @param low = 数组第1个元素 下标
     * @param mid = 数组中间元素 下标
     * @param high = 数组最后1个元素 下标
     */

    public static void merge(int[] arr, int low, int mid, int high) {

        // 1. 定义1个辅助数组用于存储结果
        int[] temp = new int[high - low + 1];

        int i = low; // 左指针,指向数组第1个元素 下标
        int j = mid + 1; // 右指针,指向数组中间元素的后1个下标
        int k = 0;

        // 2. 比较左、右两边的元素大小,将较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (arr[i] < arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        // 3. 把左边剩余的数移入新数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 4. 把右边剩余的数移入新数组
        while (j <= high) {
            temp[k++] = arr[j++];
        }

        // 5. 把新数组中的数覆盖到原有数组中
        for (int k2 = 0; k2 < temp.length; k2++) {
            arr[k2 + low] = temp[k2];
        }
    }

    /**
     * 执行 归并排序算法
     */
    public static void main(String[] args) {
        // 待排序序列
        int arr[] = { 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 执行 归并排序序列
        mergeSort(arr, 0, arr.length - 1);

        // 输出排序后的序列
        for(int a =0;a<arr.length;a++)
            System.out.println(arr[a]);
    }
}

1.7.2 非递归实现

public class MergeSort {

    /**
     * 归并排序算法实现:非递归
     * 参数说明:
     * @param arr = 需排序的数组序列
     */

    public static void mergeSort(int[] arr) {

        int len = arr.length;
        int k = 1;

        while(k < len)
        {
            MergePass(arr, k, len);
            k *= 2; // 一组组归并:1、2、4、8、16
        }

    }

    /**
     * 辅助算法
     * 作用:归并 数组中的相邻长度 = k的元素
     */

    private static void MergePass(int[] arr, int k, int n)
    {
        int i = 0;
        int j;

        // 从前->后,将2个长度为k的子序列合并为1个
        while(i < n - 2*k + 1)
        {
            merge(arr, i, i + k-1, i + 2*k - 1);
            // 参数2 = 距离长度
            // 参数3、4 = 合并的位置,如合并第1个 & 第2个位置的元素到新建的数组中
            i += 2*k;
        }

        // 该代码的作用:保证将最后“落单”的、长度不足两两合并的部分 和 前面的合并起来
        if(i < n - k )
        {
            merge(arr, i, i+k-1, n-1);
        }

    }

    /**
     * 归并排序算法中的有序合并序列 实现
     * 参数说明:
     * @param arr = 需排序的数组序列
     */

    public static void merge(int[] arr, int low, int mid, int high) {

        // 辅助数组 = 暂存合并的结果
        int[] temp = new int[high - low + 1];

        int i = low; // 左指针
        int j = mid + 1; // 右指针
        int k = 0;

        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (arr[i] < arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        // 把右边剩余的数移入数组
        while (j <= high) {
            temp[k++] = arr[j++];
        }
        // 把新数组中的数覆盖nums数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            arr[k2 + low] = temp[k2];
        }
    }

    /**
     * 执行 归并排序算法

     */
    public static void main(String[] args) {
        // 待排序序列
        int arr[] = { 50, 10, 90, 30, 70, 40, 80, 60, 20 };

        // 执行 归并排序序列
        mergeSort(arr);

        // 输出排序后的序列
        for(int a =0;a<arr.length;a++)
            System.out.println(arr[a]);
    }
}

1.7.3 性能分析

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式储能监控系统为储能电站高效运维与精细化管理赋能

1、引言 随着全球对可持续发展和环境保护意识的增强&#xff0c;能源结构正在经历深刻的转型。传统化石能源因其不可再生性和环境污染问题而逐渐受到限制&#xff0c;而可再生能源如太阳能、风能等因其清洁、可持续的特性而受到广泛关注和推广。这一转型推动了储能技术的快速发…

SciAssess——评估大语言模型在科学文献处理中关于模型的记忆、理解和分析能力的基准

概述 大规模语言模型&#xff08;如 Llama、Gemini 和 GPT-4&#xff09;的最新进展因其卓越的自然语言理解和生成能力而备受关注。对这些模型进行评估对于确定其局限性和潜力以及促进进一步的技术进步非常重要。为此&#xff0c;人们提出了一些特定的基准来评估大规模语言模型…

TiDB 关联子查询及半连接的优化实践

导读 TiDB 针对子查询语句会执行多种子查询相关的优化 ( https://docs.pingcap.com/zh/tidb/stable/subquery-optimization )&#xff0c;以提升子查询的执行性能。半连接语句和关联子查询语句是常用的两类子查询&#xff0c;TiDB 优化器默认包含一些自动优化策略&#xff0c;…

Mac 配置pytorch ---- 保姆级教程

一、安装AnaConda 或者轻量级的 miniConda AnaConda安装地址&#xff1a;https://www.anaconda.com/ miniConda安装地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda 1) 以miniConda 为例&#xff1a; 进入地址后&#xff0c;找到最新的安装版本下载…

C/C++每日一练:删除链表的倒数第N个节点

链表&#xff08;Linked List&#xff09; 链表是一种线性数据结构&#xff0c;由一系列节点&#xff08;Node&#xff09;通过指针链接在一起。与数组不同&#xff0c;链表中的元素在内存中不需要连续存储&#xff0c;每个节点包含两部分&#xff1a; 数据部分&#xff1a;存…

【python】爬去二手车数据 未完成

技术方案 python selenium 先下载Microsoft Edge WebDriver Microsoft Edge WebDriver 官网 先看一下自己的edge版本 搜索到版本然后下载自己的版本 安装依赖 pip install seleniumimport time from selenium import webdriverdriver webdriver.Edge(executable_pathr&qu…

在鸿蒙应用中 Debug 对开发者的帮助

文章目录 摘要引言Debug 的意义与挑战案例&#xff1a;页面渲染性能优化中的 Bug 排查Debug 过程详解问题定位问题解决优化布局与渲染逻辑 代码详细讲解示例代码详细讲解1. 导入必要模块2. 数据生成3. 使用虚拟列表组件items 属性itemHeight 属性renderItem 属性 4. 返回完整组…

YOLO-学习笔记

文章目录 划分区域筛选需要的目标聚类NMS(非极大值抑制) YOLOV1代码解析特征提取层 He 初始化&#xff08;He Initialization&#xff09;问题He 初始化的原理解释&#xff1a; 检测头train()输入处理函数target_processYOLO-V2基于Anchor的偏移量Ground Truth (GT)&#xff1a…

虚拟机VMware安装OpenWrt镜像

前提已经安装VMware Workstation Pro,我使用的是VM16 一.下载OpenWrt系统固件 固件有很多种&#xff0c;我选择下面这个链接的固件: Index of /releases/23.05.3/targets/x86/64/ 下载好之后将红框的镜像解压成绿框的镜像 二.安装转换工具 转换工具下载地址&#xff1a;htt…

Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解

文章目录 前言原型模式一、浅拷贝1、案例2、引用数据类型 二、深拷贝1、重写clone()方法2、序列化 总结 前言 先看一下传统的对象克隆方式&#xff1a; 原型类&#xff1a; public class Student {private String name;public Student(String name) {this.name name;}publi…

go使用mysql实现增删改查操作

1、安装MySQL驱动 go get -u github.com/go-sql-driver/mysql2、go连接MySQL import ("database/sql""log"_ "github.com/go-sql-driver/mysql" // 导入 mysql 驱动 )type Users struct {ID intName stringEmail string }var db *sql.DBfu…

vulnhub靶场【哈利波特】三部曲之Fawkes

前言 这次的靶机与前面不同&#xff0c;这里涉及到缓冲区溢出等 这个靶机也让我知道薄弱点了&#xff0c;缓冲溢出这方面之前接触少&#xff0c;所以刚拿到这个靶机打开后&#xff0c;人蒙了&#xff0c;在网上查阅好多资料&#xff0c;也只是浅学一下&#xff0c;这里主要也是…

mac下安装Ollama + Open WebUI + Llama3.1

本文介绍mac下安装Ollama Open WebUI Llama3.1 8b具体步骤。 目录 推荐配置Ollama Open WebUI Llama3.1简介安装Ollama安装Open WebUI 推荐配置 m1以上芯片&#xff0c;16g内存&#xff0c;20g以上硬盘空间 Ollama Open WebUI Llama3.1简介 Ollama: 下载&#xff0c;管理…

Android 图形系统之四:Choreographer

Choreographer 是 Android 系统中负责帧同步的核心组件&#xff0c;它协调输入事件、动画和绘制任务&#xff0c;以确保界面以固定频率&#xff08;通常是每 16ms&#xff0c;一帧&#xff09;流畅渲染。通过管理 VSYNC 信号和调度任务&#xff0c;Choreographer 是实现流畅 UI…

如何构建一个可扩展、全球可访问的 GenAI 架构?

你有没有尝试过使用人工智能生成图像&#xff1f; 如果你尝试过&#xff0c;你就会知道&#xff0c;一张好的图像的关键在于一个详细具体的提示。 我不擅长这种详细的视觉提示&#xff0c;所以我依赖大型语言模型来生成详细的提示&#xff0c;然后使用这些提示来生成出色的图像…

ceph手动部署

ceph手动部署 一、 节点规划 主机名IP地址角色ceph01.example.com172.18.0.10/24mon、mgr、osd、mds、rgwceph02.example.com172.18.0.20/24mon、mgr、osd、mds、rgwceph03.example.com172.18.0.30/24mon、mgr、osd、mds、rgw 操作系统版本&#xff1a; Rocky Linux release …

【iOS】多线程基础

【iOS】多线程基础 文章目录 【iOS】多线程基础前言进程与线程进程进程的状态进程的一个控制结构进程的上下文切换 线程为什么要用线程什么是线程线程和进程的关系线程的上下文切换 线程和进程的优缺点 小结 前言 笔者由于对于GCD不是很了解&#xff0c;导致了项目中网络请求哪…

ArraList和LinkedList区别

文章目录 一、结构不同二、访问速度三、插入和删除操作的不同1、决定效率有两个因素&#xff1a;数据量和位置。2、普遍说法是“LinkedList添加删除快”&#xff0c;这里是有前提条件的 四、内存占用情况五、使用场景六、总结 一、结构不同 LinkedList&#xff1a;它基于双向链…

芯片测试-RF中的S参数,return loss, VSWR,反射系数,插入损耗,隔离度等

RF中的S参数&#xff0c;return loss, VSWR&#xff0c;反射系数&#xff0c;插入损耗&#xff0c;隔离度 &#x1f4a2;S参数&#x1f4a2;&#x1f4a2;S11与return loss&#xff0c;VSWR&#xff0c;反射系数&#x1f4a2;&#x1f4a2;S21&#xff0c;插入损耗和增益&#…

arkTS:持久化储存UI状态的基本用法(PersistentStorage)

arkUI&#xff1a;持久化储存UI状态的基本用法&#xff08;PersistentStorage&#xff09; 1 主要内容说明2 例子2.1 持久化储存UI状态的基本用法&#xff08;PersistentStorage&#xff09;2.1.1 源码1的相关说明2.1.1.1 数据存储2.1.1.2 数据读取2.1.1.3 动态更新2.1.1.4 显示…