堆排序、快速排序和归并排序

堆排序、快速排序和归并排序是所有排序中最重要的三个排序,也是难度最大的三个排序;所以本文单独拿这三个排序来讲解

目录

一、堆排序 

1.建堆

2.堆排序

二、快速排序

1.思想解析

2.Hoare版找基准

3.挖坑法找基准

4.快速排序的优化

5.快速排序非递归

三、归并排序

1.归并思路

2.代码展示及步骤

3.代码分析

 4.归并排序非递归

 5.使用归并排序解决海量数据


一、堆排序 

所谓堆排序,就是在堆的基础上进行排序,那么如何建堆,就是堆排序的重点。

堆排序核心思想:

1.建堆:排升序建大根堆,排降序建小根堆

2.在建好堆后,每次堆顶元素和最后一个位置的元素交换

3.每交换一次,就进行向下调整操作,使其满足堆的性质

4.交换后,最后一个位置向前走一步

1.建堆

堆有两种,大根堆和小根堆。

(1)排升序,建立大根堆

(2)排降序,建立小根堆

再利用大根堆或者小根堆进行排序

(1)建堆

我们建堆使用向下调整的方式,从最后一棵子树开始。每一棵子树都需要进行向下调整。

向下调整创建大根堆

核心:先将一维数组以层序遍历的方式创建成一棵完全二叉树,然后从最后一棵子树(最后一个父亲结点)开始,向下调整(从父亲到最后一个孩子)。每一轮结束,父亲结点减一。

回忆父亲结点和孩子结点的关系:假设知道父亲结点的下标为i,则左孩子的下标为:(2*i)-1;如果知道孩子结点的下标(左右都可)为i,则父亲结点为:(i-1)/2。

通过图示理解大根堆的向下调整创建:

有该数组:{27,15,19,18,28,34,65,49,25,37}

得到一棵逻辑上的完全二叉树:

然后从最后一棵子树开始向下调整:

调整的步骤:

(1)左右孩子比较,找出孩子大的下标(2)大孩子与父亲结点比较,大于父亲结点则交换(3)父亲结点下标走到孩子位置,孩子再等于新的下标

下面是调整的过程:

第一轮:调整最后一棵子树,p的开始位置为4

c=2*9+1=18,上面标错了 

第二轮:p--,走到3的位置

c=2*7+1=15,上面标错了  

第三轮:p--,走到2的位置

c=2*6+1=13,上面标错了  

第四轮:p--,走到1的位置

 

第五轮:p--,走到0的位置

上面就是堆调整的全过程,我们总结一下:

p代表需要调整的子树,每调整完一轮,p--,直到p调整完;而在每一轮的调整中,交换完一小轮,p就要向下走,c也要继续往下走,直到不满足条件。

下面是建立大根堆的代码:

public void create(int[] arr) {//从最后一棵子树向下调整for (int parent = (arr.length -1-1)/2; parent >= 0 ; parent--) {siftDown(arr,parent,arr.length);}}/***  向下调整*/
public void siftDown(int[] arr,int parent,int len) {int child = parent*2 + 1;while(child < len) {//1.找左右孩子最大的if(child+1 < len && arr[child+1] > arr[child]) {child = child + 1;}if(arr[child] > arr[parent]) {swap(arr,child,parent);parent = child;child = parent*2 + 1;}else {break;}}
}
public void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}

下面是建立小根堆的代码:

 public void createminHeap() {for (int parent = (usedSize-1)/2; parent >= 0 ; parent--) {siftDown2(parent,usedSize);}}private void siftDown2(int parent,int end) {int child = parent*2+1;//找到左孩子下标//循环结束,说明一棵子树调整完成while (child < end) {//1.找左右孩子最小的一个.进入if说明第二个孩子比较大if(child+1 < usedSize && elem[child] > elem[child+1]) {child++;}//2.比较父亲结点和大孩子结点if(elem[parent] > elem[child]) {swap(parent,child);parent = child;child = parent*2+1;//找到左孩子下标}else {break;//满足则结束循环}}}
public void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}

2.堆排序

在学会如何建堆之后,才能进行堆排序操作,堆排序操作是比较简单的

思想:每次堆顶元素和最后一个元素交换,交换完进行一次向下调整,每次过后最后一个元素向前走

 大根堆可以保证堆顶元素是最大的,每次和最后一个位置交换;不断交换,就会形成升序。

 堆排序部分代码:

public void headSort(int[] arr) {//1.排升序,建大根堆create(arr);int end = arr.length - 1;while(end > 0) {//2.每次交换堆顶和最后一个元素swap(arr,0,end);//3.交换完调整siftDown(arr,0,end);end--;}}

堆排序完整代码:

public class heapSort {public void headSort(int[] arr) {//1.排升序,建大根堆create(arr);int end = arr.length - 1;while(end > 0) {//2.每次交换堆顶和最后一个元素swap(arr,0,end);//3.交换完调整siftDown(arr,0,end);end--;}}/*** 交换*/public void swap(int[] arr,int i,int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}public void create(int[] arr) {//从最后一棵子树向下调整for (int parent = (arr.length -1-1)/2; parent >= 0 ; parent--) {siftDown(arr,parent,arr.length);}}/***  向下调整*/public void siftDown(int[] arr,int parent,int len) {int child = parent*2 + 1;while(child < len) {//1.找左右孩子最大的if(child+1 < len && arr[child+1] > arr[child]) {child = child + 1;}if(arr[child] > arr[parent]) {swap(arr,child,parent);parent = child;child = parent*2 + 1;}else {break;}}}}

总结:

(1)时间复杂度:O(n*logn)

(2)空间复杂度:O(1)

(3)稳定性:不稳定

二、快速排序

快速排序是一种基于二叉树形式的交换数据排序,快听名字就知道他很快

下面介绍的快速排序,我们会介绍:快排的思想、Hoare版分割法、挖坑法分割法、如何优化快速排序快速排序的非递归写法

1.思想解析

(1)官方概念

在待排序的 N个记录中任意取一个记录,把该 记录放在最终位置后, 数据序列被此记录分成两部分。 (如何分成两个部分:所有关键字比该记录关键 字小的放在前一部分, 所有比它大的放置在后一部分, 并把该记录排在这两部分 的中间,这个过程称作一次快速排序)之后重复上述过程, 直到每一部分内只有 一个记录为止。

(2)简述概念

(1)每次找一个基准,再定义两个指针(分别指向数据序列的两头),遍历该数据序列。(2)遍历时,满足一定的条件,就交换指针所指向的值或者其他操作,直到两个指针相遇。

(3)指针相遇的位置就将该序列分成左右两部分

(4)左右两个部分再重复(1)-(3)的操作,直到不能再分解,排序完成

我们这里暂时称(1)(2)两个步骤为:寻找基准法 

上面的都是干巴巴的概念,很难理解,下面结合图解。

(3)图解如何完成排序

下面第一步到找到下一个基准的过程称为:找基准法(官方概念为Hoare版);找基准法还有另一种称为:挖坑法

使用Hoare找基本法的每一轮:

(4)部分代码

根据上述的图解,我们得出,每次结束,就会将数组分成两个部分,然后每个部分再重复步骤,可以想到使用递归的方式完成。

partition2方法就是找基准法,具体实现下面介绍

private static void quick(int[] array,int start,int end) {if(start >= end) {return;}int pivot = partition2(array,start,end);//记录每一轮基准的位置//递归左边quick(array,start,pivot-1);//递归右边quick(array,pivot+1,end);}

找基准法一共有三种:Hoare版、挖坑法、前后指针法,其中最常用的是:挖坑法;不常见的是:前后指针法

2.Hoare版找基准

根据Hoare版的思想完成的代码,具体思想不再重复

private static int partition1(int[] array,int left,int right) {//1.确定基准int tmp = array[left];int l = left;//2.遍历数组,直到相遇while (left < right) {while (left < right && array[right] >= tmp) {right--;}while (left < right && array[left] <= tmp) {left++;}//交换两个值swap(array,left,right);}//3.交换相遇位置和基本位置的值swap(array,l,right);//4.返回相遇位置,作为下一次的分割点return right;
}
private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

第一种快速排序完整代码:

public static void quick(int[] array,int start,int end) {if(start >= end) {return;}int pivot = partition(array,start,end);//记录每一轮基准的位置//递归左边quick(array,start,pivot-1);//递归右边quick(array,pivot+1,end);
}
private static int partition(int[] array,int left,int right) {//1.确定基准int tmp = array[left];int l = left;//2.遍历数组,直到相遇while (left < right) {while (left < right && array[right] >= tmp) {right--;}while (left < right && array[left] <= tmp) {left++;}//交换两个值swap(array,left,right);}//3.交换相遇位置和基本位置的值swap(array,l,right);//4.返回相遇位置,作为下一次的分割点return right;
}
private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;
}

3.挖坑法找基准

挖坑法是最常用的一种,具体实现和Hoare版很相似。

(1)挖坑法思想

每一轮的结果:

(2)挖坑法代码

private static int partition(int[] array,int left,int right) {//1.将基准存入临时变量中,形成一个坑int tmp = array[left];//2.遍历数组,直到相遇while (left < right) {while (left < right && array[right] >= tmp) {right--;}//3.放入left位置array[left] = array[right];while (left < right && array[left] <= tmp) {left++;}//4.放入right位置array[right] = array[left];}//5.补坑array[right] = tmp;return right;
}
private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

第二种快速排序完整代码:

public static void quick(int[] array,int start,int end) {if(start >= end) {return;}int pivot = partition(array,start,end);//记录每一轮基准的位置//递归左边quick(array,start,pivot-1);//递归右边quick(array,pivot+1,end);
}
private static int partition(int[] array,int left,int right) {//1.将基准存入临时变量中,形成一个坑int tmp = array[left];//2.遍历数组,直到相遇while (left < right) {while (left < right && array[right] >= tmp) {right--;}//3.放入left位置array[left] = array[right];while (left < right && array[left] <= tmp) {left++;}//4.放入right位置array[right] = array[left];}//5.补坑array[right] = tmp;return right;
}
private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

4.快速排序的优化

对于不同的数据,死板的写法不一定有其他排序快;但是根据不同的场景进行不同的优化,可以大大提高快速排序的效率。

下面介绍两种常见的优化方式 

(1)三数取中法

思想:在一堆序列中,找到最左边的数、中间的数和最右边的数中 中间小的一个数与最左边的数进行交换,再以最左边的数为基准进行划分。可以防止升序或者逆序而造成的单分支情况。

应对情况:常见与已有序的序列(顺序或者逆序) 

核心:找到三个数中排在中间的数

举例:下面为顺序的序列,当默认选择第一个数为基准时,右边都是比基准大的而不会交换;在递归过程中,就会形成单分支的二叉树,复杂度进而变大

 三数取中,取的数为:

 如何找中间值:

代码如下:

private static void quick(int[] array,int start,int end) {if(start >= end) {return;}//三数取中法(优化)int index = findMid(array,start,end);swap(array,index,start);int pivot = partition(array,start,end);//递归左边quick(array,start,pivot-1);//递归右边quick(array,pivot+1,end);}
/*找中间大的元素下标*/private static int findMid(int[] array,int left,int right) {int mid = left+( (right - left) >> 1 );if(array[left] > array[right]) {if(array[left] < array[mid]) {return left;}else if(array[right] > array[mid]) {return right;}else  {return mid;}}else {if(array[mid] > array[right]) {return right;}else if(array[left] > array[mid]) {return left;}else {return mid;}}}

(2)递归到小区间时,使用插入排序

思想:结合插入排序

应对情况:一棵二叉树的结点,一般2/3都集中在最后面的几层;如果一直递归下去,计算量是非常大的。

核心:会使用插入排序

private static void quick(int[] array,int start,int end) {if(start >= end) {return;}//递归到一定的区间,使用插入排序(优化)if((end-start+1) < 5) {insertSort(array,start,end);return;}int pivot = partition2(array,start,end);//递归左边quick(array,start,pivot-1);//递归右边quick(array,pivot+1,end);}/*** 区间插入排序*/private static void insertSort(int[] array,int start,int end) {for (int i = start+1; i <= end; i++) {int tmp = array[i];int j = i-1;for (; j >= start ; j--) {if(array[j] > tmp) {array[j+1] = array[j];}else {break;}}array[j+1] = tmp;}}

总结:针对递归法

(1)时间复杂度:O(N*logN)

(2)空间复杂度:O(logN)

(3)稳定性:不稳定

(4)使用场景:数据较乱,不适合趋于有序或者已有序的

5.快速排序非递归

思想:

(1)先找一次基准(借助挖坑法)

(2)(判断:如果基准的左边:pivot-1>left不成立,则不放入栈;右边:pivot+1>right不成立也不放入)放入基准左边头跟尾的两个元素,再放入基准右边的头跟尾两个元素。

(3)栈不为空,取fenbuie除两个元素,分别赋值给右跟左

(4)以左跟右的区间继续找基准

(5)重复(2)-(4)

图解:

判断是否入栈:

代码: 

public static void quickSortNor(int[] array) {Stack<Integer> stack = new Stack<>();int left = 0;int right = array.length - 1;//1.找第一次基准int pivot = partition(array,left,right);//2.将基准左右两边存入栈中if(pivot-1>left) {stack.push(left);stack.push(pivot-1);}if(pivot+1<right) {stack.push(pivot+1);stack.push(right);}//3.栈不为空出栈while (!stack.empty()) {right = stack.pop();left = stack.pop();pivot = partition2(array,left,right);//2.将基准左右两边存入栈中if(pivot-1>left) {stack.push(left);stack.push(pivot-1);}if(pivot+1<right) {stack.push(pivot+1);stack.push(right);}}}
private static int partition(int[] array,int left,int right) {//1.将基准存入临时变量中,形成一个坑int tmp = array[left];//2.遍历数组,直到相遇while (left < right) {while (left < right && array[right] >= tmp) {right--;}//3.放入left位置array[left] = array[right];while (left < right && array[left] <= tmp) {left++;}//4.放入right位置array[right] = array[left];}//5.补坑array[right] = tmp;return right;
}
private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

时间复杂度:nlogn

空间复杂度:longn

稳定性:不稳定

三、归并排序

下面的归并排序,我们注重介绍:归并排序的思路(包括文字思路,图片思路)、归并排序的代码代码执行过程的讲解

归并排序和快速排序类型,使用了分治的思想,其中,也需要用到递归的。而归并排序的核心就是一分为二,再合并。

1.归并思路

(1)大致思想

(1)将数据不断平分为两段,直到不能再分解,形成一个单独的有序个体。

(2)然后在往回走的过程中,不断合并做到有序。

(2)具体图解:

分解思路:

并起思路:在合并的过程中就完成了排序

(3)如何分解:加入一点数学知识

下面是所有片段的left,mid和right位置

根据这里得出一个规律:当left>=right时,分解停止。

2.代码展示及步骤

代码中的注释很全面的介绍了每个步骤

(1)代码展示

public class mergeSort {public static void mergeS(int[] array){mergeF(array,0,array.length-1);}//一.分解的方法(归)private static void mergeF(int[] array,int left,int right){//1.停止分解的条件if(left>=right) {return;}//2.记录拆分的位置int mid = left + ((right - left)>>1);//3.拆成的左半部分(使用递归)mergeF(array,left,mid);//4.拆成的右半部分(使用递归)mergeF(array,mid+1,right);//5.开始合并merge(array,left,mid,right);}//二.合并的方法(并)private static void merge(int[] array,int left,int mid,int right) {int[] arrTmp = new int[right-left+1];int s1 = left;//遍历第一个子序列int e1 = mid; //记录第一个子序列末端位置int s2 = mid+1;//遍历第二个子序列int e2 = right;//记录第二个子序列末端位置int k = 0;//遍历临时数组//1.将其中一个序列的数据全部拷入临时数组中while (s1<=e1 && s2<=e2) {if(array[s1] > array[s2]) {arrTmp[k++] = array[s2++];}else {arrTmp[k++] = array[s1++];}}//2.将未拷完的子序列继续拷入while (s1<=e1) {arrTmp[k++] = array[s1++];}while (s2<=e2) {arrTmp[k++] = array[s2++];}//3.将排序好的临时数组中的数据拷回原数组for (int i = 0; i < arrTmp.length; i++) {array[i+left] = arrTmp[i];}}
}

(2)数据拷贝回原数组部分

//3.将排序好的临时数组中的数据拷回原数组for (int i = 0; i < arrTmp.length; i++) {array[i+left] = arrTmp[i];}

3.代码分析

上面对代码的简述也只是一种简单的概述,下面详细介绍一下代码的执行过程,包括是如何递归,及如何并。

部分过程:

类似二叉树的递归,当递归到一定条件时,才会开始合并;并不是和前面的图片一样,要全部分解完才开始逐一递归。

部分合并过程:

总结:

(1)时间复杂度:O(N*logN)

(2)空间复杂度:O(logn)

(3)稳定性:稳定

(4)使用场景:在磁盘中的外排序问题

 4.归并排序非递归

引入:非递归的归并排序,重点也在后面的合并方法上;递归法是先递归到最小单元才开始合并,而非递归是直接开始合并。

步骤分析:

(1)定义一个变量gap,用来标识最小的有序集;每次合并两个有序集,当gap>=数组长度一半时,代表排序完成。

(2)定义一个变量i,用来遍历数组中所有的有序集,每次跳过两个有序集

(3)我们只需要根据合并的方法拿到对应的下标即可

思路解析:非递归,我们使用直接从最小单元开始合并,也就是以一个数据作为最小单位。


需要拿到的下标:这是根据上面递归法写的合并方法,这里通用。

根据上面得到以下代码:

public static void mergeSortNor(int[] array) {int gap = 1;//一组的数据个数//循环一次,完成一轮合并while (gap < array.length) {//每循环一次,代表合并两个组for (int i = 0; i < array.length; i = i+2*gap) {int left = i;int mid = left+gap-1;//两个if,防止数组越界if(mid >= array.length) {mid = array.length-1;}int right = mid+gap;if(right >= array.length) {right = array.length-1;}merge(array,left,mid,right);}gap = gap*2;}}

完整非递归代码:

public static void mergeSortNor(int[] array) {int gap = 1;//一组的数据个数//循环一次,完成一轮合并while (gap < array.length) {//每循环一次,代表合并两个组for (int i = 0; i < array.length; i = i+2*gap) {int left = i;int mid = left+gap-1;//两个if,防止数组越界if(mid >= array.length) {mid = array.length-1;}int right = mid+gap;if(right >= array.length) {right = array.length-1;}merge(array,left,mid,right);}gap = gap*2;}}
private static void merge(int[] array,int left,int mid,int right) {int[] arrTmp = new int[right-left+1];int s1 = left;int e1 = mid;int s2 = mid+1;int e2 = right;int k = 0;while (s1<=e1 && s2<=e2) {if(array[s1] > array[s2]) {arrTmp[k++] = array[s2++];}else {arrTmp[k++] = array[s1++];}}while (s1<=e1) {arrTmp[k++] = array[s1++];}while (s2<=e2) {arrTmp[k++] = array[s2++];}for (int i = 0; i < arrTmp.length; i++) {array[i+left] = arrTmp[i];}}

 5.使用归并排序解决海量数据

当所排序的对象巨大时,需要使用外部排序,而归并排序就是外部排序的一种典型代表。

外部排序:排序过程需要在磁盘等外部存储进行的排序(不直接借助内存)

使用场景:内存只有1G,而排序的数据却有100G

所以我们需要使用归并排序去完成:

(1)先把文件切分成 200 份,每个 512 M
(2)分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
(3)进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了(这个时候不借助内存)

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

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

相关文章

自定义神经网络二之模型训练推理

文章目录 前言模型概念模型是什么&#xff1f;模型参数有哪些神经网络参数案例 为什么要生成模型模型的大小什么是大模型 模型的训练和推理模型训练训练概念训练过程训练过程中的一些概念 模型推理推理概念推理过程 总结 前言 自定义神经网络一之Tensor和神经网络 通过上一篇…

IOBR2 更新(学习自备)

IOBR查看其收录的相关基因集(自备)_肿瘤 tme特征 iobr-CSDN博客 IOBR2&#xff1a;多维度解析肿瘤微环境 - 知乎 (zhihu.com) 学习手册&#xff1a;https://iobr.github.io/book/ &#xff08;里面有详细教程&#xff09; 系统综合的分析工具&#xff08;Immuno-Oncology Bi…

学习python的第7天,她不再开放她的听歌榜单

我下午登录上小号&#xff0c;打开聊天消息看到了她的回复&#xff0c;我很开心兴奋&#xff0c;可是她不再开放她的听歌榜单了&#xff0c;我感觉得到&#xff0c;我要失恋了。 “因为当年电视上看没有王菲版本的” “行”。 “那你以后还会开放听歌榜单吗&#xff1f;”我…

opencv基础 python与c++

question: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib Opencv 一、读取图片 (1).imshow Mat imread(const string& filename, intflags1 );flags: enum { /* 8bit, color or not */CV_LOAD_IMAGE_UNCHANGED -1, /* 8bit, gray */CV_LOAD_I…

基于springboot+vue的大型商场应急预案管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

应用回归分析:非参数回归

非参数回归是一种统计方法&#xff0c;它在建模和分析数据时不假设固定的模型形式。与传统的参数回归模型不同&#xff0c;如线性回归和多项式回归&#xff0c;非参数回归不需要预先定义模型的结构&#xff08;例如&#xff0c;模型是否为线性或多项式&#xff09;。这使得非参…

小米标准模组+MCU 快速上手开发(一)——之固件下载

小米标准模组+MCU 开发笔记之固件下载 背景技术名词简介● 小米IoT开发者平台● 小米IoT 模组● ESP系列简介问题描述 + 解决方式问题1:固件下载是否有示例,如何下载到硬件板卡中?问题2:固件下载的官方程序是什么?在哪里?该如何使用?问题3:固件下载时,Flash和Ram 有什…

VCRUNTIME140_1.dll丢失是怎么回事,如何解决

当计算机系统中找不到vcruntime140_1.dll文件时&#xff0c;运行依赖于该文件的软件通常会显示错误消息&#xff0c;这类错误消息可能会包含以下几种形式&#xff1a; 明确提示缺失文件&#xff1a;错误信息可能直接指出“无法找到vcruntime140_1.dll”或“vcruntime140_1.dll…

怎么自学python,大概要多久?python多久上手?

无限时长~~~~技术不断在更新&#xff0c;你的自学不也需要一直进行吗&#xff1f; 但如果是问&#xff1a;自学多长时间可以入门&#xff1f;或者可以找到工作&#xff1f;那我可以告诉你答案。 从零基础开始自学Python&#xff0c;依照每个人理解能力的不同&#xff0c;大致…

no main manifest attribute, in app.jar

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

软件工程复习笔记

一、软件工程概述 软件 = 程序 + 数据 + 相关文档 软件危机(Software Crisis) 指由于落后的软件生产方式无法满足迅速增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。 软件工程三要素 方法、工具、过程 软件工程目标 在给定成本、进度的…

目标检测新SOTA:YOLOv9 问世,新架构让传统卷积重焕生机

在目标检测领域&#xff0c;YOLOv9 实现了一代更比一代强&#xff0c;利用新架构和方法让传统卷积在参数利用率方面胜过了深度卷积。 继 2023 年 1 月 YOLOv8 正式发布一年多以后&#xff0c;YOLOv9 终于来了&#xff01; 我们知道&#xff0c;YOLO 是一种基于图像全局信息进行…

[HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,佬佬会看到更多有趣的博客哦!!! 喵喵喵,你对我真的很重要! 目录 前言 网页标题:手机批发业务-商品备选区<

解析OOM的三大场景,原因及实战解决方案

目录 一、什么是OOM 二、堆内存溢出&#xff08;Heap OOM&#xff09; 三、方法区内存溢出&#xff08;Metaspace OOM&#xff09; 四、栈内存溢出&#xff08;Stack OOM&#xff09; 一、什么是OOM OOM 是 Out Of Memory 的缩写&#xff0c;意思是内存耗尽。在计算机领域…

【Spring MVC】处理器映射器:AbstractHandlerMethodMapping源码分析

目录 一、继承体系 二、HandlerMapping 三、AbstractHandlerMapping 四、AbstractHandlerMethodMapping 4.1 成员属性 4.1.1 MappingRegistry内部类 4.2 AbstractHandlerMethodMapping的初始化 4.3 getHandlerInternal()方法&#xff1a;根据当前的请求url&#xff0c;…

Java基于物联网技术的智慧工地云管理平台源码 依托丰富的设备接口标准库,快速接入工地现场各类型设备

目录 风险感知全面化 项目进度清晰化 环境监测实时化 人员管理高效化 工地数字化 数据网络化 管理智慧化 智慧工地平台整体架构 1个可扩展监管平台 2个应用端 3方数据融合 N个智能设备 智慧工地的远程监管&#xff0c;是工地负责人掌握施工现场情况的必要手段&…

12 - grace数据处理 - 泄露误差改正 - 区域核函数法

grace数据处理 - 泄露误差改正 - 区域核函数法 *0* 引言*1* 实现过程*2* 实现的主要方法0 引言 高斯滤波又称为高斯平滑,其本质是一种加权平均方法,球面某点的信号可由其它点加权平均得到,可实现抑制高阶噪声的目的。既然是一种平滑方法,对研究区边缘数据平滑时容易产生数据…

✅技术社区项目—JWT身份验证

通用的JWT鉴权方案 JWT鉴权流程 基本流程分三步: ● 用户登录成功之后&#xff0c;后端将生成的jwt返回给前端&#xff0c;然后前端将其保存在本地缓存; ● 之后前端与后端的交互时&#xff0c;都将iwt放在请求头中&#xff0c;比如可以将其放在Http的身份认证的请求头 Author…

【编译原理】第六章课后习题(王原生第三版)

前言 课本&#xff1a; 编译原理&#xff08;第三版&#xff09;[王生原、董渊…等编著]习题&#xff1a; 主要习题内容是第一章到第八章&#xff0c;具体内容如下表 章节内容链接第一章课后部分选择题https://blog.csdn.net/Zchengjisihan/article/details/136243955第二章课…

C++ //练习 8.4 编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中。

C Primer&#xff08;第5版&#xff09; 练习 8.4 练习 8.4 编写函数&#xff0c;以读模式打开一个文件&#xff0c;将其内容读入到一个string的vector中&#xff0c;将每一行作为一个独立的元素存于vector中。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09…