【排序算法】之快速排序

一、算法介绍

快速排序(Quick sort)是由C.A.R.Hoare提出来的。快速排序法又叫分割交换排序法,是目前公认的最佳排序法,也是使用“分而治之”的方式,会先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分为两部分。其中小于中间值的数据在左边,而大于中间值的数据在右边,再以同样的方式分别处理左、右两边的数据,直到排序完为止。下面是快速排序的一些基本原理和步骤:

1. 选择基准(Pivot Selection):

  • 从待排序的数组中选择一个元素作为基准值(pivot)。
  • 常见的选择方式有:选择第一个元素、最后一个元素或随机选择一个元素。

2. 分区(Partitioning):

  • 将数组中的其他元素与基准值进行比较,将小于基准值的元素移动到基准值的左边,大于基准值的元素移动到基准值的右边。
  • 这一步完成后,基准值被放在了最终排序后的正确位置上,数组被分为两部分,左边的元素都小于基准,右边的元素都大于基准。

3. 递归排序(Recursion):

  • 对基准值左边的子数组和右边的子数组分别重复上述步骤,即选择新的基准并进行分区操作。
  • 这是一个递归过程,直到子数组只有一个或零个元素,排序结束。

快速排序的平均时间复杂度是O( n log ⁡ n n\log n nlogn),在最坏的情况下(例如输入数组已经排序或几乎排序),时间复杂度会退化到O( n 2 n^{2} n2)。为了避免这种情况,通常会采用随机化选择基准的方法来提高性能的稳定性

二、使用迭代的方式实现快速排序

以下是java代码示例:

package com.datastructures;import java.util.*;/*** 迭代方式实现快速排序算法* @author hulei*/
public class QuickSortIterative {//随机枢轴索引生成器private static final Random RANDOM = new Random();/*** 使用快速排序算法对给定的数组进行排序。** @param array 待排序的数组,数组元素必须实现Comparable接口。* @param <E> 数组元素的类型,该类型需扩展自Comparable,以支持元素之间的比较。*/public static <E extends Comparable<? super E>> void quickSortIterative(E[] array) {// 如果数组为空或长度小于等于1,则无需排序,直接返回if (array == null || array.length <= 1) {return;}// 使用栈来实现分治法中的递归调用(迭代方式)Stack<Integer> stack = new Stack<>();// 初始化栈,分别入栈数组的起始和结束索引stack.push(0);stack.push(array.length - 1);int cycle = 0;// 当栈不为空时,持续进行排序while (!stack.isEmpty()) {int high = stack.pop(); // 取出栈顶,为当前区间上界int low = stack.pop();  // 再次取出栈顶,为当前区间下界cycle++;System.out.println("第"+cycle+"轮循环交换开始");// 对当前区间进行划分,返回枢轴元素的最终位置int pivotIndex = partition(array, low, high);System.out.println("第"+cycle+"轮循环交换结束,枢轴已放置到正确位置上");System.out.println("第一轮交换后的结果:"+Arrays.toString(array)+",枢轴元素为:"+array[pivotIndex]);System.out.println("=========================================================================");// 如果枢轴元素左边还有未排序的元素,则将其入栈//pivotIndex - 1 > low说明当前分区左边起始索引到枢轴位置索引之间还有至少一个元素,根据当前排序结果只能保证枢轴左边所有的元素都是小于枢轴元素,// 但是不能保证左边起始索引到枢轴位置索引之间的元素都大于起始索引位置元素,或者内部已经排序过,所以需要再次进行排序if (pivotIndex - 1 > low) {stack.push(low);stack.push(pivotIndex - 1);}// 如果枢轴元素右边还有未排序的元素,则将其入栈//pivotIndex + 1 < high说明当前分区右边起始索引到枢轴位置索引之间还有至少一个元素,根据当前排序结果只能保证枢轴右边所有的元素都是大于枢轴元素,// 但是不能保证右边枢轴位置索引到右边结束位置索引之间的元素都小于结束索引位置元素,或者内部已经排序过,所以需要再次进行排序if (pivotIndex + 1 < high) {stack.push(pivotIndex + 1);stack.push(high);}//综上分区结束时,枢轴左右两边都各只能有且最多一个元素,所以不需要再次进行排序}}/*** 对给定区间进行划分,返回枢轴元素的最终位置。** @param array 待划分的数组* @param low 划分的起始索引* @param high 划分的结束索引* @return 枢轴元素的最终位置*/private static <E extends Comparable<? super E>> int partition(E[] array, int low, int high) {// 随机选择一个元素作为枢轴(枢轴的索引范围为[low, high])//这个表达式 RANDOM.nextInt((right - left) + 1) + left//RANDOM.nextInt((right - left) + 1)方法生成的是 [0, (right - left+1))范围内的非负随机整数即为,包含左端点0,不包含右端点right - left+1//实际上,这个表达式生成的随机整数的范围是 [0, right - left],包括left和right两个端点。//加上 left 后,范围变成了 [left, right],确保了包含两个端点。int pivotIndex = RANDOM.nextInt((high - low) + 1) + low;//取基准值E pivot = array[pivotIndex];System.out.println("随机枢轴元素pivot:"+pivot);// 将随机选中的枢轴元素与数组末尾元素交换,便于后续处理swap(array, pivotIndex, high);System.out.println("把随机枢轴元素放到数组末尾后的结果:"+Arrays.toString(array));System.out.println();int i = low - 1;for (int j = low; j < high; j++) {System.out.println("交换前数组:"+Arrays.toString(array));System.out.print("i指针索引初始值为:"+i+"----");if (array[j].compareTo(pivot) <= 0) {i++;System.out.println("j指针索引当前值为:"+j+" 对应元素为:"+array[j]+" 小于枢轴元素值:"+pivot+" i指针向右边移动一位变为:"+i);System.out.println("array[i]="+"array["+i+"]="+array[i]+",array[j]=array["+j+"]="+array[j]+",交换array[i]和array[j]元素位置");swap(array, i, j);System.out.println("array[i]="+"array["+i+"]="+array[i]+",array[j]=array["+j+"]="+array[j]+",交换后数组:"+Arrays.toString(array));}else{System.out.println("j指针索引初始值为:"+j+" 指针对应元素为:"+array[j]+" 大于枢轴元素:"+pivot);System.out.println("数组元素不交换:"+Arrays.toString(array));}System.out.println();}swap(array, i + 1, high); // 将枢轴元素放回正确的位置return i + 1;}/*** 交换数组中两个元素的位置。* @param array 要进行交换的数组。* @param index1 要交换的第一个元素的索引。* @param index2 要交换的第二个元素的索引。* @param <E> 数组元素的类型。*/private static <E> void swap(E[] array, int index1, int index2) {// 临时变量用于存储第一个元素,以便后续交换E temp = array[index1];array[index1] = array[index2]; // 将第二个元素的值赋给第一个元素array[index2] = temp; // 将之前存储的第一个元素的值赋给第二个元素}public static void main(String[] args) {Integer[] arr = new Integer[]{20, 12, 27, 15, 18, 21, 34, 28, 23, 41, 39, 14, 6, 17};System.out.println("原始数组:");System.out.println(Arrays.toString(arr));System.out.println();quickSortIterative(arr);System.out.println("快速排序后数组:");System.out.println(Arrays.toString(arr));}
}

笔者认为快速排序不是那么容易理解,所以在代码中加入了很多打印信息,以此来更加直观明了的展示快速排序的交换过程,因为打印的排序过程信息比较长,这里只截取开始部分的截图,建议自行把代码复制到IDE中运行下,查看控制台信息,加深理解。注释写的已经很清楚了,看不懂的话需要细细揣摩,或者自行把待排序的数组调整的简单点运行观察。
如第一轮是整个数组选取基准值pivot后排序。排序结束后所有小于pivot的值放在它的左边,大于pivot的值放在它的右边
第二轮和第三轮分别是对第一轮分区后的左分区和右分区,分别选取各自分区的基准值pivot后再排序,大于pivot的放右边,小于pivot的放左边



以此类推,持续进行分区排序**,每次分区排序处理后,判断枢轴元素分别到左起始索引和右结束索引之间是否还有元素**,如果有,则需要把当前分区的左起始索引到枢轴索引前一个位置的索引当作新的左分区入栈处理,枢轴索引后一个索引位置到当前分区的结束索引范围当作新的右分区入栈处理。

在这里插入图片描述这里使用了Stack,这是JDK提供的实现了栈的数据结构特点的一个官方实现类,使用Stack主要有下面几个原因:1. 避免递归带来的栈溢出风险: 快速排序的经典实现通常采用递归方法,递归在处理大规模数据时可能会导致调用栈过深,引发StackOverflowError。通过使用Stack作为迭代结构,可以将递归转换为循环,从而有效避免了栈溢出的问题。

2. 控制排序过程的迭代逻辑:
Stack用于存储待排序区间的边界索引(即low和high)。每次循环从栈顶弹出两个元素作为当前区间的上下界,对这个区间进行排序。如果排序后发现枢轴元素的左侧或右侧还有未排序的子区间,则将这些子区间的边界索引重新压入栈中,等待后续循环处理。这样,直到栈为空,所有子区间都被排序,整个数组也就完成了排序。

3. 提高空间效率
相比于递归调用时系统自动管理的调用栈,手动管理的Stack可以在一定程度上减少内存使用。尽管这种差异在小规模数据上可能不明显,但在处理大量数据时,自定义栈可以更精细地控制所需的空间。

4. 增强代码可读性和灵活性
通过显式地使用Stack来模拟递归逻辑,代码的意图更加清晰,便于理解和维护。同时,这也为后续可能的优化提供了便利,比如可以通过调整压栈顺序来改变排序策略,或者在栈操作中加入额外的逻辑来适应特定需求。

三、使用递归的方式实现快速排序

递归方式的java代码如下:

package com.datastructures;import java.util.Arrays;
import java.util.Random;/*** 递归方式实现快速排序算法* @author hulei* @date 2024/5/6 15:19*/public class QuickSortRecursive {//随机枢轴索引生成器private static final Random RANDOM = new Random();private static int cycle = 0;public static void main(String[] args) {Integer[] arr = new Integer[]{20, 12, 27, 15, 18, 29, 11, 21, 34, 28, 23, 41, 39, 14, 6, 17};System.out.println("原始数组:");System.out.println(Arrays.toString(arr));System.out.println();quickSortWithRecursive(arr, 0, arr.length - 1);System.out.println("快速排序后数组:");System.out.println(Arrays.toString(arr));}/*** 递归方式快速排序** @param array 待排序数组* @param left  排序起始索引* @param right 排序结束索引*/private static <E extends Comparable<? super E>> void quickSortWithRecursive(E[] array, int left, int right) {if (left >= right) {return;}cycle++;System.out.println("第" + cycle + "轮循环交换开始");// 对当前区间进行划分,返回枢轴元素的最终位置int pivotIndex = partition(array, left, right);System.out.println("第" + cycle + "轮循环交换结束,枢轴已放置到正确位置上");System.out.println("第一轮交换后的结果:" + Arrays.toString(array) + ",枢轴元素为:" + array[pivotIndex]);// 如果枢轴元素左边还有未排序的元素,则继续递归排序//pivotIndex - 1 > left说明当前分区左边起始索引到枢轴位置索引之间还有至少一个元素,根据当前排序结果只能保证枢轴左边所有的元素都是小于枢轴元素,// 但是不能保证左边起始索引到枢轴位置索引之间的元素都大于起始索引位置元素,或者内部已经排序过,所以需要再次进行排序if (pivotIndex - 1 > left) {quickSortWithRecursive(array, left, pivotIndex - 1);}// 如果枢轴元素右边还有未排序的元素,则继续递归排序//pivotIndex + 1 < high说明当前分区右边起始索引到枢轴位置索引之间还有至少一个元素,根据当前排序结果只能保证枢轴右边所有的元素都是大于枢轴元素,// 但是不能保证右边枢轴位置索引到右边结束位置索引之间的元素都小于结束索引位置元素,或者内部已经排序过,所以需要再次进行排序if (pivotIndex + 1 < right) {quickSortWithRecursive(array, pivotIndex + 1, right);}}/*** 划分函数** @param array 待排序数组* @param left  排序起始索引* @param right 排序结束索引* @return 返回枢轴元素的最终位置*/private static <E extends Comparable<? super E>> int partition(E[] array, int left, int right) {// 随机选择一个元素作为枢轴(枢轴的索引范围为[low, high])//这个表达式 RANDOM.nextInt((right - left) + 1) + left//RANDOM.nextInt((right - left) + 1)方法生成的是 [0, (right - left+1))范围内的非负随机整数即为,包含左端点0,不包含右端点right - left+1//实际上,这个表达式生成的随机整数的范围是 [0, right - left],包括left和right两个端点。//加上 left 后,范围变成了 [left, right],确保了包含两个端点。int pivotIndex = RANDOM.nextInt((right - left) + 1) + left;//取基准值E pivot = array[pivotIndex];System.out.println("随机枢轴元素pivot:" + pivot);// 将随机选中的枢轴元素与数组末尾元素交换,便于后续处理swap(array, pivotIndex, right);System.out.println("把随机枢轴元素放到数组末尾后的结果:" + Arrays.toString(array));System.out.println();int i = left - 1;for (int j = left; j < right; j++) {System.out.println("交换前数组:" + Arrays.toString(array));System.out.print("i指针索引初始值为:" + i + "----");if (array[j].compareTo(pivot) <= 0) {i++;System.out.println("j指针索引当前值为:" + j + " 对应元素为:" + array[j] + " 小于枢轴元素值:" + pivot + " i指针向右边移动一位变为:" + i);System.out.println("array[i]=" + "array[" + i + "]=" + array[i] + ",array[j]=array[" + j + "]=" + array[j] + ",交换array[i]和array[j]元素位置");swap(array, i, j);System.out.println("array[i]=" + "array[" + i + "]=" + array[i] + ",array[j]=array[" + j + "]=" + array[j] + ",交换后数组:" + Arrays.toString(array));} else {System.out.println("j指针索引初始值为:" + j + " 指针对应元素为:" + array[j] + " 大于枢轴元素:" + pivot);System.out.println("数组元素不交换:" + Arrays.toString(array));}System.out.println();}swap(array, i + 1, right); // 将枢轴元素放回正确的位置return i + 1;}/*** 交换数组中两个元素的位置。** @param array  要进行交换的数组。* @param index1 要交换的第一个元素的索引。* @param index2 要交换的第二个元素的索引。* @param <E>    数组元素的类型。*/private static <E> void swap(E[] array, int index1, int index2) {// 临时变量用于存储第一个元素,以便后续交换E temp = array[index1];array[index1] = array[index2]; // 将第二个元素的值赋给第一个元素array[index2] = temp; // 将之前存储的第一个元素的值赋给第二个元素}}

在这里插入图片描述
代码逻辑和迭代方式差不多,这里不再解释

四、迭代和递归方式处理快速排序的选择比较

递归方式的逻辑和迭代方式逻辑基本差不多,二者的分区处理逻辑是一样的,即**partition()**函数逻辑一致。唯一的区别是分区后持续处理分区的排序方式不同,迭代器方式通过Stack临时存储分区信息,再使用while循环处理栈数据,而递归方式代码逻辑相对简单点,分区后判断是否需要处理分区排序,递归调用函数自身实现后续分区和子分区的排序处理。

迭代和递归两者在处理逻辑上基本一致,都是基于分治法的思想,但实现机制有所不同,具体区别如下:

递归方式

  • 实现原理:递归方式的快速排序直接体现了算法的定义。它通过选择一个“基准”元素,然后将数组分为两部分,一部分都比基准小,另一部分都比基准大,之后对这两部分分别递归地进行快速排序。
  • 代码特点:递归实现相对简洁,逻辑清晰,易于理解。它通过函数自我调用来处理数组的子区间。
  • 栈空间:每次函数调用都会在调用栈上分配空间,如果排序的数据量非常大,可能会导致栈溢出。
  • 性能考量:递归调用会增加额外的时间开销,包括函数调用的压栈和弹栈操作。此外,大量的递归调用可能导致较高的内存使用。

迭代方式

  • 实现原理:迭代方式通常需要借助栈(或队列)等数据结构来模拟递归过程中的函数调用栈。通过手动管理这个栈,控制排序区间,达到与递归相同的效果。
  • 代码特点:迭代实现相比递归可能稍微复杂一些,因为它需要显式地管理排序区间的开始和结束索引,以及用于迭代的栈。
  • 栈空间:迭代方法可以减少系统调用栈的使用,避免了深度递归可能导致的栈溢出问题,对于大规模数据排序更为安全。
  • 性能考量:迭代通常能减少函数调用的开销,提高运行效率,尤其是在没有尾递归优化的编程环境中。它对于内存的使用也更加高效,因为不需要为每次函数调用分配新的栈帧。

总结
选择迭代还是递归实现快速排序,取决于具体的应用场景和需求。递归实现更直观易懂,适合自我学习和小型数据集;而迭代实现则在处理大规模数据时更为稳健,能有效避免栈溢出的风险,并可能在性能上有一定优势。在实际应用中,我们需根据实际情况权衡选择,没有最好的只有最合适的。

五、时间复杂度计算

递归方式

分析

1. partition函数时间复杂度
partition函数遍历了从left到right的所有元素一次,执行了比较和可能的交换操作。因此,这部分的时间复杂度 是线性的,即O(right - left),也可以简化为O(n),其中n = right - left + 1是子数组的长度。

2. quickSortWithRecursive函数时间复杂度
快速排序的基本思想是分而治之。在每一轮递归中,算法首先调用partition函数将数组分为两部分,然后对这两部分分别递归地进行排序。
在最好的情况下(每次划分都很均匀),每次递归调用都将问题规模减半,因此递归树的深度为O( log ⁡ n \log n logn),每一层的总工作量是线性的(因为每一层都要遍历相应子数组的元素),所以总的时间复杂度是O( n log ⁡ n n\log n nlogn)。
在最坏的情况下(每次划分都非常不均匀,例如已经排序好的数组或完全逆序的数组),递归树退化为链状结构,每次只减少一个元素,导致递归深度达到n,此时的时间复杂度退化为O( n 2 n^{2} n2)。

综合分析

  • 平均时间复杂度:O( n log ⁡ n n\log n nlogn)。这是因为大多数情况下,快速排序能够得到较好的划分,使得递归树的深度接近log n。
  • 最好情况时间复杂度:O( n log ⁡ n n\log n nlogn),当每次划分都均匀时。
  • 最坏情况时间复杂度:O( n 2 n^{2} n2),当数组已经是有序或逆序时。

实际操作中的优化
为了提高实际应用中的性能,快速排序通常会采用一些策略来避免最坏情况的发生,比如笔者在递归的代码中就采用了随机选取枢轴的方法,这有助于平衡划分,使得算法在实际应用中更倾向于O( n log ⁡ n n\log n nlogn)的平均性能。

迭代方式

主要函数分析

1. quickSortIterative函数
这个函数使用了一个栈来模拟递归调用。对于长度为n的数组,每次对一个子数组进行划分,如果子数组长度为m,则需要进行一次划分操作,时间复杂度为O(m)。
分区操作(partition函数)之后,将小于枢轴的子数组和大于枢轴的子数组分别入栈,继续进行排序。在最坏的情况下,每次划分都只能减少一个元素,导致需要进行n-1次划分,所以时间复杂度为O(n)。
但是,由于每次划分后,我们总是对较小的子数组优先进行操作,因此在平均情况下,每次划分会将问题规模减半,递归树的深度为O(log n)。由于每次划分的时间复杂度是线性的,因此总的时间复杂度是O( n log ⁡ n n\log n nlogn)。

2. partition函数
partition函数的逻辑与之前的递归版本相同,它遍历了从low到high的所有元素,进行比较和交换操作,时间复杂度为O(high - low),在最坏情况下为O(n)。

总结

  • 最好情况时间复杂度:O( n log ⁡ n n\log n nlogn),当每次划分都均匀时。
  • 平均情况时间复杂度:O( n log ⁡ n n\log n nlogn),这是迭代快速排序的主要时间复杂度,因为它总是优先处理较小的子数组。
  • 最坏情况时间复杂度:O( n 2 n^{2} n2),当数组已经是有序或逆序时,每次划分只能减少一个元素。

注意,这里的时间复杂度分析忽略了常数因子和对数项的系数,因为大O表示法主要关注算法在输入规模增长时的主要趋势。

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

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

相关文章

Verilog中4位数值比较器电路

某4位数值比较器的功能表如下。 请用Verilog语言采用门级描述方式&#xff0c;实现此4位数值比较器 参考代码如下&#xff1a; &#xff08;CSDN代码块不支持Verilog&#xff0c;代码复制到notepad编辑器中&#xff0c;语言选择Verilog&#xff0c;看得更清楚&#xff09; t…

ESP8266固件烧写

概述 因为手上有块闲置的ESP8266开发板&#xff0c;想着拿来倒腾一下WIFI探针&#xff0c;倒腾了一阵测试成功&#xff0c;博文记录用以备忘 硬件 ESP8266 NodeMCU 环境 Windows 11 步骤 1.下载esp32_win32_msys2_environment_and_toolchain-20181001.zip 2.下载xtensa…

SEO之高级搜索指令(二)

初创企业需要建站的朋友看这篇文章&#xff0c;谢谢支持&#xff1a; 我给不会敲代码又想搭建网站的人建议 新手上云 &#xff08;接上一篇。。。。&#xff09; 5 、inanchor: inanchor:指令返回的结果是导入链接锚文字中包含搜索词的页面。百度不支持inanchor:。 比如在 Go…

fork,execve,_exit从第一个程序到所有程序

操作系统启动后到底做了什么 CPU Reset → Firmware → Loader → Kernel _start() → 第一个程序 /bin/init → 程序 (状态机) 执行 系统调用 操作系统会加载 “第一个程序” 寻找启动程序代码 if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_p…

学成在线 - 第3章任务补偿机制实现 + 分块文件清理

7.9 额外实现 7.9.1 任务补偿机制 问题&#xff1a;如果有线程抢占了某个视频的处理任务&#xff0c;如果线程处理过程中挂掉了&#xff0c;该视频的状态将会一直是处理中&#xff0c;其它线程将无法处理&#xff0c;这个问题需要用补偿机制。 单独启动一个任务找到待处理任…

Java+SpringBoot+JSP实现在线心理评测与咨询系统

前言介绍 随着互联网技术的高速发展&#xff0c;人们生活的各方面都受到互联网技术的影响。现在人们可以通过互联网技术就能实现不出家门就可以通过网络进行系统管理&#xff0c;交易等&#xff0c;而且过程简单、快捷。同样的&#xff0c;在人们的工作生活中&#xff0c;也就…

一体化设计的ATA(FXS网关)设计——电源插头、WiFi、双网口、S口、USB等接口集于一身

目录 集成电源插头集成WiFi集成USB两个网口FXS接口&#xff08;Phone&#xff09;集成创新 ATA&#xff08;FXS网关&#xff09;已经走过几十年的发展&#xff0c;很难有创新。 下面介绍的这款ATA&#xff08;FXS网关&#xff09;通过一体化设计的集成创新&#xff0c;成为一款…

大数据Scala教程从入门到精通第三篇:Scala和Java的关系

一&#xff1a;Scala和Java的关系 1&#xff1a;详解 一般来说&#xff0c;学 Scala的人&#xff0c;都会 Java&#xff0c;而 Scala 是基于 Java 的&#xff0c;因此我们需要将 Scala和 Java 以及 JVM 之间的关系搞清楚&#xff0c;否则学习 Scala 你会蒙圈 Scala可以使用SDK…

爬虫学习:XPath匹配网页数据

目录 一、安装XPath 二、XPath的基础语法 1.选取节点 三、使用XPath匹配数据 1.浏览器审查元素 2.具体实例 四、总结 一、安装XPath 控制台输入指令&#xff1a;pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言&#xff0c;可以使用它在HTM…

Pycharm导入自定义模块报红

文章目录 Pycharm导入自定义模块报红1.问题描述2.解决办法 Pycharm导入自定义模块报红 1.问题描述 Pycharm 导入自定义模块报红&#xff0c;出现红色下划线。 2.解决办法 打开【File】->【Setting】->【Build,Execution,Deployment】->【Console】->【Python Con…

五分钟解决Springboot整合Mybaties

SpringBoot整合Mybaties 创建maven工程整合mybaties逆向代码生成 创建maven工程 1.通过idea创建maven工程如下图 2.生成的工程如下 以上我们就完成了一个maven工程&#xff0c;接下来我们改造成springboot项目。 这里主要分为三步&#xff1a;添加依赖&#xff0c;增加配置&…

运行一个jar包

目录 传送门前言一、Window环境二、Linux环境1、第一步&#xff1a;环境配置好&#xff0c;安装好jdk2、第二步&#xff1a;打包jar包并上传到Linux服务器3、第三步&#xff1a;运行jar包 三、docker环境1、Linux下安装docker和docker compose2、Dockerfile方式一运行jar包2.1、…

牛客网刷题 | BC80 奇偶统计

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 任意输入一个正整数…

迅饶科技 X2Modbus 网关 AddUser 任意用户添加漏洞复现

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

Python运维-文本处理、系统和文件信息监控、外部命令

本节主要目录如下&#xff1a; 一、文本处理 1.1、Python编码解码 1.2、文件操作 1.3、读写配置文件 1.4、解析XML文件 二、系统信息监控 2.1、监控CPU信息 2.2、监控内存信息 2.3、监控磁盘信息 2.4、监控网络信息 2.5、获取进程信息 2.6、实例&#xff1a;常见的…

【知识点随笔分享 | 第十篇】快速介绍一致性Hash算法

前言&#xff1a; 在分布式系统中&#xff0c;数据的分布和负载均衡是至关重要的问题。一致性哈希算法是一种解决这些挑战的有效工具&#xff0c;它在分布式存储、负载均衡和缓存系统等领域得到了广泛应用。 随着互联网规模的不断扩大&#xff0c;传统的哈希算法在面对大规模…

cmake进阶:变量的作用域(目录作用域与全局作用域)

一. 简介 前面从函数作用域方面学习了变量的作用域&#xff0c;本文从目录作用域方面来学习变量的作用域。 二. cmake进阶&#xff1a;从目录作用域方面学习变量的作用域 1. 目录作用域 什么是目录作用域&#xff1f; 我把这个作用域叫做目录作用域。子目录会将父目录的所…

Web3 ETF软件系统的主要功能

下面是Web3 ETF系统软件的主要功能&#xff0c;这些功能共同构成了Web3 ETF系统软件的核心&#xff0c;使其能够有效地为投资者提供Web3技术相关的投资机会&#xff0c;同时确保合规性、安全性和透明度。北京木奇移动软件有限公司&#xff0c;专业的软件外包开发公司&#xff0…

【Git】Git学习-10-11:GitHub,SHH配置,克隆仓库

学习视频链接&#xff1a;【GeekHour】一小时Git教程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1HM411377j/?vd_source95dda35ac10d1ae6785cc7006f365780 创建仓库 配置SSH密钥可以更加安全&#xff0c;方便地推送、拉取代码 根目录下&#xff0c;进入.ssh文件&am…

【C语言】——联合体与枚举

【C语言】——联合体与枚举 一、联合体1.1、联合体类型的声明1.2、联合体的特点1.3、相同成员的结构体和联合体对比1.4、联合体的大小计算1.5、联合体的应用举例 二、枚举2.1、枚举类型的声明2.2、枚举类型的优点 一、联合体 1.1、联合体类型的声明 联合体也叫做共用体   与…