当前位置: 首页 > news >正文

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

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个值 = 初始值 = 序列长度 / 2for (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-1heapAdjust(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 性能分析

在这里插入图片描述

相关文章:

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

1 排序 1.1 冒泡 内排序的交换排序类别 1.1.1 普通实现 public class BubbleSort {/*** 基本的 冒泡排序*/public static void bubbleSort(int[] srcArray) {int i,j; // 用于存放数组下标int temp 0; // 用于交换数值时临时存放值for(i0;i<srcArray.length-1;i){// j …...

Windows Serv 2019 虚拟机 安装Oracle19c,图文详情(超详细)

1、下载安装文件 Oracle官网下载直链&#xff1a;https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#db_ee 夸克网盘下载&#xff1a;https://pan.quark.cn/s/1460a663ee83 2、新建 Windows Server 2019 虚拟机 &#xff08;超详细&a…...

数字孪生开发之 Three.js 插件资源库(2)

在当今数字化快速发展的时代&#xff0c;数字孪生技术正逐渐成为各个领域的关键技术之一。它通过创建物理实体的虚拟副本&#xff0c;实现对实体的实时监测、模拟和优化&#xff0c;为企业和组织带来了诸多好处&#xff0c;如提高生产效率、降低成本、改进产品质量等。然而&…...

小米C++ 面试题及参考答案下(120道面试题覆盖各种类型八股文)

指针和引用的区别?怎么实现的? 指针和引用有以下一些主要区别。 从概念上来说,指针是一个变量,它存储的是另一个变量的地址。可以通过指针来间接访问所指向的变量。例如,我们定义一个整型指针int *p;,它可以指向一个整型变量的内存地址。而引用是一个别名,它必须在定义的…...

OpenOCD之J-Link下载

NOTE&#xff1a;此篇文章由笔者的 VSCode编辑GCC for ARM交叉编译工具链Makefile构建OpenOCD调试&#xff08;基于STM32的标准库&#xff09;派生而来。 1.下载USB Dirver Tool.exe&#xff0c;选择J-Link dirver&#xff0c;替换成WinUSB驱动。&#xff08;⭐USB Dirver Tool…...

华为云云连接+squid进行正向代理上网冲浪

1 概述 ‌Squid‌是一个高性能的代理缓存服务器&#xff0c;主要用于缓冲Internet数据。它支持多种协议&#xff0c;包括FTP、gopher、HTTPS和HTTP。Squid通过一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求&#xff0c;这使得它在处理请求时具有较高的效率‌。…...

情绪识别项目

文章目录 1、mp4s文件转mp3文件2、Audition下载3、Audition安装4、Audition使用&#xff1a; 1、mp4s文件转mp3文件 在线转&#xff1a;Convert audio to MP3&#xff08;https://audio.online-convert.com/convert-to-mp3&#xff09; 2、Audition下载 Audition CC2019/64位…...

【RISC-V CPU debug 专栏 2.2 -- Hart DM States】

文章目录 Hart DM StatesHart 的 DM 状态1. 不存在(Non-existent)2. 不可用(Unavailable)3. 运行(Running)4. 暂停(Halted)状态转换与复位行为状态指示信号Hart DM States 在 RISC-V 调试架构中,每个可以被选择的硬件线程(hart)处于以下四种调试模块(DM)状态之一…...

从零样本到少样本学习:一文读懂 Zero-shot、One-shot 和 Few-shot 的核心原理与应用!

爆款标题&#xff1a; 《从零样本到少样本学习&#xff1a;一文读懂 Zero-shot、One-shot 和 Few-shot 的核心原理与应用&#xff01;》 正文&#xff1a; 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Zero-shot、One-shot 和 Few-shot 学习已经成为衡量大语言…...

【LC】3101. 交替子数组计数

题目描述&#xff1a; 给你一个二进制数组nums 。如果一个子数组中 不存在 两个 相邻 元素的值 相同 的情况&#xff0c;我们称这样的子数组为 交替子数组 。返回数组 nums 中交替子数组的数量。 示例 1&#xff1a; 输入&#xff1a; nums [0,1,1,1] 输出&#xff1a; 5 …...

如何构建SAAS项目

在后台使用JDBC方式动态创建用户输入的数据库信息&#xff08;库名、地址、用户名、密码&#xff09; 执行预先写好的sql文件&#xff08;如mybatis的scriptRunner)执行建表语句及插入基础数据&#xff08;管理员用户、普通用户&#xff09;...

树莓派搭建NextCloud:给数据一个安全的家

前言 NAS有很多方案&#xff0c;常见的有 Nextcloud、Seafile、iStoreOS、Synology、ownCloud 和 OpenMediaVault &#xff0c;以下是他们的特点&#xff1a; 1. Nextcloud 优势&#xff1a; 功能全面&#xff1a;支持文件同步、共享、在线文档编辑、视频会议、日历、联系人…...

深入解读 MongoDB 查询耗时:Execution 和 Fetching 阶段详解

在使用 MongoDB 时&#xff0c;查询性能的分析与优化是开发者关注的重点。MongoDB 的查询过程通常分为两个主要阶段&#xff1a;Execution&#xff08;执行阶段&#xff09;和Fetching&#xff08;拉取阶段&#xff09;。每个阶段的耗时代表不同的性能瓶颈&#xff0c;优化思路…...

frida_hook_dlopen(当年到lib目录下找发现一个so都没有,hook下dlopen)

Frida 脚本用于拦截 Android 应用程序中的 dlopen 和 android_dlopen_ext 函数。这两个函数用于动态加载共享库&#xff0c;脚本通过拦截这些函数的调用来记录加载的库的路径。 代码分析 var dlopen Module.findExportByName(null, "dlopen"); // 6.0 var android…...

Zero to JupyterHub with Kubernetes中篇 - Kubernetes 常规使用记录

前言&#xff1a;纯个人记录使用。 搭建 Zero to JupyterHub with Kubernetes 上篇 - Kubernetes 离线二进制部署。搭建 Zero to JupyterHub with Kubernetes 中篇 - Kubernetes 常规使用记录。搭建 Zero to JupyterHub with Kubernetes 下篇 - Jupyterhub on k8s。 参考&…...

WordCloud去掉停用词(fit_words+generate)的2种用法

-------------词云图集合------------- WordCloud去掉停用词&#xff08;fit_wordsgenerate&#xff09;的2种用法 通过词频来绘制词云图&#xff08;jiebaWordCloud&#xff09; Python教程95&#xff1a;去掉停用词词频统计jieba.tokenize示例用法 将进酒—李白process_t…...

Python 中如何处理异常?

在Python中&#xff0c;异常处理是一种重要的编程技术&#xff0c;它允许开发者优雅地处理程序运行过程中出现的错误或异常情况&#xff0c;而不是让程序直接崩溃。 通过异常处理&#xff0c;我们可以使程序更加健壮、用户友好。 异常处理的基本结构 Python中最基本的异常处…...

C++——多态(下)

目录 引言 多态 4.多态的原理 4.1 虚函数表指针 4.2 多态的原理 5.单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 5.2 多继承中的虚函数表 结束语 引言 接下来我们继续学习多态。 没有阅读多态&#xff08;上&#xff09;的可以点击下面的链接哦~ C——多态…...

qsort函数详解+代码展示

文章目录 概要系列文章目录前言(1) 定义(2) 使用&#xff08;举例子 上代码&#xff09;1、定义数组&#xff1a;2、定义比较函数&#xff1a;3、调用 qsort&#xff1a;4、输出结果&#xff1a; (3) 注意事项 小结 概要 本篇博客将详细地介绍qsort排序函数&#xff0c;&#x…...

leetcode hot100【LeetCode 136. 只出现一次的数字】java实现

LeetCode 136. 只出现一次的数字 题目描述 给定一个非空整数数组&#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 …...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...