排序【数据结构】

文章目录

  • 一、 稳定性
  • 二、排序
    • 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.mzph.cn/news/654934.shtml

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

相关文章

CHS_03.2.3.2_2+进程互斥的硬件实现方法

CHS_03.2.3.2_2进程互斥的硬件实现方法 知识总览中断屏蔽方法TestAndSet指令Swap指令 知识回顾 进程互斥的四种软件实现方法 知识总览 这个小节我们会介绍另外的三种进程互斥的硬件实现方法 那么 这个小节的学习过程当中 大家需要注意理解各个方法的原理 并且要稍微的了解各个…

【Uni-App】Vue3如何使用pinia状态管理库与持久化

安装插件 pinia-plugin-unistorage 引入 // main.js import { createSSRApp } from "vue"; import * as Pinia from "pinia"; import { createUnistorage } from "pinia-plugin-unistorage";export function createApp() {const app create…

SpringBoot不同的@Mapping使用

文章目录 一、介绍二、使用 一、介绍 一般Mapping类注解在Spring框架中用于将HTTP请求映射到对应的处理器方法。它们各自对应于不同类型的HTTP方法&#xff0c;主要用于RESTful Web服务中。以下是每个注解的作用&#xff1a; GetMapping: 用于映射HTTP GET请求到处理器方法。通…

Life is Strange 奇异人生汉化指南

奇异人生汉化指南 引言&#xff1a;在搜索引擎上看了许多的攻略&#xff0c;都无法得到指向性明确的安装步骤&#xff0c;其中最令人不解的分别为汉化包与汉化包的安装地址&#xff0c;以下会以汉化包获取与汉化包安装地址两个维度来确保汉化的正确&#xff0c;以及在最终附上…

爬虫学习笔记-get请求获取豆瓣电影排名多页数据★★★★★

1. 导入爬虫需要使用的包 import urllib.request import urllib.parse 2.创建请求函数 def create_request(page): # 定义不变的url部分 base_url https://movie.douban.com/j/chart/top_list?type5&interval_id100%3A90&action& # 根据规律定义data拼接url …

算法沉淀——二分查找(leetcode真题剖析)

算法沉淀——二分查找 01.二分查找02.在排序数组中查找元素的第一个和最后一个位置03.搜索插入位置04.x 的平方根05.山脉数组的峰顶索引06.寻找峰值07.寻找旋转排序数组中的最小值08.LCR 173. 点名 二分查找&#xff08;Binary Search&#xff09;是一种在有序数组中查找特定元…

【算法专题】二分查找(入门)

&#x1f4d1;前言 本文主要是二分查找&#xff08;入门&#xff09;的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日…

幻兽帕鲁服务器怎么收费?4核16G配置

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

前言:穿越迷雾,探索C语言指针的奇幻之旅

各位少年&#xff0c;大家好&#xff0c;我是博主那一脸阳光&#xff0c;今天给大家分享指针&#xff0c;内存和地址的使用&#xff0c;以及使用。 前言&#xff1a; 在编程的世界中&#xff0c;若论灵活多变、深邃神秘的角色&#xff0c;非“指针”莫属。如同哈利波特手中的魔…

深度学习快速入门--7天做项目

深度学习快速入门--7天做项目 0. 引言1. 本文内容2. 深度学习是什么3. 项目是一个很好的切入点4. 7天做项目4.1 第一天&#xff1a;数据整理4.2 第二天&#xff1a;数据处理4.3 第三天&#xff1a;简单神经网络设计4.4 第四天&#xff1a;分析效果与原因4.5 第五天&#xff1a;…

基于SpringBoot的玩具租赁系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

【原创课程】KUKA机器人与S7-1200进行Profinet通讯

一、KUKA机器人与S7-1200进行Profinet通讯 1、硬件配置 ①硬件配置 名称 型号 数量 PLC S7_1217C 1个 机器人 KUKA_KR-210 1台 2、机器人一侧参数配置 ①添加备选软件包 首先&#xff0c;从KUKA机器人控制柜中将KOP备选软件包拷贝出来&#xff0c;然后在”WorkVi…

【lodash.js】非常好用高性能的 JavaScript 实用工具库,防抖,深克隆,排序等

前言&#xff1a;lodash是一款前端必须要知道的js库&#xff0c;它里面提供了许多常用的功能和实用的工具函数 基本上我参与的项目中都有lodash&#xff0c;只能说lodash太强大了&#xff0c;lodash.js 提供了超过 300 个实用的工具函数&#xff0c;涵盖了很多常见的编程任务 l…

【ascii码对照表】

计算机各种表 ascii码表BCD码&#xff08;Binary-Coded Decimal‎&#xff09;有权码-8421码有权码-2421码有权码-5421码无权码-余3码无权码-余3循环码无权码-格雷码 ascii码表 BCD码&#xff08;Binary-Coded Decimal‎&#xff09; BCD码也称二进码十进数 BCD用4位二进制数来…

数字图像处理(实践篇)三十六 OpenCV-Python 使用ORB和BFmatcher对两个输入图像的关键点进行匹配实践

目录 一 涉及的函数 二 实践 ORB(Oriented FAST and Rotated BRIEF)是一种特征点检测和描述算法,它结合了FAST关键点检测和BRIEF描述子。ORB算法具有以下优势: ①实时性:能够在实时应用中进行快速的特征点检测和描述。 ②

Windows系统云服务器自定义域名解析导致网站无法访问怎么解决?

本文九河云介绍Windows实例内部自定义域名解析与本地网络域名解析不一致导致无法访问网站的问题描述、问题原因和解决方案。 问题描述 在Windows实例内部通过浏览器无法访问某网站&#xff0c;但在其他设备上可以正常访问&#xff0c;排查发现Windows实例内部自定义域名解析与…

网络安全科普:SSL证书保护我们的网上冲浪安全

当我们在线上愉快冲浪时&#xff0c;各类网站数不胜数&#xff0c;但是如何判定该站点是安全还是有风险呢&#xff1f; 当当当&#xff0c;SSL数字证书登场&#xff01;&#xff01; SSL证书也称为数字证书&#xff0c;是一种用于保护网站和用户之间通信安全的加密协议。由权…

Python基础语法——数据输入(input语句)

一、引言 在Python编程中&#xff0c;数据的输入是一个基础且重要的环节。Python的input()函数允许用户从控制台输入数据&#xff0c;是Python中获取用户输入的主要方式。本文将详细解析input()函数的工作原理&#xff0c;以及如何处理和验证用户输入。 二、input()函数的工作…

Mac安装nvm,安装多个不同版本node,指定node版本

一.安装nvm brew install nvm二。配置文件 touch ~/.zshrc echo export NVM_DIR~/.nvm >> ~/.zshrc echo source $(brew --prefix nvm)/nvm.sh >> ~/.zshrc三.查看安装版本 nvm -vnvm常用命令如下&#xff1a;nvm ls &#xff1a;列出所有已安装的 node 版本nvm…

【网络】传输层TCP协议 | 三次握手 | 四次挥手

目录 一、概述 2.1 运输层的作用引出 2.2 传输控制协议TCP 简介 2.3 TCP最主要的特点 2.4 TCP连接 二、TCP报文段的首部格式 三、TCP的运输连接管理 3.1 TCP的连接建立(三次握手) 3.2 为什么是三次握手&#xff1f; 3.3 为何两次握手不可以呢&#xff1f; 3.4 TCP的…