【排序算法】之快速排序

一、算法介绍

快速排序(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://xiahunao.cn/news/3018683.html

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

相关文章

未来娱乐新地标?气膜球幕影院的多维体验—轻空间

在中国&#xff0c;一座独特的娱乐场所正在崭露头角&#xff1a;气膜球幕影院。这个融合了气膜建筑与激光投影技术的创新场所&#xff0c;不仅令人惊叹&#xff0c;更带来了前所未有的科幻娱乐体验。让我们一起探索这个未来的娱乐空间&#xff0c;感受其中的多维魅力。 现场演出…

java语言数据结构(单链表)

前言 不得承认java应用的广泛&#xff0c;所以毅然决定java版本的数据结构和算法专题还是要坚决更新。每日更新2题&#xff0c;希望学习的小伙伴可以关注一波跟上&#xff0c;评论区欢迎讨论交流。 实现原理 节点&#xff08;Node&#xff09;&#xff1a;链表的基本构建单元…

微信小程序(Taro)获取经纬度并转化为具体城市

1、获取经纬度 申请权限&#xff0c;想要使用微信小程序获取经纬度的方法是要申请该方面的权限。 获取经纬度的方法有很多选择其中一个使用就好。 我使用的是Taro.getFuzzyLocation(&#xff09; 在app.config.js中需要添加设置 requiredPrivateInfos: ["getFuzzyLocat…

Raft共识算法图二解释

下面是有关Raft协议中不同术语和概念的翻译及解释&#xff1a; 术语和概念&#xff1a; 任期号&#xff08;term number&#xff09;&#xff1a;用来区分不同的leader。前一个日志槽位的信息&#xff08;prelogIndex&#xff09;&#xff1a;这是前一个日志条目的索引&#…

集成逻辑分析器( ILA)IP核用法详解

集成逻辑分析器&#xff08;Integrated Logic Analyzer, ILA&#xff09;IP核是一个可定制的逻辑分析器&#xff0c;用于监测设计的内部信号。ILA核心包含了现代逻辑分析器的许多高级特性&#xff0c;比如布尔触发方程&#xff08;boolean trigger equations&#xff09;和边沿…

H5视频付费点播打赏影视系统程序全开源运营版

这是一款视频打赏源码&#xff0c;勿做非法用途&#xff0c;由用户亲测功能完善&#xff0c;源码仅用于学习使用&#xff0c;分享链接是用户云盘&#xff0c;具有时效性&#xff0c;感兴趣的可以去学习。 thinkphp开发&#xff0c;前后端分离设计&#xff0c;支持游客登陆、VIP…

了解集合与数据结构(java)

什么是数据结构? 数据结构就是 数据结构, 功能就是描述和组织数据 比如我有10万个QQ号, 我来组织, 有很多种组织方法, 比如链表, 树, 堆, 栈等等. 假如QQ号要查找数据, 有种数据结构查找数据速度很快, 我们就用它 加入QQ号要进行删除数据, 有种数据结构删除速度很快, 我们…

Python中设计注册登录代码

import hashlib import json import os import sys # user interface 用户是界面 UI """ 用户登录系统 1.注册 2.登陆 0.退出 """ # 读取users.bin def load(path): return json.load(open(path, "rt")) # 保存user.bin def save(dic…

Covalent引入五个新网络运营商,提升去中心化特性和数据安全性

为了进一步扩大运营商基础以并践行去中心化网络基础设施的宗旨&#xff0c;Covalent Network&#xff08;CQT&#xff09;在网络中引入了五个新的区块样本生产者&#xff08;BSPs&#xff09;角色。该举措不仅重申了 Covalent Network&#xff08;CQT&#xff09;对社区驱动协议…

如何保护数据安全?迅软DSE加密系统给信息撑把保护伞!

信息安全当然需要保护&#xff0c;不然企业的信息可以发给任何人&#xff0c;普通信息还好&#xff0c;如果是重要机密呢&#xff0c;企业重要信息被发出去后可能会造成一些麻烦&#xff0c;所以可以使用加密系统&#xff0c;对数据进行安全保护&#xff0c;防止泄密问题&#…

pandas 预处理

文章目录 第1关&#xff1a;数据读取与合并第2关&#xff1a;数据清洗第3关&#xff1a;数据转换 第1关&#xff1a;数据读取与合并 任务描述 本关任务&#xff1a;加载 csv 数据集&#xff0c;实现 DataFrame 合并。 知识讲解 Pandas 模块导入 import pandas as pd 读取 cs…

如何在没有备份的情况下恢复 Mac 上丢失的数据

如果您因意外删除、错误格式化硬盘或文件损坏而丢失了重要的、感伤的文件、照片或音乐&#xff0c;那么这可能会令人非常痛苦。幸运的是&#xff0c;您有几个选择。 您的 Mac 位于数字宇宙的中心。您可能会在上面留下照片和视频形式的记忆&#xff0c;以及来自您不再见面的朋友…

[嵌入式系统-69]:RT-Thread-组件:网络组件“组”,RT-Thread系统通向外部网络世界的入口

目录 RT-Thread 提供的网络世界入口 - 网络组件 1. 总概 2. AT 3. Lwip&#xff1a; 轻量级IP协议栈 4. W5500 5. Netdev 6. RT-Thread SAL&#xff08;Socket Abstraction Layer&#xff09;套接字和BSD套接字区别 RT-Thread SAL 套接字接口示例 BSD 套接字接口示例 …

css: hover 划过显示/隐藏 div 样式

1. 图例: 划过用display: block;和 display: none; 显示div和隐藏div div: <div class="sectorBox"> <div v-for="(item, index) in sectorList" :key="index" class="sill"> <div class="si…

大数据Scala教程从入门到精通第五篇:Scala环境搭建

一&#xff1a;安装步骤 1&#xff1a;scala安装 1&#xff1a;首先确保 JDK1.8 安装成功: 2&#xff1a;下载对应的 Scala 安装文件 scala-2.12.11.zip 3&#xff1a;解压 scala-2.12.11.zip 4&#xff1a;配置 Scala 的环境变量 在Windows上安装Scala_windows安装scala…

通过红黑树封装 map 和 set 容器(1):红黑树的迭代器

一、红黑树的迭代器 红黑树的遍历默认为中序遍历 —— key 从小到大&#xff0c;因此 begin() 应该获取到红黑树的最左节点 —— 最小&#xff0c;end() 获取到红黑树最右节点的下一个位置&#xff0c; operator() 也应保证红黑树的遍历为中序的状态。 首先对红黑树节点进行改造…

FMEA助力智能电网升级:构建安全、高效、可靠的电力网络

随着科技的不断进步&#xff0c;智能电网已成为现代电力行业的重要发展方向。而在这个过程中&#xff0c;FMEA&#xff08;失效模式和影响分析&#xff09;作为一种重要的质量管理工具&#xff0c;正日益发挥着其在智能电网建设中的赋能作用。本文将从FMEA的基本概念出发&#…

Springboot 集成 Consul 实现服务注册中心-05

因为后续很多模块都要用到注册中心&#xff0c;所以此处先实现此模块。 Consul简介 Consul是一个开源的服务发现和配置管理工具&#xff0c;具有跨平台、运行高效等特点。它由HashiCorp公司开发&#xff0c;并使用Go语言编写。Consul主要用于实现分布式系统中的服务发现、健康…

python中一些莫名其妙的异常

目录 一、字符串中空格\xa0二、文件写入为空问题三、Counter对NAN空值的统计问题 一、字符串中空格\xa0 对于文本中的一些空格&#xff0c;原始状态时显示为普通“空格”&#xff08;其实是latin1编码字符&#xff09;&#xff0c;但是经过split()操作后&#xff0c;这些latin…

MOSFET场效应管栅极驱动电流的计算

MOSFET驱动 MOSFET场效应管是电压驱动器件&#xff0c;输入有电容&#xff0c;因此为可靠驱动MOSFET&#xff0c;栅极需要施加较大的驱动电流。 功率MOSFET开关模型 该模型显示了影响开关性能的最重要的寄生器件。 图1 MOSFET开通过程 MOSFET场效应管的开通动作可分为如下…