【数据结构与算法】排序算法总结:冒泡 / 快排 / 直接插入 / 希尔 / 简单选择 / 堆排序 / 归并排序
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()中将枢纽的选择方式改为三数取中,具体原理是:
- 找出中间元素
- 比较左、右端数据元素,保证左端较小(若左>右,就交换位置)
- 比较中、右端数据元素,保证中端较小(若中>右,就交换位置)
- 比较中、左端数据元素,保证中端较小(若中>左,就交换位置)
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 = 当前位置
- 比较 第 i 个记录 & 剩余的 (n-i)个记录
- 在(n - i +1)个记录中,选择最小的记录
- 将最小的记录 与 第 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官网下载直链:https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#db_ee 夸克网盘下载:https://pan.quark.cn/s/1460a663ee83 2、新建 Windows Server 2019 虚拟机 (超详细&a…...
数字孪生开发之 Three.js 插件资源库(2)
在当今数字化快速发展的时代,数字孪生技术正逐渐成为各个领域的关键技术之一。它通过创建物理实体的虚拟副本,实现对实体的实时监测、模拟和优化,为企业和组织带来了诸多好处,如提高生产效率、降低成本、改进产品质量等。然而&…...
小米C++ 面试题及参考答案下(120道面试题覆盖各种类型八股文)
指针和引用的区别?怎么实现的? 指针和引用有以下一些主要区别。 从概念上来说,指针是一个变量,它存储的是另一个变量的地址。可以通过指针来间接访问所指向的变量。例如,我们定义一个整型指针int *p;,它可以指向一个整型变量的内存地址。而引用是一个别名,它必须在定义的…...
OpenOCD之J-Link下载
NOTE:此篇文章由笔者的 VSCode编辑GCC for ARM交叉编译工具链Makefile构建OpenOCD调试(基于STM32的标准库)派生而来。 1.下载USB Dirver Tool.exe,选择J-Link dirver,替换成WinUSB驱动。(⭐USB Dirver Tool…...
华为云云连接+squid进行正向代理上网冲浪
1 概述 Squid是一个高性能的代理缓存服务器,主要用于缓冲Internet数据。它支持多种协议,包括FTP、gopher、HTTPS和HTTP。Squid通过一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求,这使得它在处理请求时具有较高的效率。…...
情绪识别项目
文章目录 1、mp4s文件转mp3文件2、Audition下载3、Audition安装4、Audition使用: 1、mp4s文件转mp3文件 在线转:Convert audio to MP3(https://audio.online-convert.com/convert-to-mp3) 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 的核心原理与应用!
爆款标题: 《从零样本到少样本学习:一文读懂 Zero-shot、One-shot 和 Few-shot 的核心原理与应用!》 正文: 在自然语言处理(NLP)领域,Zero-shot、One-shot 和 Few-shot 学习已经成为衡量大语言…...
【LC】3101. 交替子数组计数
题目描述: 给你一个二进制数组nums 。如果一个子数组中 不存在 两个 相邻 元素的值 相同 的情况,我们称这样的子数组为 交替子数组 。返回数组 nums 中交替子数组的数量。 示例 1: 输入: nums [0,1,1,1] 输出: 5 …...
如何构建SAAS项目
在后台使用JDBC方式动态创建用户输入的数据库信息(库名、地址、用户名、密码) 执行预先写好的sql文件(如mybatis的scriptRunner)执行建表语句及插入基础数据(管理员用户、普通用户)...
树莓派搭建NextCloud:给数据一个安全的家
前言 NAS有很多方案,常见的有 Nextcloud、Seafile、iStoreOS、Synology、ownCloud 和 OpenMediaVault ,以下是他们的特点: 1. Nextcloud 优势: 功能全面:支持文件同步、共享、在线文档编辑、视频会议、日历、联系人…...
深入解读 MongoDB 查询耗时:Execution 和 Fetching 阶段详解
在使用 MongoDB 时,查询性能的分析与优化是开发者关注的重点。MongoDB 的查询过程通常分为两个主要阶段:Execution(执行阶段)和Fetching(拉取阶段)。每个阶段的耗时代表不同的性能瓶颈,优化思路…...
frida_hook_dlopen(当年到lib目录下找发现一个so都没有,hook下dlopen)
Frida 脚本用于拦截 Android 应用程序中的 dlopen 和 android_dlopen_ext 函数。这两个函数用于动态加载共享库,脚本通过拦截这些函数的调用来记录加载的库的路径。 代码分析 var dlopen Module.findExportByName(null, "dlopen"); // 6.0 var android…...
Zero to JupyterHub with Kubernetes中篇 - Kubernetes 常规使用记录
前言:纯个人记录使用。 搭建 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去掉停用词(fit_wordsgenerate)的2种用法 通过词频来绘制词云图(jiebaWordCloud) Python教程95:去掉停用词词频统计jieba.tokenize示例用法 将进酒—李白process_t…...
Python 中如何处理异常?
在Python中,异常处理是一种重要的编程技术,它允许开发者优雅地处理程序运行过程中出现的错误或异常情况,而不是让程序直接崩溃。 通过异常处理,我们可以使程序更加健壮、用户友好。 异常处理的基本结构 Python中最基本的异常处…...
C++——多态(下)
目录 引言 多态 4.多态的原理 4.1 虚函数表指针 4.2 多态的原理 5.单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 5.2 多继承中的虚函数表 结束语 引言 接下来我们继续学习多态。 没有阅读多态(上)的可以点击下面的链接哦~ C——多态…...
qsort函数详解+代码展示
文章目录 概要系列文章目录前言(1) 定义(2) 使用(举例子 上代码)1、定义数组:2、定义比较函数:3、调用 qsort:4、输出结果: (3) 注意事项 小结 概要 本篇博客将详细地介绍qsort排序函数,&#x…...
leetcode hot100【LeetCode 136. 只出现一次的数字】java实现
LeetCode 136. 只出现一次的数字 题目描述 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。 …...
(免费送源码)计算机毕业设计原创定制:Java+ssm+JSP+Ajax SSM棕榈校园论坛的开发
摘要 随着计算机科学技术的高速发展,计算机成了人们日常生活的必需品,从而也带动了一系列与此相关产业,是人们的生活发生了翻天覆地的变化,而网络化的出现也在改变着人们传统的生活方式,包括工作,学习,社交…...
对抗攻击算法:FGSM和PGD
FGSM 传送门 FGSM 利用了梯度上升的思想,通过损失函数相对于输入图像的梯度来找到 最容易 迷惑网络的方向,并沿着这个方向对图像进行微小的扰动。 FGSM 的基本想法是,沿着这个梯度的符号方向对图像进行微调,以最大化损失函数。具…...
【八股文】小米
文章目录 一、vector 和 list 的区别?二、include 双引号和尖括号的区别?三、set 的底层数据结构?四、set 和 multiset 的区别?五、map 和 unordered_map 的区别?六、虚函数和纯虚函数的区别?七、extern C …...
xtu oj 众数
样例输入# 3 1 0 1 2 1 1 2 3 1 1 2 2样例输出# 1 2 3 解题思路:与数组大小有关,先排序 举个例子思考一下 n4 k2 数组为1 2 3 4 如果我们想让众数那个位的值为3(即max3),3出现的次数为3,即众数为3,需要修改多少次…...
ENVI计算ROI分离度为灰色compute roi separability
我们在使用ENVI做影像分类的时候,需要采集样本兴趣区(ROI),在采集完兴趣区需要计算样本ROI的分离度。 但是有时会发下你 计算ROI分离度的选项为灰色状态不能计算。 如果不是以下问题: “一个是必须首先选择或创建至少…...
Adaboost集成学习 | Python实现基于NuSVR-Adaboost多输入单输出回归预测
目录 效果一览基本介绍程序设计参考资料效果一览 基本介绍 基于NuSVR-Adaboost多输入单输出回归预测python代码 NuSVR是一种支持向量回归(SVR)算法的变体,用于解决回归问题。SVR是一种监督学习方法,它用于预测连续目标变量,而不是分类标签。NuSVR在SVR的基础上引入了一个…...
Python学习第十三天--面向对象,类和对象
一、面向过程和面向对象区别 面向过程:需要实现一个功能时,着重的是开发的步骤和过程,每个步都需要自己亲力亲为,需要编写代码(自己来做) 面向对象:需要实现一个功能时,不注重的是…...
AI运用落地思考:如何用AI进行系统运维?
1. 故障预测与预防 数据收集与分析:通过收集系统的各种运行数据,如服务器性能指标(CPU使用率、内存占用、磁盘I/O等)、网络流量数据、应用程序日志等。利用AI算法对这些海量数据进行分析,挖掘数据中的模式和相关性。例…...
springboot学习-分页/排序/多表查询的例子
最近喜欢上了springboot,真是个好的脚手架。今天继续学习分页/排序/多表查询等复杂功能。按步骤记录如下. 按步骤做的发现不可用,最终还是用的jdbctemplate解决。这也是一次经验。总计在最后。 1.maven依赖 首先从https://start.spring.io/ 选择需要的…...
windows 应用 UI 自动化实战
UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环,网络上也有很多 UI 自动化相关的知识或资料,具体到 windows 端的 UI 自动化,我们需要从以下几个方面考虑: 开发语言 毋庸置疑,在 UI 自动化测试领域&am…...
如何建设论坛网站/seo快速排名外包
描述 此方法返回位于字符串的指定索引处的字符。该字符串的索引从零开始。 语法 此方法定义的语法如下: public char charAt(int index) 参数 这里是参数的细节: index -- 返回字符的索引。 返回值 该方法的返回指定索引处char值。 例子: 1 public class Test { 2 …...
终身免费网站建设/网络营销的营销理念
切片 概述 切片是程序员对数组对象的抽象,在Go里面,数组长度是不可变的,这样会造成我们使用集合的时候比较笨重,只有在固定的场所才可以使用。 Go提供了一种较为灵活的数组,我们可以理解为动态数组,他对比…...
合肥市住房和城乡建设局网站/百度推广竞价是什么意思
在MSDN中,.net的数据库连接字符串都有详细的说明,具体的每一项代表的意义可以参看MSDN.ADO.net 中数据库连接方式(微软提供) 微软提供了以下四种数据库连接方式:System.Data.OleDb.OleDbConnectionSystem.Data.SqlClient.SqlConnectionSystem…...
网站图片的作用/如何优化网站快速排名
我们知道python下的多进程做异步还是可以的,但是做并发利用多核处理器是行不通的,而且速度还会更慢。那么我们来试试多进程的效果吧。简单看下多进程的几种实现方法。 1. 普通进程启动与测试 #!/usr/bin/env python #################################…...
网站推广预算/网址最全的浏览器
ThinkPHP3.2判断是否为手机端访问并跳转到另一个模块的方法 目录结构 公共模块Common,Home模块,Mobile模块配置Application/Common/Conf/config.php文件 MODULE_ALLOW_LIST > Home,Mobile接下来配置Application/Common/Common/function.php文件 添加…...
淘宝网站优化实例/什么是电商
先上软件效果图 代码如下1.根据Url地址得到网页的html源码 1 public static string GetWebContent(string Url)2 {3 string strResult "";4 try5 {6 HttpWebRequest request (HttpWebRequest)WebReq…...