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

速通数据结构与算法第七站 排序

系列文章目录

速通数据结构与算法系列

1   速通数据结构与算法第一站 复杂度          http://t.csdnimg.cn/sxEGF

2   速通数据结构与算法第二站 顺序表          http://t.csdnimg.cn/WVyDb

3   速通数据结构与算法第三站 单链表          http://t.csdnimg.cn/cDpcC

4   速通数据结构与算法第四站 双链表          http://t.csdnimg.cn/0VyDl

5   速通数据结构与算法第五站 栈&&队列     http://t.csdnimg.cn/MRU83

6   速通数据结构与算法第六站 树&&堆        速通数据结构与算法第六站 树&&堆-CSDN博客

感谢佬们支持!


目录

系列文章目录

  • 前言
  • 一、插入排序
  •         复杂度分析
  • 二、希尔排序
  •         复杂度分析
  • 三、选择排序
  •         复杂度分析
  • 四、堆排序
  •         复杂度分析
  • 五、冒泡排序
  •         复杂度分析
  • 六、快排
  •       1 hoare法
  •       2 挖坑法
  •       3 前后指针法(recommend)
  •       4 非递归
  •       5 优化之三数取中(随机选key)
  •       6 优化之小区间优化
  •       7 优化之三路划分
  •       8 SGI sort设计
  •       9 复杂度分析
  • 七、归并排序
  •       1 递归版本
  •       2 非递归但梭哈实现法
  •       3 非递归但不梭哈实现法
  •       4 复杂度分析
  • 八、其他排序的介绍
  •       1 计数排序
  •               复杂度分析
  •       2 桶排序
  •                复杂度分析
  •       3 基数排序
  • 九、排序总结
  • 总结

前言

   这一节是速通数据结构的最后一节,我们要来学习排序。排序,看起来是个很简单的话题,实则一点也不简单。

举个例子,为了达到极致的效率,STL(SGI)算法库中的排序优化一大堆,首先要用快排,当数据个数小于16为了减少递归层数,改调插排,为了key的大小更适中搞了三数取中

一旦"划分恶化"改调堆排,防止时间复杂度恶化到O(n方)。所以排序还是很值得我们学习滴~

注:这篇文章收稿时达到了2w字!最用心的一集


一、插入排序

插入排序类似于我们整理扑克的过程

学排序首先要从单趟排序开始

假设现在有一个[2,4,7]的有序序列,我们要往其中插入一个数

1、假设我们插入的是1,2、4、7就都要往后移动

2、假设我们插入的是5,那需要挪的就是7

3、假设我们插入的是8,那就不需要挪,直接在最后插入即可

有了思路之后,我们就可以先来搞单趟排序了

单趟排序的逻辑是将tmp插入[0,end]的有序区间时

“我比你小,你挪;我比你大,插你后面”

void InsertSort(int*a,int tmp)
{int end;int tmp;while(end>=0){if(a[end]>tmp){a[end+1]=a[end];}   else{break;}//我比你大||我比所有人都小,即end=-1a[end+1]=tmp;}
}

有了单趟排序,就能推出整个排序了,显然,第一个数不用排,最后一个数的位置是i-1

所以每次的end为i-1,tmp为a [i]

最后得出的代码就是这样的。


void InsertSort(int *a,int n)
{assert(a);for (int i = 1; i < n; ++i){int end = i-1;int tmp=a[i];while (end >= 0){if (a[end] >tmp){a[end + 1] = a[end];--end;}else//a[end]<=x{break;}}a[end + 1] = tmp;}
}//SGI°æ²åÈëÅÅÐòint main()
{int arr[] = { 3,5.2,9,8,10,2 };int size = sizeof(arr) / sizeof(arr[0]);InsertSort(arr, size);
}

打印结果:


 复杂度分析

按最坏来看,如果是完全逆序的情况

时间复杂度就是1+2+……+n,最后就是O(n^2)

但是如果数据是完全有序/接近有序的情况,我们仅需一次遍历即可

也就是O(n)


空间复杂度是O(1)


二、希尔排序


希尔排序算是对插入的升级

希尔排序又称缩小增量法,希尔排序的基本增量是:先选定一个整数,将待排序中的所有记录分成个组,所有距离为选定整数的记录分在同一组内,并对每一组的记录做好排序

。然后将选定整数除以一个数,重复上述步骤,当这个整数到达1时,进行的最后一次排序

会得到一个有序的结果

看似逻辑很复杂,实际很简单,就是分组进行插入排序而已,利用了插入排序数据越有序

时间复杂度越小这一特性 

如何分组?

我们通过选取一个gap来操作,让间隔为gap的数为一组

例:

gap=3

[9,1,2,5,7,4,8,6,3,5]

我们将其分为红、绿、蓝三组

现对红色一组进行插入排序

蓝色一组

绿色一组


我们先来写红色组排队的逻辑

只需一次让i+=gap即可

            int gap=3;for (int i = 0; i < n - gap; i+=gap){int end=i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else//a[end]<=x{break;}}a[end + gap] = tmp;}

下来如果要排三组,就要再加一层for循环

由于只有3组,所以我们给一个gap次的for循环即可

            int gap=3;for(int j=0;j<gap;++j){for (int i = j; i < n - gap; i+=gap){int end=i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else//a[end]<=x{break;}}a[end + gap] = tmp;}}

或者用另一种方法,这样就可以在代码层面上减少一层循环(实际循环是没有减少的)

我们不让i一次+=gap了,而是一次加一个

        int gap=3;for (int i = 0; i < n - gap; i++){int end=i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else//a[end]<=x{break;}}a[end + gap] = tmp;}

这样就从之前的一组一组插排变成了多组同时开始插排


在此基础上,我们要变化gap的值,最终让gap变为1,这样的话,最终gap就会等于1,最后一次排序的时候就会变成插排,实现有序

#include<stdio.h>
#include<assert.h>
//ϣvoid ShellSort(int* a, int n)
{assert(a);int gap = n;while (gap > 1)//gap==1 {gap /= 2;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else//a[end]<=x{break;}}a[end + gap] = tmp;}}
}int main()
{int arr[] = { 3,5,2,9,8,10,2,2,9,8 };int size = sizeof(arr) / sizeof(arr[0]);ShellSort(arr, size);
}

 打印结果


复杂度分析

希尔排序按我们的常理来看,其时间复杂度取决于gap的选择

所以其时间复杂度很难计算,这里给到两本权威书上的解释

由于咱们的gap是按照knuth的方式来提出的,所以时间复杂度就O(n^1.25)~1.6*O(n^1.25)来算

所以这个排序是个很玄学的排序,就像跳表中选取每次插入数据是否提高层数的概率p一样


 空间复杂度依旧是O(1) 


三、选择排序

先介绍一下选择排序的基本思想

每一次从待排序的数据元素中选出最大/最小的一个元素,存放在序列的起始位置,直到待排序的所有元素全部排完

我们首先容易想到的就是直接选择排序,每次遍历我们可以同时选出最大/最小的数,将其放在序列的最左/最右

单趟就很容易想了,就是每次遍历选出最小/最大即可


int left = 0, right = n - 1;int maxi = left, mini = left;for (int i = left; i <= right; ++i)//ұ{if (a[i] > a[maxi]){maxi = i;}if (a[i] < a[mini]){mini = i;}}swap(&a[left], &a[mini]);swap(&a[right], &a[maxi]);

剩下只要每次维护left,right即可

但是还有一个坑,如果某一波,maxi正好就在left的位置

那更新最小值的时候就把最大值换走了

所以我们加个特判即可

#include<stdio.h.>void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void SelectSort(int*a,int n)
{int left = 0, right = n - 1;while (left < right){int maxi = left, mini = left;for (int i = left; i <= right; ++i)//ұ{if (a[i] > a[maxi]){maxi = i;}if (a[i] < a[mini]){mini = i;}}swap(&a[left], &a[mini]);if (maxi == left){maxi = mini;}swap(&a[right], &a[maxi]);++left;--right;}
}
int main()
{int arr[] = { 3,5,2,9,8,10,2,2,9,8 };int size = sizeof(arr) / sizeof(arr[0]);SelectSort(arr, size);for (int i = 0; i < size; ++i){printf("%d ", arr[i]);}

打印结果


复杂度分析

选择排序由于每次都需要遍历找最大/最小值,所以时间复杂度最好最坏情况都是O(n^2),确实是最fvv的排序


空间复杂度是O(1)


四、堆排序

堆排序就是从直接选择排序变成了用堆选数。

关于堆排序的逻辑我们在上篇博客已经介绍过了,这里就不再赘述了

直接上代码

#include<stdio.h>//swap
void swap(int* px, int* py)
{int tmp = *px;*px = *py;*py = tmp;
}//向上调整
void AdjustUp(int* a, int child)
{int parent = (child - 1) / 2;while (child > 0)//最多调到根{if (a[child] > a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//向下调整
void AdjustDown(int* a, const int n, int parent)
{//找左右孩子大的一个交换int child = parent * 2 + 1;//suppose左孩子大,经典玩法while (child < n)//如果孩子超出了数组范围,说明parent是叶子节点{if (child + 1 < n && a[child + 1] > a[child])//防止越界{child++;}if (a[child] > a[parent]){swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}//排升序--建大堆(向上调整建堆)
void HeapSort1(int* a, int n)
{for (int i = 1; i < n; ++i)//O(lgN){AdjustUp(a, i);}int end = n - 1;while(end>0){swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}//排升序--建大堆(向下调整建堆)
void HeapSort2(int* a, int n)
{for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}int main()
{int arr[] = { 2,4,5,6,7,2,6 };const int n = sizeof(arr) / sizeof(arr[0]);HeapSort2(arr, n);for (int i = 0; i < n; ++i){printf("%d ", arr[i]);}printf("\n");return 0;
}

打印结果


复杂度分析 

关于堆排序的时间复杂度我们也分析过了

如果是向下调整建堆,就是O(n*lgn)


由于是原地建堆,所以空间复杂度是O(1)


五、冒泡排序

冒泡排序属于交换排序

我们来介绍一下交换排序的思想

所谓交换,就是根据序列中的两个记录键值对的比较结果来swap这两个记录在序列的位置

交换排序的特点是:将键值较大的记录先后移动,将键值较小的记录先前移动

冒泡排序也是我们非常熟悉的排序了

这里直接上代码

#include<stdio.h>
#include<stdbool.h>void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void BubbleSort(int* a, int n)
{for (int i = 0; i < n; ++i){bool flag = false;for (int j = i + 1; j < n; ++j){if (a[i] > a[j]){flag = true;swap(&a[i], &a[j]);}}if (flag == false){break;}}
}int main()
{int arr[] = { 3,5,2,9,8,10,2,2,9,8 };int size = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, size);for (int i = 0; i < size; ++i){printf("%d ", arr[i]);}
}

打印结果


 复杂度分析

显然,如果序列本身有序,遍历一次即可排好序

所以时间复杂度最好情况为O(n)

如果是最坏情况,则是O(n^2)

需要注意的是,在相对有序的情况下,冒泡排序的时间复杂度还是不如插入排序的


空间复杂度是O(1) 


六、快排

1 hoare法

单趟:选出一个基准值key,把他放到正确的位置(最终排好序要蹲的事)

例:

最终就会变成

我们有三种方法,第一种是这样的:

法一:

1、具体操作为左边找严格比key大的,右边找严格比key小的,然后swap

2、最后在最后停下的位置和key所在的位置交换一下

一波单趟排序就做好啦~

//hoare法
int partition1(int* a, int left, int right)
{int mid = GetMidNumi(a, left, right);swap(&a[left], &a[mid]);int keyi = left;while (left < right){//左边是key,右边先走//带上=,表示找严格大/严格小while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}swap(&a[left], &a[right]);}//最后left和key换一下swap(&a[left], &a[keyi]);return keyi;
}

注:由于是循环套循环,所以做好边界判断

为什么要找严格大于/小于的?

如果某个用例为[2,2,2,2,2,2];那就完了,会一直死循环


2 挖坑法

由于hoare大佬的这个方法太拉拉了,又给出了第二种(感觉更复杂了,意思差不多)

法二:

1、先将key值存在一个变量里,就会形成一个坑位

2、依然是左边找大,右边找小

右边找小,放到坑里,更新坑的位置

左边再找大,放进坑里,更新坑的位置

3、当left和right相遇,将key填入坑中

按图来看,就是这样

//挖坑法
int partition2(int* a, int left, int right)
{int mid = GetMidNumi(a, left, right);swap(&a[left], &a[mid]);int key = a[left];int hole = left;//此时 left为坑while (left < right){//左边是key,右边先走//带上=,表示找严格大/严格小while (left < right && a[right] >= key){right--;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}//最后坑的位置放kea[left] = key;return hole;
}

3 前后指针法(recommend)

这个方法还是很清爽很简洁的

法三

1、初始时,prev指向序列开头,cur指针指向prev指针后的一个位置,就像这样

2、cur所在位置比key小的,

如果找到了,就++prev,和cur交换

交换完以后,++cur

3 如果cur所在位置比key大

就++cur

在排序过程中,大概就是两种情况

1 prev紧跟着cur

2 prev和cur隔着比key大的一段数的区间 

按图来看,就是这样

//前后指针法
int partition3(int* a, int left, int right)
{int mid = GetMidNumi(a, left, right);swap(&a[left], &a[mid]);int keyi = left;int cur = left+1;int prev = left;while (cur <= right){if (a[cur] < a[keyi] && (++prev) < cur)//不要自己和自己换~swap(&a[cur], &a[prev]);++cur;}swap(&a[keyi], &a[prev]);return prev;
}

那么问题来了,我们已经学习了3种单趟排序的方法?

如何实现完整的排序呢?

我们知道,第一波单趟排序排好的是最终key的位置

所以下来的操作非常简单

1 我们对key的左右区间递归使用该函数即可

2 当区间只有一个数/区间不存在时,递归调用结束

有了上一节我们学到的递归的经验,这波函数体就会是这样(以第三种方法为例)

void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi+1, right);
}

需要注意的是,三种单趟排序对同一序列的结果可能不同,如果有数据结构题目问

单趟排序的结果,我们需要考虑3种方式 


  4 非递归

非递归的思路也很简单,只要将递归的逻辑用栈代替即可

1、每次将left、right入栈;

2、每次取两次栈顶组成begin、end调用单趟排序

3、将单趟排序返回的keyi左右两端区间的左右顶点入栈

4、当栈为空时,循环结束

//前后指针法
int partition3(int* a, int left, int right)
{int keyi = left;int cur = left + 1;int prev = left;while (cur <= right){if (a[cur] < a[keyi] && (++prev) < cur)//不要自己和自己换~swap(&a[cur], &a[prev]);++cur;}swap(&a[keyi], &a[prev]);return prev;
}void QuickSortNonR(int* a, int left, int right)
{stack<int> st;st.push(left);st.push(right);while (!st.empty()){int end = st.top();st.pop();int begin = st.top();st.pop();int keyi = partition3(a, begin, end);//begin,keyi-1 ;  keyi+1,endif (begin < keyi - 1){st.push(begin);st.push(keyi-1);}if ( keyi + 1<end){st.push(keyi+1);st.push(end);}}}int main()
{int arr[] = { 3,5,2,9,8,10,2,2,9,8 };int size = sizeof(arr) / sizeof(arr[0]);QuickSortNonR(arr, 0, size - 1);for (int i = 0; i < size; ++i){printf("%d ", arr[i]);}
}

打印结果


5 优化之三数取中(随机选key)

三数取中其实很简单,由于我们固定选最左边的数为key会有不确定性,所以我们选取

左中右三个数中中间的那个为key

//三数取中
int GetMidNumi(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}//说明mid是最大的else if (a[right] > a[left]){return right;}else{return left;}}else{if (a[mid] > a[right]){return mid;}//说明mid是最小的else if (a[right] > a[left]){return left;}else{return right;}}
}

6 优化之小区间优化

我们注意到,当递归到一定深度时,每次的区间长度不长

但仍需要递归,这就会导致递归次数太多,有栈溢出的风险

所以我们限制一个长度,在此长度以下,我们直接调插入排序

由于是接近有序,所以效率不会太拉还可以减少递归层数

void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小区间优化--小区间直接使用插入排序if ((right - left + 1) > 10){int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort(a+left, right - left + 1);}
}

7 优化之三路划分

当你有了以上优化之后,你会发现,你依然无法通过力扣的那道排序题

. - 力扣(LeetCode)

因为有个用例是大量重复的数

这对我们的快排是极为不利的

大量的重复意味着大量的递归+遍历,而每次单趟排序都不能做事

所以有人提出了所谓三路划分


之前我们通过选取key找到大于key/小于key的区间,算的上是一种双路划分

所以三路划分是这样的

单独划分出一个等于key的区间

核心思想是这样的

1、 跟key相等的值,往后推

2、比key小的在左边,比key大的在右边,和key相等的在中间

所以我们的双指针法就变成了三指针法了,

1、a[cur]<key,交换left和cur,left++,cur++

2、a[cur]>key,交换right和cur,right--,cur++

3、a[cur]==key,cur++(只动cur!)

搞定了单趟其他就简单了

我们和之前一样递归即可

简单实现一波

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;if((end-begin+1)<15){InsertSort(a+begin, end - begin + 1);}// 小区间优化--小区间直接使用插入排序else{int mid=GetMidNumi(a,begin,end);swap(a[begin],a[mid]);int left=begin;int right=end;int key=a[begin];int cur=begin+1;while(cur<=right){if(a[cur]<key){left++;cur++;}else if(a[cur]>key){swap(a[cur],a[right]);--right;}else{cur++;}}QuickSort(a,begin,left-1);QuickSort(a,right+1,end);}
}

有了这个优化,我们就可以通过力扣的排序题啦 


8 SGI sort设计

(参考侯捷老师的《STL源码剖析》)

学习了上述优化,我们就可以来评鉴一下C++的算法库中的sort了

总述:STL的sort算法,数据量大时采用QuickSort,分段递归排序,一旦分段后的数据量小于某个门槛,为避免QuickSort的递归调用带来过大的额外负荷,就改用InsertSort

如果递归层数过深,还会改用HeapSort

InsertSort

SGI的InsertSort有两个版本,一种是递增,另一种是仿函数

版本二的可以先忽略,我们重点来看版本一的

这个是插入排序的外循环

template<class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first,RandomAccessIterator last)
{if(first==last)return ;for(RandomAccessIterator i=first+1;i!=last;++i)//外循环{__liner_insert(first,i,value_type(first));}
}

__linear_insert是这么做的

template<class RandomAccessIterator,class T>
inline void __liner_insert(RandomAccessIterator first,RandomAccessIterator last,T*)
{T value =*last;//记录尾元素//和我们写的不同的是,这里分了两种情况,是否尾比头还小(头为最小元素)if(value<*first){copy_backward(first,last,last+1);//整体后移*first=value;}else{__unguarded_linear_insert(last,value); }
}

这里的__unguarded的命名很讲究,表明这波不用判断是否超过边界(也就是我们所写代码中end>=0的逻辑),

因为我们的源码确保了最小值必然在内循环区间的最边缘

void __unguarded_linear_insert( RandomAccessIterator last,T value)
{RandomAccessIterator next=last;while(value<*next)//内循环的逻辑{*last=*next;last=next;--next;}*last=value;
}

细节满满,看似只是省下了一个简单的判断,但是在大数据量下,影响还是很可观的


QuickSort

SGI的QuickSort提供了两种单趟排序(partioning)分别是我们的单趟1和单趟3

别的都差不多,没什么好说的


threshold

SGI sort还认为,对于一个小数据量序列,甚至简单的插入排序可能更快

鉴于这种情况,适度评估序列的大小,再决定选择插排还是快排,是值得采纳的优化措施

侯捷老师认为并无定论,5~20都有可能,因设备而定


final Insert Sort

SGI也采用了当序列小于一个值就调用插入排序的做法

(在源码中,这个值是16)

const int __stl_threshold =16;

原文:如果我们令某个大小以下的序列滞留在"几近排序但尚未成功"的状态,最后再以

一次InsertSort将所有这些"几近排序但尚未成功"的子序列做一次完整的排序,其效率一般认为会比"将所有子序列彻底排好"更好,这是因为InsertSort在面对"几近排序"的序列时,有更好的表现


introsort

不当的轴承(就是key值)选择,可能导致QuickSort退化为O(n^2),所以David大佬提出了一种混合式排序,内省式排序,简称introsort。在分割行为有二次行为的倾向时,能自动检测,转而使用HeapSort,保住O(n*lgn)的下限

自我检测的函数为__lg,其实就是在找2^k<=n的最大值k

template<class Size>
inline Size __lg(Size n)
{Size k;for(k=0;n>1;n>>=1)++k;return k;
}

所以,最终的sort本体是这样的~

template<class RandomAccessIterator>
inline void sort(RandomAccessIterator first,RandomAccessIterator last)
{__introsort_loop(first,last,value_type(first),__lg(last-first)*2);__final_insertion_sort(first,last);
}
template<class RandomAccessIterator,class T,class Size>
void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last,T* ,Size depth_limit)
{while(last-first>__stl_threshold){if(depth_limit==0){//分裂恶化//调用堆排!partial_sort(first,last,last);return ;}--depth_limit;//下来就是单趟排序+三数取中的逻辑了,这里简化一下,就不写了RandomAccessIterator cut;//递归左右区间调用__introsort_loop,这里也简化一下__introsort_loop();__introsort_loop()}

当__introsort_loop()结束后,[first,last)会存在多个元素大于16的元素

此时回到主函数sort,再进行__final_insertion_sort(first,last);

template<class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first,RandomAccessIterator last)
{if(last-first>__stl_threshold){__insertion_sort(first,first+__stl_threshold);__unguarded_insertion_sort(first+__stl_threshold,last);//就是循环调用__unguarded_linear_insert; }else{__insertion_sort(first,last);}
}

该函数首先判断元素个数是否大于16,如果为否,直接调用__insertion_sort

如果为是,就分割为一段长为16的区间和剩下的区间,分别调用__insertion_sort和——__unguarded__insertion_sort处理


这就是SGI STL sort的故事了,设计非常巧妙(最用心的一集)

9 复杂度分析 

根据我们在SGI sort分析的那样,如果是普通快排,时间复杂度一般是O(n*lgn)

但是在极端情况下,会退化至O(n^2)。

但是SGI sort,我们可以保证一个O(n*lgn)的下限


空间复杂度的来源是栈帧的建立,为O(lgn)~O(n)


七、归并排序

归并排序是建立在归并操作上的一种有效的排序算法,采用了经典的分治法。

将已有序的子序列合并,得到完全有序的序列

归并的逻辑很简单,就是我们之前的合并两个有序数组的逻辑最后再拷回原数组即可 

1 递归版本
void _merge(int* a, int left, int right,int *tmp)
{//一个值时/区间不存在时结束if (left >= right){return;}int mid = (left+right) / 2;//子区间递归排序_merge(a, left, mid, tmp);_merge(a, mid + 1, right, tmp);//归并int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;printf("begin1:%d end1:%d\n", begin1, end1);printf("begin2:%d end2:%d\n", begin2, end2);int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}if (begin1 > end1){while (begin2 <= end2){tmp[i++] = a[begin2++];}}if (begin2 > end2){while (begin1 <= end1){tmp[i++] = a[begin1++];}}//记住加left!memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}void MergeSort(int* a, int n)
{int* nums = (int*)malloc(sizeof(int) * n);_merge(a,0, n-1,nums);free(nums);
}int main()
{int arr[] = { 3,5,2,9,8,10,2,2};int size = sizeof(arr) / sizeof(arr[0]);MergeSort(arr, size);for (int i = 0; i < size; ++i){printf("%d ", arr[i]);}
}

 2 非递归但梭哈实现法

非递归如何模拟模拟归并的过程呢?

归并的逻辑是先一一归并,再二二归并,在此基础上四四归并,他的逻辑更像一个后序遍历

而栈则对应的是前序遍历,所以这波不能用栈,而可以控制一个gap,实现一一、二二、四四……归并

gap==1,此时为一一归并

gap*=2,就变成了二二归并

……


 光这样还不够,我们只考虑了元素个数是二的次方的情况

如果是9,20个数据,end2,begin2,begin1会有越界的风险

所以我们要修正边界


光修正边界还不够,我们要考虑拷回原数组的问题,这里分为一把拷(梭哈)和归一部分拷一部分(不梭哈)两种情况

我们可以打印一波边界来看看情况

我们试试9个元素

//一把梭哈
void MergeSortNonRS(int* a, int n)
{int* nums = (int*)malloc(sizeof(int) * n);int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){//归并int begin1 = i;int end1 = i+gap-1;int begin2 = i+gap;int end2 = i+2*gap-1;printf("begin1:%d end1:%d\n", begin1, end1);printf("begin2:%d end2:%d\n", begin2, end2);int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){nums[j++] = a[begin1++];}else{nums[j++] = a[begin2++];}}if (begin1 > end1){while (begin2 <= end2){nums[j++] = a[begin2++];}}if (begin2 > end2){while (begin1 <= end1){nums[j++] = a[begin1++];}}}//记住加left!memcpy(a , nums, sizeof(int) *n);gap *= 2;}free(nums);
}int main()
{int a[] = { 6,1,2,6,9,3,4,6,10};MergeSortNonRS(a, 9);
}

由于begin1为i,所以不可能越界

下来,越界有这么几种情况


1、end1越了,不归并了,但是是要拷贝的(因为只剩一个数了)

【8,11】【12,15】

由于我们要一次梭哈拷贝,所以这波我们要修正边界,才能不被覆盖


2、end1没越界,begin2越界了

同理,不用归并,也要修正边界

【8,8】【9,9】


3、只有end2越界了

他是需要归并的,修正end2

【0,7】【8,15】

不归并的修正边界很简单,我们只需修成一个不存在的区间,就不进循环了

而最后需要归并的end2,我们需要计算一下修正到的值,即n-1

//一把梭哈
void MergeSortNonRS(int* a, int n)
{int* nums = (int*)malloc(sizeof(int) * n);int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){//归并int begin1 = i;int end1 = i+gap-1;int begin2 = i+gap;int end2 = i+2*gap-1;printf("begin1:%d end1:%d\n", begin1, end1);printf("begin2:%d end2:%d\n", begin2, end2);//修正if (end1 >= n){end1 = n - 1;begin2 = n;end2 = n - 1;}else if (begin2 >= n){///修成一个不存在的区间begin2 = n;end2 = n - 1;}else if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){nums[j++] = a[begin1++];}else{nums[j++] = a[begin2++];}}if (begin1 > end1){while (begin2 <= end2){nums[j++] = a[begin2++];}}if (begin2 > end2){while (begin1 <= end1){nums[j++] = a[begin1++];}}}//记住加left!memcpy(a , nums, sizeof(int) *n);gap *= 2;}free(nums);
}

3 非递归但不梭哈实现法

好消息是,不拷贝,前两个就可以不用修正边界了,直接break出去

只需修正第二个区间的右边界即可

//归一部分拷一部分
void MergeSortNonR(int* a, int n)
{int* nums = (int*)malloc(sizeof(int) * n);int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){//归并int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;printf("begin1:%d end1:%d\n", begin1, end1);printf("begin2:%d end2:%d\n", begin2, end2);//修正if (end1 >= n || begin2 >= n){break;}else if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){nums[j++] = a[begin1++];}else{nums[j++] = a[begin2++];}}if (begin1 > end1){while (begin2 <= end2){nums[j++] = a[begin2++];}}if (begin2 > end2){while (begin1 <= end1){nums[j++] = a[begin1++];}}memcpy(a +i , nums+i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
free(nums);
}

复杂度分析

归并排序的时间复杂度也是标准的O(n*lgn)


空间复杂度来源于开辟的新数组,为O(n)


归并排序还可以用作外排序,大家感兴趣的话,可以再了解一下~


八、其他排序的介绍

计数排序

计数排序又称鸽巢原理,是对哈希直接定址法的应用

操作为:

1、统计相同元素个数

2、根据统计结果将序列回收回原来的序列

既然是模仿哈希,那么原理是这样的

思考一下元素的大致范围,开一个大的哈希表(数组),通过定址法将带牌序列的元素映射至

哈希表,最后再从哈希表提取元素即可

例:

{6,1,2,1,9,3,3,2,2,8}

我们直接开一个大小为10的数组就够了

然后遍历数组,直接定址,再对应下标值加1即可

遍历之后是这样的

下来就简单,遍历哈希表排序即可


看似很简单的计数排序,我们要考虑一些别的问题

如果序列是这样

{100,101,101,103,109,120},我们再从0开始定址就有些浪费了

所以我们可以统计最大,最小值,就能最大程度的节省空间了

需要注意的是,如果序列含有负数,我们的排序也可以解决

那代码是这样的~


void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 1; i < n; ++i){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}int range = max - min + 1;int* countA = (int*)malloc(sizeof(int) * range);if (countA == NULL){perror("malloc fail\n");return;}memset(countA, 0, sizeof(int) * range);// 计数for (int i = 0; i < n; i++){countA[a[i] - min]++;}// 排序int j = 0;for (int i = 0; i < range; i++){while (countA[i]--){a[j++] = i + min;}}free(countA);
}int main()
{int arr[] = { 2,10,3,90,589,184,505.29,8,83 };for (auto e : arr){std::cout << e << " ";}std::cout << std ::endl;int sz = sizeof(arr) / sizeof(arr[0]);CountSort(arr, 0, sz);for (auto e : arr){std::cout << e << " ";}return 0;
}

 复杂度分析

显然,如果序列内的值相差不太大,计数排序的时间复杂度能逼急O(n),非常逆天

其实是O(n+range)

但是由于我们用到了哈希表,那就意味着你得能哈希出一个值来才能排序

所以浮点数肯定是寄了,但是字符串可以(字符串哈希)。 


空间复杂度是O(range)


桶排序

桶排序(Bucket Sort)是一种基于分布的排序算法,特别适用于数据分布比较均匀的情况。它的基本思想是将数据分成若干个桶(Bucket),然后分别对每个桶中的数据进行排序,最后再将各个桶中的数据按顺序合并,得到最终的有序数据。

桶排序的基本步骤:

  1. 创建桶
    • 根据数据范围创建一定数量的桶,每个桶代表一个区间范围。
  2. 将元素分配到桶中
    • 遍历输入数据,将每个数据根据其值分配到对应的桶中。
  3. 对每个桶内的数据排序
    • 对每个桶内的数据单独进行排序(可以使用任何排序算法,通常使用快速排序或插入排序)。
  4. 合并桶中的数据
    • 最后按顺序将各个桶中的数据合并成一个整体,即完成排序

复杂度分析

时间复杂度:

  1. 最好情况: O(n+k)O(n + k)O(n+k)

    • 如果输入数据均匀分布到桶中,且每个桶内的数据可以使用一个高效的排序算法(如插入排序)来进行排序,桶排序的时间复杂度接近线性,等于 O(n)O(n)O(n)。
    • 此外,每个桶的排序时间是与桶内元素数量有关的。如果使用插入排序,最好情况下每个桶的排序复杂度是 O(1)O(1)O(1),总的时间复杂度为 O(n+k)O(n + k)O(n+k),其中 nnn 是输入数据的数量,kkk 是桶的数量。
  2. 平均情况: O(n+k)O(n + k)O(n+k)

    • 当数据大致均匀分布到桶中时,桶排序的平均时间复杂度与最好情况类似,也是 O(n+k)O(n + k)O(n+k),因为分配数据到桶中的过程是线性的。
  3. 最坏情况: O(n2)O(n^2)O(n2)

    • 最坏情况下,所有数据都被分配到同一个桶中,此时桶排序退化为在单个桶中对 nnn 个元素进行排序。若该桶内使用插入排序或其他 O(n2)O(n^2)O(n2) 时间复杂度的排序算法,则总的时间复杂度为 O(n2)O(n^2)O(n2)。

空间复杂度:

  • 空间复杂度: O(n+k)

这种排序一般没什么用,大家了解一下即可


基数排序​​​​​​​

首先我们要知道,基数排序也是一个和之前不同,即不需要比较、移动的排序

是一种借助一种多关键字的排序对单关键字进行排序的方法

例:

我们的扑克牌就有两种关键字进行排序

一种是花色,♠️,♣️,♦️,♥️

另一种是数字大小,1,2,3,4,5,6,7,8,9,10,J,Q,K

再介绍两个概念

最高位优先(MSD)&最低位优先(LSD)

以扑克牌为例 

MSD为:每个子序列花色相同但数字不同

LSD为:4个1,4个2,4个3……


给一个例子

我们以低位优先为例

由于是低位优先,所以我们先按个位排,

将其遍历,重组回原数组(注意,这波063先进,所以先出063)


下来再排十位


排好百位,这波就结束啦


原理还是很简单的,建一个挂队列的哈希表

然后进行数位次操作

每次操作先分发,再组合

写好的代码就是这样的~ 

#define K 3
#define RADIX 10std::queue<int> Q[RADIX];int GetKey(int value,int k)
{int key = 0;while (k >= 0){key = value % 10;value /= 10;k--;}return key;
}void Distribute(int*arr,int left,int right,int k)
{for (int i = left; i < right; ++i){int key = GetKey(arr[i],k);Q[key].push(arr[i]);}
}void Collect(int*arr)
{int k = 0;for (int i = 0; i < RADIX; ++i){while (!Q[i].empty()){arr[k++] = Q[i].front();Q[i].pop();}}
}void RadixSort(int* arr, int left, int right)
{for (int i = 0; i < K; ++i){//分发Distribute(arr,left,right,i);//组合Collect(arr);}
}int main()
{int arr[] = { 278,10,63,930,589,184,505.269,8,83 };for (auto e : arr){std::cout << e << " ";}std::cout << std ::endl;int sz = sizeof(arr) / sizeof(arr[0]);RadixSort(arr, 0, sz);for (auto e : arr){std::cout << e << " ";}return 0;
}

复杂度分析

假设我们搞了r个队列

那我们的空间复杂度就是O(r)


假设我们要进行k次操作

每次分发和组合的时间复杂度就是(n+r)

所以总体就是O(k(n+r))


九、排序总结

这里我们再介绍一个所谓的稳定性的概念

假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的

相对次序保持不变。

即在原序列中,r[i]=r[j],且r[i]在r[j]之前,在排序之后,如果r[i]仍在r[j]之前,就说明这种排序是稳定的,否则就是不稳定的

说人话就是,假设某个序列有相同的数字,经过排序之后,这些相同的数的顺序不变,就是稳定的

有什么用呢?

假定考试时规定分数相同先交卷的人更牛逼,我们就用得着这个稳定性了

根据这些排序的原理,我们便可推出一个排序是否稳定了

排序名是否稳定?解释
插入排序稳定

显然,如果规定了相等了不往后

挪即可

希尔排序不稳定相同的数可能被分到不同的组
选择排序不稳定每次选到的数不固定
堆排序不稳定每次作为堆顶的元素不确定,而堆顶之下可能会有重复元素
冒泡排序稳定相同不交换就可以做到稳定
快速排序不稳定每次的key值不确定
归并排序稳定
计数排序稳定
基数排序稳定辅助数组元素是队列,可以通过先进先出保证

总结

 做总结,这篇博客我们学习了排序,这将是速通数据结构算法初阶的最后一集,接下来我们将迅速的投入沉淀C++的篇章中

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee侠:今天你交gitee了嘛

相关文章:

速通数据结构与算法第七站 排序

系列文章目录 速通数据结构与算法系列 1 速通数据结构与算法第一站 复杂度 http://t.csdnimg.cn/sxEGF 2 速通数据结构与算法第二站 顺序表 http://t.csdnimg.cn/WVyDb 3 速通数据结构与算法第三站 单链表 http://t.csdnimg.cn/cDpcC 4 速通…...

灵当CRM index.php接口SQL注入漏洞复现 [附POC]

文章目录 灵当CRM index.php接口SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 灵当CRM index.php接口SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技…...

修复: Flux女生脸不再油光满面, 屁股下巴 -- 超实用Comfyui小技巧

ComfyUI上目前最强画图模型公认为Flux. 初次用Flux基础模型画真实的女生时, 和SD比起来, 会觉得画出来细节更多, 更真实. 但是当画多了, 就会觉得画出来的女生总是似曾相识. 仔细观察, 会发现一些共同的特征. 人偏老气, 像30~50的女生. 改了提示词也效果不大. 颧骨凸起, 嘴…...

Actions Speak Louder than Words Meta史诗级的端到端推荐大模型落地

发现好久之前整理的推荐系统被遗忘在了草稿箱&#xff0c;让它出来见见世面。。。后续空了持续更新 文章目录 1.Background2.Related works2.1 典型推荐模型2.1.1 DIN2.1.2 DIEN2.1.3 SIM2.1.4 MMoE2.1.5 其他 2.2. 生成式推荐 3.Method3.1 统一特征空间3.2 重塑召回排序模型3.…...

金智维KRPA之Excel自动化

Excel自动化操作概述 Excel自动化主要用于帮助各种类型的企业用户实现Excel数据处理自动化&#xff0c;Excel自动化是可以从单元格、列、行或范围中读取数据&#xff0c;向其他电子表格或工作簿写入数据等活动。 通过相关命令&#xff0c;还可以对数据进行排序、进行格式…...

哪款宠物空气净化器能有效去除浮毛?希喂、352实测分享

你是否曾经站在家电卖场里&#xff0c;面对琳琅满目的宠物空气净化器产品而感到无所适从&#xff1f;或者在浏览网上商城时&#xff0c;被海量的参数和功能描述搞得头晕眼花&#xff1f;别担心&#xff0c;你不是一个人。在这个科技飞速发展的时代&#xff0c;选择一台既能满足…...

2024.9.28更换启辰R30汽车火花塞

2024.9.28周六汽车跑了11万公里&#xff0c;实在加速肉&#xff0c;起步顿挫&#xff0c;油耗在8个&#xff0c;决定更换火花塞。第一个火花塞要拆掉进气歧管。第二和第三个可以直接换。打开第二个火花塞一看电极都被打成深坑&#xff0c;针电极都被打凸。我有两个旧的火花塞&a…...

2024上海网站建设公司哪家比较好TOP3

判断一家网建公司的好坏&#xff0c;第一是看公司背景&#xff0c;包括成立时间&#xff0c;工商注册信息等&#xff0c;第二可以去看看建站公司做的案例&#xff0c;例如&#xff0c;网站开发、设计、引流等等的以往案例&#xff0c;了解清楚具体的业务流程。 一、公司背景 …...

TDesign组件库+vue3+ts 如何视觉上合并相同内容的table列?(自定义合并table列)

背景 当table的某一列的某些内容相同时&#xff0c;需要在视觉上合并这一部分的内容为同个单元格 如上图所示&#xff0c;比如需要合并当申请人为同个字段的列。 解决代码 <t-table:data"filteredData":columns"columns":rowspan-and-colspan"…...

BACnet协议-(基于ISO 8802-3 UDP)(2)

1、模拟设备的工具界面如下&#xff1a; 2、使用yet another bacnet explorer 用作服务&#xff0c;用于发现设备&#xff0c;界面如下&#xff1a; 3、通过wireshark 抓包如下&#xff1a; &#xff08;1&#xff09;、整体包如下&#xff1a; &#xff08;2&#xff09;、m…...

android 根据公历日期准确节气计算年月日时天干地支 四柱八字

1 年柱 判断当前日期是否超过本年的立春 未超过年份-1 已超过按当前年份计算 2月柱 当前日期是否超过当月的第一个节气 未超过-1 超过当前月份计算 节气对日柱时柱没影响。 获取某年某月第一个节气的准确日期 private int sTerm(int y, int n) {int[] sTermInfo…...

VMware虚拟机连接公网,和WindTerm

一、项目名称 vmware虚拟机连接公网和windterm 二、项目背景 需求1&#xff1a;windows物理机&#xff0c;安装了vmware虚拟机&#xff0c;需要访问公网资源&#xff0c;比如云服务商的yum仓库&#xff0c;国内镜像加速站的容器镜像&#xff0c;http/https资源。 需求2&#xf…...

游戏盾SDK真的能无视攻击吗

游戏盾SDK真的能无视攻击吗&#xff1f;在当今的互联网环境中&#xff0c;游戏行业蓬勃发展&#xff0c;但同时也面临着日益严峻的安全挑战。DDoS攻击、CC攻击、外挂作弊等恶意行为频发&#xff0c;不仅威胁着游戏的稳定性和公平性&#xff0c;也严重影响了玩家的游戏体验。为了…...

【QT】亲测有效:“生成的目标文件包含了过多的段,超出了编译器或链接器允许的最大数量”错误的解决方案

在使用dlib开发人脸对齐功能时&#xff0c;出现了”生成的目标文件包含了过多的段&#xff0c;超出了编译器或链接器允许的最大数量的错误“。 主要功能代码如下&#xff1a; #include <QApplication> #include <QImage> #include <QDebug>#include <dlib…...

什么是 Apache Ingress

Apache Ingress 主要用于管理来自外部的 HTTP 和 HTTPS 流量&#xff0c;并将其路由到合适的 Kubernetes 服务。 容器化与 Kubernetes 是现代云原生应用程序的基础。Kubernetes 的主要职责是管理容器集群&#xff0c;确保它们的高可用性和可扩展性&#xff0c;同时还提供自动化…...

SpringBoot助力墙绘艺术市场创新

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…...

Antlr的使用

概念 ANTLR&#xff08;ANother Tool for Language Recognition&#xff09;是一个强大的解析器生成工具&#xff0c;用于读取、处理、执行或翻译结构化文本或二进制文件。ANTLR通过定义文法&#xff08;grammar&#xff09;来识别、构建和访问语言中的元素。 ANTLR为包括Jav…...

HealChat心理大语言模型 丨OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…...

PyQt5整合爬虫制作图片爬取器-幽络源

前言 本篇教程适合对Python爬虫和Python软件制作感兴趣的小伙伴阅读&#xff0c;看完本篇教程&#xff0c;你将能更深入了解PyQt5与实际功能的整合方式。 1.设计界面 首先在pycharm中创建一个新目录&#xff0c;这里我建立的目录名为爬图片&#xff0c;然后按如图打开Qt设计…...

DC00023基于jsp+MySQL新生报到管理系统

1、项目功能演示 DC00023基于jsp新生报到管理系统java webMySQL新生管理系统 2、项目功能描述 基于jspMySQL新生报到管理系统项目分为学生、辅导员、财务处和系统管理员四个角色。 2.1 学生功能 1、系统登录 2、校园新闻、报到流程、学校简介、在线留言、校园风光、入校须知…...

AdaptIoT——制造业中使用因果关系的自我标签系统

0.概述 论文地址&#xff1a;https://arxiv.org/abs/2404.05976 在许多制造应用中&#xff0c;机器学习&#xff08;ML&#xff09;已被证明可以提高生产率。针对制造业应用提出了一些软件和工业物联网&#xff08;IIoT&#xff09;系统&#xff0c;以接收这些 ML 应用。最近&…...

代码随想录算法训练营Day15

654.最大二叉树 力扣题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 前序递归、循环不变量 class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {return findmax(nums,0,nums.length);}public TreeNode findmax(int[] nums,int lefti…...

Thinkphp/Laravel旅游景区预约系统的设计与实现

目录 技术栈和环境说明具体实现截图设计思路关键技术课题的重点和难点&#xff1a;框架介绍数据访问方式PHP核心代码部分展示代码目录结构解析系统测试详细视频演示源码获取 技术栈和环境说明 采用PHP语言开发&#xff0c;开发环境为phpstudy 开发工具notepad并使用MYSQL数据库…...

SpringCloud学习记录|day1

学习材料 2024最新SpringCloud微服务开发与实战&#xff0c;java黑马商城项目微服务实战开发&#xff08;涵盖MybatisPlus、Docker、MQ、ES、Redis高级等&#xff09; 学redis讲到微服务就停了&#xff0c;nginx也是。 所以嘛&#xff0c;我终于来到微服务了。 复习MyBatisP…...

Elasticsearch讲解

1.Elasticsearch基本知识 1.基本认识和安装 Elasticsearch是由elastic公司开发的一套搜索引擎技术&#xff0c;它是elastic技术栈中的一部分。完整的技术栈包括&#xff1a; Elasticsearch&#xff1a;用于数据存储、计算和搜索 Logstash/Beats&#xff1a;用于数据收集 Kib…...

Linux嵌入式有发展吗,以及对uboot,kernel,rootfs的领悟

工作多年后&#xff0c;对uboot&#xff0c;kernel&#xff0c;rootfs的领悟&#xff0c;总结 上大学时&#xff0c;51单片机&#xff0c;正点原子的stm32&#xff0c;linux arm开发。对uboot&#xff0c;kernel&#xff0c;rootfs的理解云里雾里&#xff0c;感觉自己很懂了 其…...

基于Springboot+Vue的公寓管理系统(含源码+数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 该系统…...

多功能声学气膜馆:承载梦想与希望的舞台—轻空间

在9月29日上午&#xff0c;苏州大学应用技术学院的2024级新生开学典礼暨开学第一课在轻空间建造的多功能声学气膜馆内盛大举行。这一盛典不仅见证了2849名新生的入学&#xff0c;也展示了气膜馆的独特魅力与优越功能。 卓越的声学表现 声学气膜馆采用高性能材料&#xff0c;确保…...

【线程】线程池

线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现&#xff0c;线程池中的线程可以从阻塞队列中获取任务进行任务处理&#xff0c;当线程都处于繁忙状态时可以将任务加入阻塞队列中&#xff0c;等到其它的线程空闲后进行处理。 线程池作用&#xff1a; 1.降…...

输出 / 目录下所有目录文件的大小并排序

使用 du -sh /* 输出 / 目录下所有的目录总大小&#xff0c;看下效果&#xff1a; [rootlocalhost ~]# du -sh /* 0 /bin 110M /boot 0 /dev 32M /etc 12K /home 0 /lib 0 /lib64 0 /media 0 /mnt 0 /opt du: cannot access ‘/proc/2731/task/2731/fd/4’: No such file or …...

wordpress后台登录页面美化/网络推广赚钱

1.真机调试时出现 takinginstallLock 堵塞 解决:这是由于你的设备有程序正在安装,等待安装完成,或重启你的设备 2.Cannot run on the selected destination 在项目的Resources目录下找到info.plist,单击该文件&#xff0c;在Xcode右上角点击“Hide or show the Utilities”按钮…...

天津网站建设如何/郑州网站顾问

Caused by: java.lang.ClassCastException:android.widget.LinearLayout$LayoutParams 最近&#xff0c;在android中用代码动态改变某种布局&#xff08;组件&#xff09;的高度时&#xff0c;会遇到如题所示的类转换异常。上网查了一下&#xff0c;如下所示&#xff1a;Thes…...

男女做那个的小视频网站/郴州网站seo外包

开头 此文希望能给想跳槽和面试朋友一些参考。 金九银十已过&#xff0c;面试的狂热季也已结束&#xff0c;小编也正是选择了在金九十银跳槽&#xff0c;之前在腾讯做了五年Android开发工作&#xff0c;之后感觉公司不一定能继续提供给我想要的发展空间与前景。说白了&#x…...

网站建设与设计教程视频/软文发稿系统

缘起 书说前两篇文章《 十五 ║ Vue前篇&#xff1a;JS对象&字面量&this》和 《 十六 ║ Vue前篇&#xff1a;ES6初体验 & 模块化编程》&#xff0c;已经通过对js面向对象&#xff0c;类和模式封装&#xff0c;ES6新特性等多个角度讲解了Vue入门的一些储备知识&…...

中国核工业华兴建设公司网站/深圳网络推广服务公司

两独立样本Wilcoxon检验&#xff08;也称为Wilcoxon秩和检验或Mann-Whitney检验&#xff09;是一种非参数替代配对双样本t检验&#xff0c;其可以被用于比较样品的两个独立的组。当您的数据不是正态分布时使用。在第九讲中&#xff0c;我们讲到了两独立样本t检验的假设条件是样…...

wordpress 打断点/百度大数据预测平台

C#的Garbage Collector(GC,垃圾回收器)往往让很多程序员产生了对于程序中使用的内存撒手不管的态度。他们会认为既然已经有GC在后台运行了&#xff0c;代码中就不需要多加注意了。事实上GC可以是最好的朋友&#xff0c;也可以是最坏的敌人&#xff0c;完全取决于代码。 ★垃圾…...