排序【数据结构】

文章目录

  • 一、 稳定性
  • 二、排序
    • 1. 插入排序
      • (1) 直接插入排序
      • (2) 希尔排序
    • 2. 选择排序
      • (1) 直接选择排序
      • (2) 堆排序
    • 3. 交换排序
      • (1) 冒泡排序
      • (2) 快速排序
        • ① 普通版快排
        • ② 关于优化快排
        • ③ 快速排序的非递归方式
    • 4. 归并排序
    • 5. 计数排序
  • 三、 总结

一、 稳定性

在计算机科学中,稳定性是指在排序过程中,相等的元素的相对顺序保持不变。也就是说,如果元素a和b在排序之前是相等的,那么在排序之后,a和b的相对顺序应该和排序之前一样;否则不稳定。

二、排序

1. 插入排序

(1) 直接插入排序

直接插入排序是一种简单的排序方法,它的基本操作是将一条记录插入到已经排好序的有序表中,从而得到一个新的、记录数量增1的有序表。

具体操作步骤如下:

  1. 从未排序的序列中选择一个元素,将其插入到已排序序列的合适位置。
  2. 继续从未排序序列中取出下一个元素,然后将其插入到已排序序列的合适位置。
  3. 重复步骤2,直到所有元素都被插入到已排序序列中。

直接插入排序的时间复杂度为O(n ^ 2),其中n为待排序序列的长度。由于每次插入都需要移动元素,所以对于较大的数据集来说,直接插入排序可能效率较低。

直接插入排序是稳定

排升序

这里采用的基本思想是: 先假设第一个元素是有序的,然后从后方进行插入元素,然后再与前面的元素进行比较,当然前提是把要插入的元素临时存放一下(避免元素移动后,给覆盖了)。如果比前面的元素小,就要把前面的元素后移,直到找到比前面的大或者第一个位置,然后把要插入的元素放进去。

如图;
在这里插入图片描述

代码展示

void InsertSort(int* arr,int n) 
{//升序//从末端开始//先假设第一个元素 有序//第二元素从从末端进行插入,与前面的元素比较//使用末端元素,如果比前面的小,前面的元素进行后移//之后再把元素进行插入int i = 0;for (i = 0; i < n-1;i++){int end = i;	int tmp = arr[end + 1];	//临时存放插入的元素while (end >=0){//小于前的,就让前面的元素后移if (tmp < arr[end]) {arr[end + 1] = arr[end];--end;}else {//当大于等于的时候直接跳出循环break;}}//把要插入的元素放进去arr[end + 1] = tmp;}
}

(2) 希尔排序

希尔排序是直接插入排序算法的一种更高效的改进版本,它是插入排序的一种,也称为“缩小增量排序”,因 D.L.Shell 于 1959 年提出而得名。

希尔排序的基本思想是:现将待排序的数组分成多个待排序的子序列,使得每个子序列的元素较少,然后对各个子序列分别进行插入排序,待到整个待排序的序列基本有序的时候,最后在对所有的元素进行一次插入排序。

也就是:分为 预排序 和 插入排序

  • 预排序:这以升序为例,根据间隔(gap)分为多个子序列进行插入排序,排完后,就把较大的数据放在后面,较小的数据放在前面,这样就变为部分有序。
    关于这里的gap的取值:
    gap越大,大的值更快调到后面,小的值更快调到前面,接近有序的速度越慢
    gap越小,跳的越慢,但是越接近有序。gap == 1 相当于插入排序
  • 一次插入排序:一次插入排序后,变成整体有序。

希尔排序是不稳定的,(原因是:相同的数据可能被分在不同的组,前后数据的位置难以控制 ) 希尔排序的时间复杂度取决于间隔序列的选择。理论上,如果间隔序列是逐一减半的,希尔排序的时间复杂度可以接近O(n log n)。但是,如果间隔序列选择不当,可能会导致最坏情况的时间复杂度为O(n^2)。
所以,希尔排序的时间复杂度通常是在O(n log n)和O(n ^ 2)之间,具体取决于间隔序列的选择。希尔排序的时间复杂度约是O(n^1.3)。

如图:
在这里插入图片描述

代码展示

void ShellSort(int* arr ,int n) 
{int gap = n;while(gap > 1){gap = gap / 3 + 1;	//一组子序列中的每个数的间距for (int i = 0; i < n - gap; i++){int end = i;//插入排序,尾插进行排序int tmp = arr[end + gap];	//记录尾部的数据while (end >= 0){// 升序if (tmp < arr[end]){//后面的小于前面的向后移动arr[end + gap] = arr[end];end-=gap;	//注意是子序列}else{//后面的大于前的直接插入break;}}//把排序的数据放进去arr[end + gap] = tmp;}}
}

2. 选择排序

(1) 直接选择排序

基本思想: 每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

需要注意的是,直接选择排序中存在大跨度的数据移动,
是一种不稳定的排序方式
原因是:
在这里插入图片描述

时间复杂度是O(n ^ 2),最好的情况是O(N ^ 2),最坏的情况是O(N ^ 2); 空间复杂度为O(1)。

假设,排升序,这里我们用数组存储数据,那就是要遍历数组进行直接选择排序(选出较小的依次从数组的起始位置开始排)

图1:

因为选择排序要么选出最大的,要么选出最小的。

  • 首先根据数组的个数,来确定数组存放最小值的地方和最大值的地方(升序,数组最左端存放小值[begin],数组最右端存放大值[end])
  • 再使用maxi和mini下标来进行记录一轮中的最大值的下标和最小值的下标
  • 找到下标后,把mini和maxi指向的值与begin和end进行交换
  • 交换后,begin++;end - -; 一轮排好了,缩小数组范围,再进行下一轮遍历。

在这里插入图片描述

代码展示

选择排序,选出大的和选出小的同时进行

//选择排序
//时间复杂度;O(N^2)
//最好的情况:O(N^2)
void SelectSort(int* arr, int n) 
{int begin = 0;int end = n - 1;	//数组最后一个元素的下标while (begin < end){//把大的数放到最右边,小的数放到最左边//不断向中间缩小范围int maxi = begin, mini = begin;for (int i = begin+1; i <= end;i++){if (arr[i] < arr[mini]){//把下标给minimini = i;}if (arr[i] > arr[maxi]){maxi = i;}}//一轮找完后//把大的数放到最右边,小的数放到最左边swap(&arr[begin], &arr[mini]);//上述的swap 就是 把小的值与左端begin进行交换//需要注意的是:当左端begin的下标与maxi(那一趟认为较大的数的下标)相等时,//maxi == begin ,swap较换后把小值放到了begin,而maxi下标也是指向begin//当再把maxi指向的数据放到右边的时候,maxi之前指向的数已经让上面swap给换了,换到mini所指向的值了//所以需要加个判断给换回来if (begin == maxi){maxi = mini;}swap(&arr[end], &arr[maxi]);++begin;--end;}
}

选出小的依次进行排序

//选小进行排序
void SelectSort(int* arr, int n)
{int begin = 0;while (begin < n){int mini = begin;for (int i = begin; i < n; i++){if (arr[i] < arr[mini]){mini = i;}}swap(&arr[begin], &arr[mini]);++begin;}
}

选出大的从后往前放进行排序

//选大进行排序
void SelectSort(int*arr,int n)
{int end = n - 1;while (end >= 0) {int maxi = end;for (int i = end; i >= 0;i--){//寻找较大的下标if (arr[i] > arr[maxi]) {maxi = i;}}swap(&arr[end],&arr[maxi]);--end;}
}

(2) 堆排序

这里以升序为例。我们需要把数据先构建成大堆(根的值最大),然后把堆的根元素与堆最后面一个元素进行交换。然后堆的大小减一。

依次重复上述。直到排完。

堆排序的时间复杂度为O(nlogn),它是不稳定排序算法。
不稳定的原因 例如
在这里插入图片描述

关于堆排序,猛击链接 堆排序 更详细的介绍

代码展示

//堆排序//交换两个数
void swap(int*s1,int*s2) 
{int tmp = *s1;*s1 = *s2;*s2 = tmp;
}//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//先去找根结点的较大的孩子结点int child = 2 * parent + 1;//可能会向下调整多次while (child<size) {//这里使用假设法,先假设左孩子的值最大//如果不对就进行更新if ((child+1 < size)&&a[child] < a[child+1]) {child++;}//根结点与其孩子结点中的较大的一个进行交换if(a[child] > a[parent]) {swap(&a[child],&a[parent]);//更新下标parent = child;child = 2 * parent + 1;}else {break; //调完堆}}
}//堆排序
void HeapSort(int* arr, int n) 
{int i = 0;//使用向下调整算法向上调整,把大的值调到上方。for (i = (n - 1 - 1) / 2; i >= 0;i--){//先找到数组最后端的父结点的下标//父结点的下标减一就是另一个//使用向下调整算法进行调整AdjustDown(arr,n,i);}//进行排序//因为是大堆,所以根结点的值是最值//把最值与堆的最后一个结点进行交换//再把交换后的根节点进行向下调整//然后堆的大小减一//注意end 是从n-1开始的(数组最后一个元素的下标)int end = n-1;while (end > 0) {//swap end = n-1 这表示下标swap(&arr[0],&arr[end]);//adjustdown 函数里面的end是元素的个数,所以不是先--end//所以AdjustDown(arr,end,0);end--;}
}

3. 交换排序

(1) 冒泡排序

冒泡排序的基本思想是:对相邻的元素进行两两比较,顺序相反则进行交换,这样每一次遍历都将最大的元素"浮"到数列的最后,下一次遍历则考虑剩下的元素。通过多次遍历,可以将整个数列排序。

  • 如果是n个数据元素,需要遍历n-1趟,在第一趟排序中,需要比较的次数为n-1次。在第二趟排序中,需要比较的次数为n-2次,以此类推。

代码展示

void BubbleSort(int* arr,int n) 
{//遍历n-1趟for (int i = 0; i < n - 1;i++) {//每一趟交换的次数for (int j = 0; j < n - 1 - i;j++) {//冒泡升序if (arr[j] > arr[j+1]){swap(&arr[j], &arr[j + 1]);}}}
}

冒泡排序总结:

  1. 冒泡排序是稳定的
  2. 是一种交换类排序
  3. 时间复杂度,最坏的情况:O(n^2) ; 最好的情况:可以达到O(n)

最好情况就是,数据本身是有序的,在去遍历一遍时通过记录值来判断有序,当整体有序直接跳出循环

如下方:

void BubbleSort(int* arr,int n) 
{for (int i = 0; i < n-1;i++){//使用记录值bool exchange = false;for (int j = 0; j < n - 1 - i;j++) {if (arr[j] > arr[j+1]){swap(&arr[j],&arr[j+1]);//发生了交换,说明不是有序exchange = true;}}//当上方循环一遍,exchange仍为false,说明整体有序直接跳出循环//这样最好的情况时间复杂度可以达到O(N)if (exchange == false)break;}
}

(2) 快速排序

快速排序是一种被广泛运用的排序算法,它的基本原理是分治法。具体来说,就是通过趟排序将待排序的序列分割为左右两个子序列,左边的子序列中所有数据都比右边子序列中的数据小,然后对左右两个子序列继续进行排序,直到整个序列有序。

  1. 选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot)。
  2. 分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大。
  3. 递归地对左右两个子序列进行快速排序,直至序列有序。

快速排序的平均时间复杂度为O(nlogn),最坏情况下为O(n^2)。快速排序在处理大量数据时效率较高。但是快速排序是不稳定的,快速排序在处理相同元素时,它们的相对位置可能会改变。例如,对序列 9 6 9 1 2 3 进行排序,第一趟排序后变为 3 6 9 1 2 9,原本第一个9的位置发生了变化,因此快速排序是不稳定的。

所以这里先 根据基准 key 来进行分左右子序列, 基准的选择先固定选数组的第一个元素,然后找左右下标,从数组左边找大,数组右边找小。左右两边找到后进行交换,当left == right,在把基准进行交换。

① 普通版快排
void QuickSort(int* arr, int begin,int end) 
{//区间只有一个结点或者区间不存在,停止递归if (begin >= end)return;int keyi = begin;int left = begin, right = end;while (left<right){//右边找小while (left<right && arr[right] >= arr[keyi]){right--;}//左边找大while (left<right && arr[left]<= arr[keyi]) {left++;}//找到后进行交换swap(&arr[left],&arr[right]);}//交换基准swap(&arr[left],&arr[keyi]);//递归左右子序列keyi = left;//[begin,keyi-1]keyi[keyi+1,end]QuickSort(arr,begin,keyi-1);QuickSort(arr,keyi+1,end);
}
② 关于优化快排
  1. 三数取中法:在开始(begin)、中间(mid)、最后(end)这三个位置中选出中间大(即中位数)的那个数确定下标为midi。

进行数据的交换,把中间大的数据仍然交换放到数组的左端,作为基准进行排序。时间复杂度为O(n*logn)
代码展示

void swap(int* s1 ,int* s2) 
{int tmp = *s1;*s1 = *s2;*s2 = tmp;
}void PrintSort(int* arr, int n)
{for (int i = 0; i < n; ++i){printf("%d ",arr[i]);}
}int GetMidi(int* arr, int begin,int end) 
{int midi = (begin + end) / 2;if (arr[begin] < arr[midi]) {if (arr[midi] < arr[end]){return midi;}else if (arr[begin] > arr[end]){return begin;}elsereturn end;}else {if (arr[midi] > arr[end]){return midi;}else if (arr[begin] < arr[end]){return begin;}else {return end;}}
}void QuickSort(int* arr,int begin,int end) 
{//只有一个结点的时候直接进行返回if (begin >= end)return;//优化部分,三数取中int midi = GetMidi(arr,begin,end);//把中位数放到基准的位置swap(&arr[midi],&arr[begin]);//选基准int left = begin;int right = end;int keyi = begin;while (left < right) {//数组的右边的数据先走// 左右两边向中间进行聚拢//右边走遇到比基准小的值就停下,左边走遇到大于基准的就停下//都停下后,交换左右两边的数while (left<right && arr[right] >= arr[keyi]){right--;}while (left<right && arr[left] <= arr[keyi]){left++;}//找到后就就进行交换swap(&arr[left],&arr[right]);}//当上方一轮遍历完后//即left == right//在相遇点与基准keyi进行交换swap(&arr[keyi],&arr[left]);keyi = left;//进行递归快排左右子序列//[begin,keyi-1]keyi[keyi+1,end]QuickSort(arr,begin,keyi-1);QuickSort(arr,keyi+1,end);
}
  1. 小区间优化法

    	//当排序元素数据量很小的时候直接调用插入排序if (end - begin+1 < 10) {InsertSort(arr+begin, end - begin + 1);}else{... //调用快速排序}
    
  2. 挖坑法
    因为快排在进行完单趟排序后,然后去递归左右子区间的单趟排序,这针对之前hoare版本的单趟排序进行优化

    首先,在区间的开头左边的值记录一下(key),然后用坑位下标(holei)记录此数据。左边就是坑位;右边找小填到左边的坑中,之后,右边就形成了新的坑;左边找大填到右边的坑中;当begin == end 就结束,最后把key记录的值填到最后的坑里面去。
    在这里插入图片描述

    //挖坑法
    int QuickSortPart2(int* arr, int begin, int end) 
    {//三数取中int midi = GetMidi(arr, begin, end);swap(&arr[begin], &arr[midi]);	//交换基准值int key = arr[begin];int holei = begin;while (begin < end)	//相遇停止{//右边找小while (begin < end && arr[end] >= key){--end;}//找到后填到左边的坑中,这样就把比基准小的值放到左边arr[holei] = arr[end];//此时右边形成新的坑位了,更新坑位下标holei = end;//左边找大while (begin<end && arr[begin] <= key){++begin;}//找到填到右边的坑arr[holei] = arr[begin];holei = begin;}//相遇把key记录的值放进去arr[holei] = key;return holei;
    }
    
  3. 前后指针法

    单趟进行优化,首先,一个指针指向开头(prev),一个指针指向其后(cur)。当cur遇到比key小的值,++prev交换prev和cur位置的值,再++cur。当cur遇到比key大的值,++cur。
    如图:
    在这里插入图片描述

    //双指针法(前后指针法)
    int QuickSortPart3(int* arr, int begin, int end) 
    {//三数取中int midi = GetMidi(arr, begin, end);swap(&arr[begin], &arr[midi]);	//交换基准值int keyi = begin;//首先一个指针指向开头,另一个指向其后面那个int prev = begin;int cur = prev + 1;//while (cur <= end )//{//	//当cur遇到比key大的值,++cur//	if (arr[cur] > arr[keyi])//	{//		++cur;//	}//	else//	{//		//cur遇到比key小的值,++prev,交换prev和cur位置的值,再++cur//		++prev;//		swap(&arr[prev],&arr[cur]);//		++cur;//	}//}//swap(&arr[prev], &arr[keyi]);//return prev;//优化一//while ( cur <= end) //{//	if (arr[cur] < arr[keyi])//	{//		++prev;//		swap(&arr[prev],&arr[cur]);//	}//	++cur;//}//swap(&arr[prev],&arr[keyi]);//return prev;//优化二while (cur <= end) {if (arr[cur] < arr[keyi] && ++prev != cur)swap(&arr[prev],&arr[cur]);++cur;}swap(&arr[prev],&arr[keyi]);return prev;
    }
    
③ 快速排序的非递归方式

递归改为非递归我们需要借助 数据结构的栈来进行实现。借助出栈来取出每一次的区间进行处理。分出两段区间,处理完之后的入栈条件是:区间合理(左<=右),当左>右不入栈。当不再有区间进栈后(栈为空时) 结束。
如图:
在这里插入图片描述

代码展示

//非递归快速排序
void QuickSortNonR(int* arr, int begin, int end) 
{Stack st;InitStack(&st);//栈 后进先出//区间压栈 //要想让区间的左值先出,选要后进栈PushStack(&st,end);PushStack(&st,begin);while (!EmptyStack(&st)){//出栈 找出区间的左右下标,进行处理int left = TopStack(&st);PopStack(&st);int right = TopStack(&st);PopStack(&st);// 使用了双指针法进行了一次快速排序int keyi = QuickSortPart3(arr, left, right);//左右子区间 [left,keyi-1] keyi [keyi+1,right]//区间合理(左<=右),当左>右不入栈。当不在有区间进栈后(栈为空时) 结束//左区间满足条件if (left < keyi - 1){//后进先出//左子区间的右下标进栈PushStack(&st, keyi - 1);//左子区间的左下标进栈PushStack(&st, left);}//右区间满足条件if (keyi+1 < right) {//后进先出//右子区间的右下标进栈PushStack(&st,right);//右子区间的左下标进栈PushStack(&st,keyi+1);}}DestroyStack(&st);
}

4. 归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效且稳定的排序算法,主要思想是将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。依次归并达到整体有序。
若将两个有序表合并成一个有序表,称为二路归并。具体过程为:

  • 申请空间(辅助空间),使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。
  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置。
  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。
  • 重复步骤3直到某一指针超出序列尾。将另一序列剩下的所有元素直接复制到合并序列尾。

在这里插入图片描述

代码展示

//归并排序子函数
void _MergeSort(int* arr,int begin,int end,int* tmp) 
{//当区间只有一个结点或区间不存在,停止递归,返回调用的地方if (begin >= end)return;//找到区间中间点//分成左右两个区间进行递归//[begin,mid][mid+1,end]int mid = (begin + end) / 2;_MergeSort(arr,begin,mid,tmp);_MergeSort(arr,mid+1,end,tmp);//有序归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;//归并时,有一边结束就结束了,两个都没有结束就继续int i = begin;	//注意 归并到指定的位置去while (begin1 <= end1 && begin2 <= end2) {//取小数据尾插到tmp数组if (arr[begin1] <= arr[begin2]){tmp[i++] = arr[begin1++];}else{tmp[i++] = arr[begin2++];}}//可能存在一边先结束,那么另一边就继续while (begin1 <= end1){tmp[i++] = arr[begin1++];}while (begin2 <= end2){tmp[i++] = arr[begin2++];}//之后再把数组拷贝回去memcpy(arr+begin,tmp+begin,sizeof(int)*(end-begin+1));}//归并排序
void MergeSort(int* arr,int n) 
{//归并排序,先分解成单个有序,再进行有序归并//借助辅助空间O(n)int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){perror("malloc fail");return;}//开辟成功调用子函数,进行归并_MergeSort(arr,0,n-1,tmp);free(tmp);
}

归并排序总结:

  1. 时间复杂度是:O( n*log(n) ) ,因为需要借助辅助空间,所以空间复杂度为O( n )
  2. 归并排序是稳定的

归并排序的非递归方式
思路:先一个一个(单独一个默认有序)数据归并成两个有序,然后再两个两个归并成四个有序,… ,直到整体有序
如图:
在这里插入图片描述
代码展示

void MergeSortNR(int* a, int n) 
{//非递归方式实现归并排序//申请辅助空间int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){perror("malloc fail");return;}//二路归并:两个有序子序列 归并为一个有序的序列int gap = 1;	//先单个元素有序while (gap < n) {//一层归并int i = 0;for (i = 0;i<n;i+= 2*gap){int begin1 = i, end1 = i+gap-1;int begin2 = i+gap, end2 = i+2*gap-1;if (end1 >= n || begin2 >= n) {break;}if (end2 >= n){end2 = n - 1;}int j = begin1;	//注意 这里是从i 因为归并不是一定全在下标0开始的,也有可能从右边的子数组开始的while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[j++] = a[begin1++];elsetmp[j++] = a[begin2++];}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//一组归并后,立即拷贝memcpy(a+i,tmp+i,sizeof(int)*(end2-i+1));	//注意放在里面}gap *= 2;}free(tmp);
}

5. 计数排序

计数排序的思想:

  • 统计每个数据出现的次数
  • 将统计出现的次数放入一个临时数组(采用相对映射的方式-将数据的最小值放到第一个数据)

代码展示

void CountSort(int* a, int n)
{//排升序//第一步 相对映射的的方式找出最小值和最大值int i = 0;int min = a[0], max = a[0];for (i = 1; i < n;i++) {if (a[i] <= min)min = a[i];if (a[i] > max)max = a[i];}//第二步 根据最大和最小值确定相对映射数组的取值范围,并动态开辟临时数组int range = max - min + 1;int* count = (int*)calloc(range,sizeof(int));if (count == NULL){perror("calloc fail");return;}//第三步 统计数据出现的次数i = 0;for(i = 0;i<n;i++){count[a[i] - min]++;	// 相对映射}//第四步 将临时数组记录的数据有序放入数组a中//注意 因为是相对映射,所以这里要加上min(放入数组a的时候)int j = 0;i = 0;for (j = 0; j < range;j++) {//因为相同数据可能会出现多次//没有出现一次的元素,临时数组存放0while(count[j]--){//有数据就放入a数组中a[i++] = j + min;}}//第五步 释放内存free(count);
}

计数排序的优缺点:

  • 缺点:不适合分散的数据,更适合集中的数据,数据类型只适合整数
  • 优点:效率极高,O(N+cntN)
  • 时间复杂度:O(N+range)
  • 空间复杂度:O(range)

三、 总结

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
直接插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(n*log(n))~O(n^2)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(n*log(n))O(n*log(n))O(n*log(n))O(1)不稳定
归并排序O(n*log(n))O(n*log(n))O(n*log(n))O(n)稳定
快速排序O(n*log(n))O(n*log(n))O(n^2)O(log(n))~O(n)不稳定

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

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

相关文章

81.网游逆向分析与插件开发-背包的获取-装备栏数据结构的逆向分析

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;自动化助手显示物品数据-CSDN博客 然后游戏中有弓箭&#xff0c;弓箭有数量&#xff0c;可以作为突破口&#xff0c;也可以使用物品id 获取弓的方式 获取弓箭的方式 然后搜索250 然后搜索出一个 然后…

大数据 - Spark系列《一》- 分区 partition数目设置详解

目录 &#x1f436;3.2.1 分区过程 &#x1f436;3.2.2 SplitSize计算和分区个数计算 &#x1f436;3.2.3 Partition的数目设置 1. &#x1f959;对于数据读入阶段&#xff0c;输入文件被划分为多少个InputSplit就会需要多少初始task. 2. &#x1f959;对于转换算子产生的…

千帆杯AI原生应用开发挑战赛,每期10万,等你而战!

大赛介绍 随着大模型技术的飞速发展&#xff0c;2024年将会成为AI原生应用爆发的元年&#xff0c;引领千行百业的创新变革。在这一时代背景下&#xff0c;百度智能云重磅推出千帆杯AI原生应用开发挑战赛&#xff0c;旨在激发广大开发者的创意潜能&#xff0c;推动AI原生应用在…

vit细粒度图像分类(五)TransFC学习笔记

1.摘要 细粒度图像具有不同子类间差异小、相同子类内差异大的特点。现有网络模型在处理过程中存在特征提取能力不足、特征表示冗余和归纳偏置能力弱等问题&#xff0c;因此提出一种改进的 Transformer图像分类模型。 首先&#xff0c;利用外部注意力取代原 Transformer模型中的…

数据分析入门指南:用 Python 开启数据之旅

文章目录 前言发现宝藏为什么选择 Python 进行数据分析&#xff1f;准备工作数据分析基础1. 数据加载2. 数据探索3. 数据清洗4. 数据可视化 探索更多可能性好书推荐总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。…

C语言王道第八周一题

Description 初始化顺序表&#xff08;顺序表中元素为整型&#xff09;&#xff0c;里边的元素是 1,2,3&#xff0c;然后通过 scanf 读取一个元素&#xff08;假如插入的是 6&#xff09;&#xff0c;插入到第 2 个位置&#xff0c;打印输出顺序表&#xff0c;每个 元素占 3 个…

vuepress搭建个人博客以及部署

vuepress&#xff0c;Vue 驱动的静态网站生成器&#xff0c;以 Markdown 为中心的项目结构&#xff0c;以最少的配置帮助你专注于写作。 vuepress官网 vuepress存在很多主题&#xff0c;也可以自定义设计主题&#xff0c;上传npm使用 这里采用vuepress-theme-hope主题模板进行制…

AIGC专题:2024年金融业生成式AI应用报告

今天分享的是AIGC系列深度研究报告&#xff1a;《AIGC专题&#xff1a;2024年金融业生成式AI应用报告》。 &#xff08;报告出品方&#xff1a;度小满&#xff09; 前言 毫无疑问&#xff0c;生成式人工智能是2023年全球最具影响力的创新科技&#xff0c;它代表着一种范式转…

赛氪荣获“2023天津高新技术企业大会支持单位”

1月23日上午&#xff0c;2023天津市高新技术企业大会新闻发布会在天开高教科技园核心区综合服务中心召开&#xff0c;市高企协以及来自高校、企业、社会组织等80余人现场参会。 大会组委会秘书长张博航介绍到&#xff1a;“本次大会将实现自开办以来的多个首次&#xff0c;首次…

GoogLeNet模型详解

模型介绍 GoogLeNet是谷歌工程师设计的深度神经网络结构&#xff0c;于2014年在ImageNet比赛中取得了冠军。它的设计特点在于既有深度&#xff0c;又在横向上拥有“宽度”&#xff0c;并采用了一种名为Inception的核心子网络结构。这个网络名字中的“GoogLeNet”是对LeNet的致…

sql优化的方法

目录 一、准备数据 1.1、创建表结构 1.2、创建存储过程 二、索引介绍 2.1、类型介绍 2.2、建立索引 2.3、建立复合索引 2.4、查看所有建立的索引 2.5、删除索引 三、EXPLAIN分析参数说明 四、SQL优化案例 4.1、避免使用SELECT * 4.2、慎用UNION关键字 4.4、避免使…

响应式架构设计:性能更高更快的架构模式(框架部分后续再完善)

文章目录 一、初识响应式1、什么是Reactive&#xff08;响应式&#xff09;2、响应式编程&#xff08;Reactive Programming&#xff09;的含义3、响应式编程的特点4、响应式编程的主要模式5、响应式编程的核心元素&#xff08;1&#xff09;流&#xff08;2&#xff09;传播变…