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

深入刨析数据结构之排序(上)

目录

1.内部排序

1.1概述

1.2插入排序

1.2.1其他插入排序

1.2.1.1 折半插入排序

1.2.1.2 2-路插入排序

1.3希尔排序

1.4快速排序

1.4.1起泡排序

1.4.2快速排序

1.4.2.1hoare版本

1.4.2.2挖坑版本

1.4.2.3前后指针版本

1.4.2.4优化版本

1.4.2.4.1小区间插入排序优化

1.4.2.4.2三路划分优化

1.4.2.5非递归版本


1.内部排序

1.1概述

    排序(Sorting)是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。
    为了查找方便,通常希望计算机中的表是按关键字有序的。因为有序的顺序表可以采用查找效率较高的折半查找法,其平均查找长度为log₂(n+1)-1,而无序的顺序表只能进行顺序查找,其平均查找长度为(n+1)/2。又如建造树表(无论是二叉排序树或 B-树)的过程本身就是一个排序的过程。因此,学习和研究各种排序方法是计算机工作者的重要课题之一。
    为了便于讨论,在此首先要对排序下一个确切的定义:
假设含 n个记录的序列为
                                                                  {R₁,R₂,…, Rn} 
其相应的关键字序列为
                                                                   {K₁,K₂,…, Kn}
需确定1,2,…,n的一种排列p₁,p₂,…, pn,使其相应的关键字满足如下的非递减(或非递增)关系
                                                                K₁<=K₂<=…<=Kn
即使第一个序列成为一个按关键字有序的序列
                                                                {Rp₁,Rp₂,…, Rpn} 
这样一种操作称为排序。
    上述排序定义中的关键字 Ki可以是记录 Rᵢ(i=1,2,…,n)的主关键字,也可以是记录 Rᵢ的次关键字,甚至是若干数据项的组合。若Ki是主关键字,则任何一个记录的无序序列经排序后得到的结果是唯一的;若Ki是次关键字,则排序的结果不惟一,因为待排序的记录序列中可能存在两个或两个以上关键字相等的记录。假设Ki=Kj(1≤i≤n,1≤j≤n,i≠j),且在排序前的序列中Ri领先于Rj(即i<j)。若在排序后的序列中Rᵢ仍领先于Rj,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中Rj领先于Ri,则称所用的排序方法是不稳定的。(对不稳定的排序方法,只要举出一组关键字的实例说明它的不稳定性即可。)
    由于待排序的记录数量不同,使得排序过程中涉及的存储器不同,可将排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程;另一类是外部排序,指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,因此需要做特殊处理来进行排序...

1.2插入排序

    直接插入排序(Straight Insertion Sort)是一种最简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。
例如,已知待排序的一组记录的初始排列如下所示:①
                                      R(49),R(38),R(65),R(97),R(76),R(13),R(27),R(49),… 
假设在排序过程中,前4个记录已按关键字递增的次序重新排列,构成一个含4个记录的有序序列
                                                       {R(38),R(49),R(65),R(97)} 
现要将第一个式子中第5个(即关键字为76的)记录插入上述序列,以得到一个新的含5个记录的有序序列,则首先要在第二个式子的序列中进行查找以确定R(76)所应插入的位置,然后进行插入。假设从R(97)起向左进行顺序查找,由于65<76<97,则R(76)应插入在R(65)和R(97)之间,从而得到下列新的有序序列
                                                  {R(38),R(49),R(65),R(76),R(97)} 
称从第二式到第三式的过程为一趟直接插入排序。一般情况下,第i趟直接插入排序的操作为:在含有i-1个记录的有序子序列 r[1.. i-1]中插入一个记录r[i]后,变成含有i个记录的有序子序列r[1..i]。

由于上述过程较为抽象,我们来看看实际应用中插入排序的动图:

步骤及思路:(按照升序排序)

1.将数组分为有序与无序的两个部分

2.将循环设置为0至n-2(这里不能等于n-1的原因是无序的部分不能没有数据)

3.每次循环将有序部分的最后一个下标记录,并将有序部分的下一个,也就是无序部分的第一个保存在tmp变量当中

4.到前面已经有序的部分寻找比tmp小的元素,先跟有序部分的最后一个进行比较,如果tmp大,则直接退出循环,否则从有序部分的最后一个开始,往后移一个单位长度,一直到找到比tmp小的元素,将这个元素后移一个,tmp放入

下面来看代码实现:

void InsertSort(int arr[], int n)
{int i = 0;for (i = 0; i < n - 1; i++){int youxu = i;int tmp = arr[youxu + 1];while (youxu >= 0){//一直找到小于youxu为止if (tmp < arr[youxu]){arr[youxu + 1] = arr[youxu];youxu--;}else{break;}}arr[youxu + 1] = tmp;}
}

   从上面的叙述可见,直接插入排序的算法简洁,容易实现,那么它的效率如何呢?
   从空间来看,它只需要一个记录的辅助空间,从时间来看,排序的基本操作为:比较两个关键字的大小和移动记录。先分析一趟插入排序的情况。for 循环的次数取决于待插记录的关键字与前i一1个记录的关键字之间的关系。若L.r[i]key<L.r[1].key,则内循环中,待插记录的关键字需与有序子序列L.r[1..i-1]中i-1个记录的关键字和tmp中的关键字进行比较,并将L.r[1..i-1]中 i-1个记录后移。
   则在整个排序过程(进行n一1趟插入排序)中,当待排序列中记录按关键字非递减有序排列(以下称之为“正序”)时,所需进行关键字间比较的次数达最小值n-1,记录不需移动;反之,当待排序列中记录按关键字非递增有序排列(以下称之为“逆序”)时,总的比较次数达最大值(n+2)(n-1)/2,记录移动的次数也达最大值(n+4)(n-1)/2,若待排序记录是随机的,即待排序列中的记录可能出现
的各种排列的概率相同,则我们可取上述最小值和最大值的平均值,作为直接插人排序时所需进行关键字间的比较次数和移动记录的次数,约为n^2/4。由此,直接插入排序的时间复杂度 O(n^2)

1.2.1其他插入排序


   从上一节的讨论中可见,直接插入排序算法简便,且容易实现。当待排序记录的数量n很小时,这是一种很好的排序方法。但是,通常待排序序列中的记录数量n很大,则不宜采用直接插入排序。由此需要讨论改进的办法。在直接插入排序的基础上,从减少“比较”和“移动”这两种操作的次数着眼,可得下列各种插入排序的方法。

1.2.1.1 折半插入排序

   由于插入排序的基本操作是在一个有序表中进行查找和插入,那么这个查找就可以考虑利用”折半查找”来实现,这里就不多介绍代码原理,仅进行有关讨论,折半插入排序仅减少了关键字间的比较次数,而记录的移动次数不变,因此,折半插入排序的时间复杂度仍然为O(n^2)。

1.2.1.2 2-路插入排序

   2-路插入排序是在折半插入排序的基础上再改进之,其目的是减少排序过程中移动记录的次数,但为此需要n个记录的辅助空间。具体做法是:另设一个和L.r同类型的数组d,首先将L.r[1]赋值给d[1],并将 d[1]看成是在排好序的序列中处于中间位置的记录,然后从L.r中第2个记录起依次插入到d[1]之前或之后的有序序列中。先将待插记录的关键字和dL1]的关键字进行比较,若L.r[i].key<d[1].key,则将L.r[i]门插入到d[1]之前的有序表中。反之,则将L.r[i][插入到d[1]之后的有序表中。在实现算法时,可将d看成是一个循环向量,并设两个指针first 和final分别指示排序过程中得到的有序序列中的第一个记录和最后一个记录在d中的位置。

   具体代码就不详细撰写,我们来讨论这类插入排序的性能:2-路插入排序中,移动记录的次数约为n^2/8,时间复杂度为O(n^2),且只能减少移动记录的次数,而不能绝对避免移动记录。并且,当L.r[1]是待排序记录中关键字最小或最大的记录时,2-路插入排序就完全失去它的优越性。

1.3希尔排序

   希尔排序(Shell’s Sort)又称“缩小增量排序”(Diminishing Increment Sort),它也是种属插入排序类的方法,但在时间效率上较前述几种排序方法有较大的改进。
   从对直接插入排序的分析得知,其算法时间复杂度为O(n^2),但是若待排记录序列为“正序”时,其时间复杂度可提高至 O(n)。由此可设想,若待排记录序列按关键字“基本有序”,即序列中具有下列特性
                                                    L.r[i].key<max{L.r[j].key} (1<=j<i)
的记录较少时,直接插入排序的效率就可大大提高,从另一方面来看,由于直接插人排序算法简单,则在n值很小时效率也比较高。希尔排序正是从这两点分析出发对直接插入排序进行改进得到的一种插入排序方法。
   它的基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。


先看一下希尔排序的过程:初始关键字序列如图10.5的第1行所示。首先将该序列分成5个子序列   

                                           {R1,R6},{R2,R7},⋯,{R5,R10}

如图10.5的第2行至第6行所示,分别对每个子序列进行直接插入排序,排序结果如图10.5的第7行所示,从第1行的初始序列得到第7行的序列的过程称为一趟希尔排序。然后进行第二趟希尔排序,即分别对下列3个子序列:

                                         {R1,R4,R7,R10},{R2,R5,R8} 和 {R3,R6,R9}

进行直接插入排序,其结果如图 10.5 的第11行所示,最后对整个序列进行一趟直接插入排序。至此,希尔排序结束,整个序列的记录已按关键字非递减有序排列。

由于上述过程较为抽象,我们来看看实际应用中希尔排序的动图:

步骤及思路:(按照升序排序)

1.先要了解清楚插入排序的基本原理,希尔排序只是对插入排序进行改进,将相邻两个元素进行比较改为相邻gap的元素进行比较

2.首先完成内循环,我们将循环改为0-(n-2*gap),并将每次循环的增量改为gap,其他也是在插入排序的基础之上稍加改动,将有序部分最后一个距离gap的元素保存在tmp中,再进行跨度为gap的寻找较小元素

3.外循环我们对gap进行调整,将gap的初始值设为n,每次循环时/=2,缩小跨度,直到gap为1,循环结束,排序完成,注意:这里必须确保gap最后一定要为1,来完成对基本有序的数组的最后一次排序

下面来看代码实现:

void ShellSort(int* arr, int n)
{int gap = n;//gap可以改变,不停变化,本质还是插入while (gap > 1){gap /= 2;int j = 0;for (j = 0; j < gap; j++){int i = 0;for (i = j; i < n - gap; i += gap){int youxu = i;int tmp = arr[youxu + gap];while (youxu >= 0){if (tmp < arr[youxu]){arr[youxu + gap] = arr[youxu];youxu -= gap;}else{break;}}arr[youxu + gap] = tmp;}}//或者/*int i = 0;for (i = j; i < n - gap; i++){int youxu = i;int tmp = arr[youxu + gap];while (youxu >= 0){if (tmp < arr[youxu]){arr[youxu + gap] = arr[youxu];youxu -= gap;}else{break;}}arr[youxu + gap] = tmp;}*/}
}

我们来看希尔排序的性能:

这是出自数据结构C语言版(严蔚敏)中对希尔排序性能的分析,我们可以进行参考,实际上,希尔排序的时间复杂度呈现的是先上升后下降的趋势,需要复杂的数学分析来证明希尔排序的具体复杂度,我们只需了解平均复杂度即可。

1.4快速排序

1.4.1起泡排序

   这一节讨论一类借助“交换”进行排序的方法,其中最简单的一种就是人们所熟知的起泡排序(又称冒泡排序) (Bubble Sort),起泡排洋的过程很简单。首先将第一个记录的关键字和第二个记录的关键学进行比较,若为逆序(即L.r[1].key>L.r[2].key),则将两个记录交换之,然后比较第二个和第三个记录的关键字。以此类推,直至第n-1个记录和第n个记录的关键字进行过比较为止。上述过程叫做第一趟起泡排序,其结果使得关键字最大的记录被安置到最后一个记录的位置上。然后进行第二趟起泡排序,对前n-1个记录进行同样操作,其结果是使关键字次大的记录被安置到第n一1个记录的位置上。一般地,第i趟起泡排序是从L.r[1]到L.r[n-i+1]依次比较相邻两个记录的关键字,并在“逆序”时交换相邻记录,其结果是这n-i+1个记录中关键字最大的记录被交换到第n-i+1的位置上。整个排序过程需进行k(1<=k<n)趟起泡推序,显然,判别起泡排定结束的条件应该是“在一趟排序过程中没有进行过交换记录的操作(这可以作为我们优化冒泡排序的入手点)”图10.6展示了起泡排序的一个实例。从图中可见,在起泡排序的过程中,关键字较小的记录好比水中气泡逐趟向上飘浮,而关键字较大的记录好比石块往下沉,每一趟有一块“最大”的石头沉到水底。

由于上述过程较为抽象,我们来看看实际应用中冒泡排序的动图:

步骤及思路:(按照升序进行排序)

1.设置外层循环,使内层循环每次的循环终点依次减少(这里也可以代表循环起点依次增加),一直到n-2,(这里不设为n-1的原因是还要留下一个元素进行比较和交换)

2.设置内层循环,使数组的第j个元素和第j+1个元素进行比较,直到达到循环终点,也可以设置为从循环起点开始第j个元素与第j+1个元素比较,直到到达数组末尾

3.在内层循环中如果发现第j个元素比第j+1个元素大,就进行交换

4.优化:设置一个布尔类型的变量,如果在内层循环中进行过交换就将变量改为1,否则为0,这样可以判断内层循环所代表的数组的部分是否有序,如果已经有序就跳出外层循环,排序完成

下面来看代码实现:

void BubbleSort(int* arr, int n)
{int i = 0;for (i = 0; i < n - 1; i++){int j = 0;bool judge = false;for (j = 0; j < n - 1 - i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j + 1];arr[j + 1] = arr[j];arr[j] = tmp;judge = true;}}if (judge == false){break;}}
}

分析起泡排序的效率,容易看出,若初始序列为“正序”序列,则只需进行一趟排序,在排序过程中进行n-1次关键字间的比较,且不移动记录;反之,若初始序列“逆序”序列,则需进行n-1躺排序,需进行n(n-1)/2次比较,并作等数量级的记录移动,因此,总的时间复杂度为O(n^2)。

1.4.2快速排序

     快速排序(Quick Sort)是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
     假设待排序的序列为

                                                    {L.r[s],L.r[s+1],…,L.r[t ])

首先任意选取一个记录(通常可选第一个记录 L.r[s])作力枢轴(或支点)(pivot),然后按下述原则重新排列其余记录:
将所有关键字较它小的记录都安置在它的位置之前,将所有关键字较它大的记录都安置在它的位置之后。由此可以该“枢轴”记录最后所落的位置;作分界线,将序列

                                                           {L.r[s],⋯,L.r[t]}

分割成两个子序列

                     {L.r[s],L.r[s+1] ⋯ L Li-1]}和{L.r[i+1],L.r[i+2],⋯,Lr[t] }

这个过程称做一趟快速排序(或一次划分)。
    一趟快速排序的具体做法是:附设两个指针begin和end,它们的初值分别为left和right,设枢轴记录的关键字为key) 首先从right所指位置起向前搜索找到第一个关键字小于key的记录和枢轴记录互相交换,然后从left所指位置起向后搜索,找到第一个关键字大于 key的记录和枢轴记录互相交换,重复这两步直至left=right为止

1.4.2.1hoare版本

霍尔的快速排序也是最开始快速排序的版本,让我们先看快速排序的动图了解原理:

步骤及原理:(按照升序排序)

1.先将数组首地址,开始元素下标和末尾元素下标传入,将左下标赋给begin变量保存,右下标赋给end变量保存,定义key变量来保存left元素下标

2.在left小于right的前提下,我们从右边开始找比key对应的数组元素还要小的元素,从左边开始找比key对应的数组元素还要大的元素,找到之后我们将这两个元素交换,反复这个过程,设为外层循环,直到left不再小于right,外层循环结束

3.将key对应的元素与此时left和right共同指向的那个元素交换,将key设为left或right,调用递归两次(前面我们保存了begin和end,这里key设定完毕后将数组分为了两段,分别为begin~key-1以及key+1~end),请注意这里key对应的数组元素已经排在了正确的位置上,并且确保数组左边的元素比这个元素小,右边的比这个元素大,为递归调用提供了前提。

下面是代码实现:

//这里其实是两路划分,当数组接近有序时,就会退化成n方
void QuickSort1(int* arr, int left, int right)
{if (left >= right){return;}int begin = left;int end = right;//随机数取数/*int randi = left + (rand() % (right - left));swap(&arr[randi], &arr[left]);*///三路取中int midi = GetMidNumi(arr, left, right);swap(&arr[midi], &arr[left]);int key = left;while (left < right){//左边做key让右边先走,相遇位置比key要小,或者就是key的位置//右边做key让左边先走,相遇位置比key要大while (left < right && arr[right] >= arr[key]){right--;}while (left < right && arr[left] <= arr[key]){left++;}swap(&arr[right], &arr[left]);}swap(&arr[key], &arr[left]);key = left;QuickSort1(arr, begin, key - 1);QuickSort1(arr, key + 1, end);
}

优化和说明:细心的读者会发现,代码中多了GetMidNumi这个函数,这其实是在对基础版的快速排序进行优化,

1.选择数组第一个元素下标为key并不是绝对的,我们同样可以选择最后一个元素下标为key,前者先让右指针right寻找比关键字小的元素,再让左指针left寻找比关键字大的元素,当两个指针相遇时指向的元素一定比关键字小(这里是霍尔经过大量数据验证出来的结果,不做证明)如果选择后者,那么就要先让左指针left寻找比关键字大的元素,再让右指针right寻找比关键字小的元素,当两个指针相遇时指向的元素一定比关键字大

2.这里还可以使用其他方法来提高快速排序的性能,可以用到一个rand函数来随机选取数组区间内的一个数,来与第一个元素进行交换,这样关键字的下标并没有改变,改变的是关键字的内容,这样可以提升快排的效率

3.上面的随机取数其实并不推荐,我更为推荐三数取中这种方法,即选择数组区间内首个,中间,末尾这三个元素中第二大的元素,因此我完成了GetMidNumi函数的编写:

int GetMidNumi(int* arr, int left, int right)
{int mid = (left + right) / 2;if (arr[mid] > arr[left]){if (arr[right] > arr[mid]){return mid;}else if (arr[right] < arr[left]){return left;}else{return right;}}else{if (arr[right] < arr[mid]){return mid;}else if (arr[left] < arr[right]){return left;}else{return right;}}
}

补充说明:快速排序的递归类似于二叉树遍历中的前序(先序)遍历,具体的递归过程就不做过多赘述,如果对递归调用还存在疑问的读者,不妨去看看本栏目中二叉树的相关遍历知识,介绍了双重递归调用的过程并附有递归展开图。

1.4.2.2挖坑版本

这种快速排序实则是对基础班的改进,让我们先通过动图了解原理:

步骤及原理:(按照升序排序)

1.先将数组首地址,开始元素下标和末尾元素下标传入,将左下标赋给begin变量保存,右下标赋给end变量保存,定义key变量来保存left元素(注意这里不同于基础版,必须保存元素,如果保存下标会随着坑位改变填入新的元素而改变),定义hole来表示坑位下标

2.在left小于right的前提下,我们从右边开始找比key对应的数组元素还要小的元素,从左边开始找比key对应的数组元素还要大的元素,找到之后我们分别将元素“填入”坑中,将元素赋值给坑,反复这个过程,设为外层循环,直到left不再小于right,外层循环结束

3.将坑内元素赋值为保存的key,将key设为left或right,调用递归两次。

下面是代码实现:

//挖坑法:将key设为坑,找到值后将数丢到坑里
void QuickSort2(int* arr, int left, int right)
{if (left >= right){return;}int begin = left;int end = right;int hole = left;int key = arr[left];while (left < right){//左边做key让右边先走,相遇位置比key要小,或者就是key的位置//右边做key让左边先走,相遇位置比key要大while (left < right && arr[right] >= key){right--;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] <= key){left++;}arr[hole] = arr[left];hole = left;}arr[hole] = key;key = left;QuickSort2(arr, begin, key - 1);QuickSort2(arr, key + 1, end);
}
1.4.2.3前后指针版本

前后指针版本是快速排序中最推荐的版本,较为简洁,我们来看一下动图理解原理:

步骤及思路:(按照升序排序)

1.设置两个前后指针,分别是cur和prev,cur保存的是数组第二个元素下标,prev保存的是第一个元素下标,定义key来保存第一个元素的下标,begin和end来保存left和right

2.设置一个外层循环,当cur<=right时循环进行,当cur指向的值比key指向的值要小时,让prev先往后走一个单位,再让cur于prev交换,直到cur>right循环结束

3.将prev对应的元素与key对应的元素交换,将key赋值prev,两次调用递归

下面来看代码实现:

//前后指针版本的快速排序
void QuickSort3(int* arr, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int prev = left;int cur = left + 1;int key = left;while (cur <= right){if (cur <= right && arr[cur] < arr[key]){//找到比key小的就先让prev往前走,再交换,cur最后++prev++;swap(&arr[prev], &arr[cur]);}cur++;}swap(&arr[key], &arr[prev]);key = prev;QuickSort3(arr, begin, key - 1);QuickSort3(arr, key + 1, end);
}
1.4.2.4优化版本

如果想要继续优化快速排序,我们还可以采用以下两种方法:

1.4.2.4.1小区间插入排序优化

在数组区间递归调用时长度小于设定值时,可以采用插入排序的方法直接来对这一个小区间元素进行排序,这样可以起到一定的优化作用,由于插入排序已经在前面做了有关介绍,就不做过多赘述,直接调用:

//对快速排序进行小区间优化
void QuickSort4(int* arr, int left, int right)
{if (left >= right){return;}if (right - left + 1 > 5){int begin = left;int end = right;int key = left;while (left < right){while (left < right && arr[right] >= arr[key]){right--;}while (left < right && arr[left] <= arr[key]){left++;}swap(&arr[right], &arr[left]);}swap(&arr[key], &arr[left]);key = left;QuickSort1(arr, begin, key - 1);QuickSort1(arr, key + 1, end);}else{InsertSort(arr + left, right - left + 1);}
}
1.4.2.4.2三路划分优化

由于过程稍微有点复杂,我们配上动图来便于理解:

步骤及思路:(按照升序排序)

1.定义left指向数组第一个元素,定义right指向数组最后一个元素,定义cur指向第二个元素,定义key记录数组第一个元素的数据

2.设置循环,如果cur<=right循环继续,否则循环结束

3.将cur指向的元素与key比较,如果前者小于后者,那么交换left和cur的元素,left向后一个单位,cur向后一个单位,如果两者相等,那么cur向后一个单位,如果前者大于后者,那么交换right和cur的元素,right向前一个单位,cur不动,因为如果right对应元素的值和key需要继续比较

4.和上面大多数快排一样,两次调用递归

下面是代码实现:

//三路划分,大大提高快排的效率
void QuickSort5(int* arr, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int key = arr[left];int cur = left + 1;while (cur <= right){if (arr[cur] < key){swap(&arr[cur], &arr[left]);left++;cur++;}else if (arr[cur] == key){cur++;}else{swap(&arr[right], &arr[cur]);right--;}}QuickSort5(arr, begin, left - 1);QuickSort5(arr, left + 1, end);
}
1.4.2.5非递归版本

上面所述的都是递归调用的方式实现快速排序,这里我们尝试使用非递归的方式去实现快速排序:

步骤及思路:(按照升序排序)

1.我们定义一个栈结构(具体实现方式本栏也有提到,这里直接调用),先让记录数组最后一个元素下标的right入栈,再让记录数组首个元素下标的left入栈

2.设置循环,当栈不为空时我们取出一个栈顶元素,让这个元素出栈,作为排序数组的begin,再取出一个栈顶元素,出栈后作为排序数组的end

3.调用GetKey函数来获得key的取值,如果key+1<end就让数组的左右下标入栈,如果begin<key-1就让数组的左右下标入栈,直到栈内所有元素都被取出,排序结束

补充:

1.其实在调用GetKey函数获得key的取值时就相当于快速排序的一趟排序

2.这里先入栈右边下标再入栈左边下标遵循了栈的特征,FILO(First In Last Out)先入后出,先让右边入栈,那么取出的第一个就是左边下标,这样方便了我们对排序数组begin和end的定义

下面是代码实现:连通GetKey一并给出!

int GetKey(int* arr, int left, int right)
{int begin = left;int end = right;int key = left;while (left < right){if (left < right && arr[right] >= arr[key]){right--;}if (left < right && arr[left] <= arr[key]){left++;}swap(&arr[right], &arr[left]);}swap(&arr[key], &arr[left]);key = left;return key;
}
void QuickSortNonR(int* arr, int left, int right)
{Stack st;StackInit(&st);StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int key = GetKey(arr, begin, end);//在获得key的时候其实就在排序if (key + 1 < end){StackPush(&st, end);StackPush(&st, key + 1);}if (begin < key - 1){StackPush(&st, key - 1);StackPush(&st, begin);}}StackDestroy(&st);
}

来看看书上关于快速排序性能的讲解:

书中提到,这里优化的方法有三数取中,我们在上面也已经实现,同时,时间复杂度的数量级为O(nlogn),从时间上看,快速排序的平均性能优于前面讨论过的各种排序方式,从空间上看,前面讨论的各种方法,除2-路插入排序,都只需要一个记录的附加空间即可,但快速排序需要一个栈空间来实现递归,即使不使用递归,也需要额外使用栈结构,若每一趟排序都将记录序列均匀地分成长度相接近的两个子序列,则栈的最大深度为[logn]+1,这其实是快速排序的空间复杂度,为O(logn)

欲知其他排序,请看下文...

相关文章:

深入刨析数据结构之排序(上)

目录 1.内部排序 1.1概述 1.2插入排序 1.2.1其他插入排序 1.2.1.1 折半插入排序 1.2.1.2 2-路插入排序 1.3希尔排序 1.4快速排序 1.4.1起泡排序 1.4.2快速排序 1.4.2.1hoare版本 1.4.2.2挖坑版本 1.4.2.3前后指针版本 1.4.2.4优化版本 1.4.2.4.1小区间插入排序优…...

Java - 日志体系_Apache Commons Logging(JCL)日志接口库_桥接Logback 及 源码分析

文章目录 PreApache CommonsApache Commons ProperLogging &#xff08;Apache Commons Logging &#xff09; JCL 集成logbackPOM依赖配置文件 logback.xml使用 源码分析jcl-over-slf4j 的工作原理1. LogFactory 的实现2. SLF4JLogFactory 和 Log 的实例化过程3. SLF4JLog 和 …...

力扣刷题:栈和队列OJ篇(下)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 1.括号匹配问题&#xff08;1&#xff09;题目…...

QT:控件属性及常用控件(1)------核心控件及属性

一个图形化界面上的内容&#xff0c;不需要我们直接从零去实现 QT中已经提供了很多的内置控件&#xff1a; 按钮&#xff0c;文本框&#xff0c;单选按钮&#xff0c;复选按钮&#xff0c;下拉框等等。。。。。 文章目录 1.常用控件属性1.1 enabled1.2 geometry1.2.1 geometry…...

【juc】Lock锁和AQS的继承关系

目录 1. 说明2. Lock接口与AQS的关系2.1 Lock接口2.2 AQS&#xff08;AbstractQueuedSynchronizer&#xff09; 3. ReentrantLock与AQS的具体联系3.1 ReentrantLock的实现3.2 AQS在ReentrantLock中的作用 1. 说明 1.Lock锁和AQS&#xff08;AbstractQueuedSynchronizer&#x…...

自学记录鸿蒙API 13:实现多目标识别Object Detection

起步&#xff1a;什么叫多目标识别&#xff1f; 无论是生活中的动物识别、智能相册中的场景分类&#xff0c;还是工业领域的检测任务&#xff0c;都能看到多目标识别的身影。这次&#xff0c;我决定通过学习HarmonyOS最新的Object Detection API&#xff08;API 13&#xff09…...

BOC调制信号matlab性能仿真分析,对比功率谱,自相关性以及抗干扰性

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…...

C# 事件机制

C# 事件机制详解&#xff1a;从概念到实践 在 C# 中&#xff0c;事件机制是处理对象间通信的重要方式&#xff0c;尤其是在 GUI 应用程序&#xff08;如 WPF、WinForms&#xff09;中&#xff0c;事件用于响应用户交互&#xff08;如按钮点击、鼠标移动等&#xff09;。本文将…...

使用 Python 实现随机中点位移法生成逼真的裂隙面

使用 Python 实现随机中点位移法生成逼真的裂隙面 一、随机中点位移法简介 1. 什么是随机中点位移法&#xff1f;2. 应用领域 二、 Python 代码实现 1. 导入必要的库2. 函数定义&#xff1a;随机中点位移法核心逻辑3. 设置随机数种子4. 初始化二维裂隙面5. 初始化网格的四个顶点…...

GPT分区 使用parted标准分区划分,以及相邻分区扩容

parted 是一个功能强大的命令行工具&#xff0c;用于创建和管理磁盘分区表和分区。它支持多种分区表类型&#xff0c;如 MBR&#xff08;msdos&#xff09;、GPT&#xff08;GUID Partition Table&#xff09;等&#xff0c;并且可以处理大容量磁盘。parted 提供了一个交互式界…...

【Triton-ONNX】如何使用 ONNX 模型服务与 Triton 通信执行推理任务上-Triton快速开始

模型部署系列文章 前置-docker 理解:【 0 基础 Docker 极速入门】镜像、容器、常用命令总结前置-http/gRPC 的理解: 【HTTP和gRPC的区别】协议类型/传输效率 /性能等对比【保姆级教程附代码】Pytorch (.pth) 到 TensorRT (.plan) 模型转化全流程【保姆级教程附代码(二)】Pytor…...

问题记录:[FATAL] [1735822984.951119148]: Group ‘manipulator‘ was not found.

前言&#xff1a;最近仿照UR5手眼标定的例程&#xff0c;在新的机械臂上进行手眼标定&#xff0c;还准备用easy_hand手眼标定包。将机器人功能包导入到工作空间后进行编译运行&#xff0c;启动launch文件&#xff1a; roslaunch easy_handeye eye_to_hand_CR7_calibration.lau…...

SpringCloudAlibaba实战入门之Sentinel服务降级和服务熔断(十五)

一、Sentinel概述 1、Sentinel是什么 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 一句话概括:sentinel即Hystrix的替代品,官网: https://sentinelguard.io/zh…...

Scrum中敏捷项目经理(Scrum Master)扮演什么角色?

敏捷开发模式已经逐渐被主流的软件研发团队所接受&#xff0c;其中Scrum是最具代表性的敏捷方法之一。Scrum框架中有三个核心角色&#xff1a;Product Owner&#xff08;PO&#xff09;、Scrum Master&#xff08;SM&#xff09;和Development Team&#xff08;DT&#xff09;。…...

SpringMVC(四)响应

目录 数据处理及跳转 1. 结果跳转方式 ①.ModelAndView ②.ServletAPI 1、通过HttpServletResponse进行输出 2、通过HttpServletResponse实现请求转发 3、通过HttpServletResponse实现重定向 ③.SpringMVC 1.直接输出 2.请求转发 3.重定向 2.ResponseBody响应json数…...

操作系统之文件系统

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

Android SPRD 工模测试修改

设备有两颗led灯&#xff0c;工模测试需全亮 vendor/sprd/proprietories-source/factorytest/testitem/led.cpp -13,6 13,10 typedef enum{#define LED_BLUE "/sys/class/leds/blue/brightness"#define LED_RED …...

C语言与操作系统

学习C语言有助于理解计算机底层原理和操作系统的工作方式 C语言自诞生以来&#xff0c;就与计算机底层操作紧密相连。作为一门高级编程语言&#xff0c;C语言提供了对硬件直接控制的能力&#xff0c;同时保留了结构化编程的特性&#xff0c;这使得它成为编写操作系统、编译器和…...

信息安全管理:网络安全

1 网络的定义和特征 1.1 网络的定义 &#xff08;根本懒得说。。你们自己wiki吧&#xff09; 网络的用处 What is a network…Devices in a network…LAN, WAN and InternetworksWhat do networks do for you… Sharing resourcesUse/share applications 1.2 网络的特征 Ch…...

python-leetcode-轮转数组

189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09; class Solution:def rotate(self, nums: List[int], k: int) -> None:"""Do not return anything, modify nums in-place instead."""n len(nums)k % n # 如果 k 大于 n&#xff0c;…...

Windows上安装Go并配置环境变量(图文步骤)

前言 1. 本文主要讲解的是在windows上安装Go语言的环境和配置环境变量&#xff1b; Go语言版本&#xff1a;1.23.2 Windows版本&#xff1a;win11&#xff08;win10通用&#xff09; 下载Go环境 下载go环境&#xff1a;Go下载官网链接(https://golang.google.cn/dl/) 等待…...

【JS】期约的Promise.all()和 Promise.race()区别

概述 Promise.all() 和 Promise.race() 都是 JavaScript 中处理多个异步操作的 Promise 方法&#xff0c;但它们的行为和返回结果有所不同。 Promise.all()和Promise.race() 1. Promise.all() Promise.all() 接受一个由多个 Promise 实例组成的可迭代对象&#xff08;例如数…...

【Linux】信号处理

一、Linux系统信号 1、常见的系统信号 常见的Linux系统信号 信号值描述1SIGHUP挂起&#xff08;hang up&#xff09;进程2SIGINT中断进&#xff08;interrupt&#xff09;程3SIGQUIT停止&#xff08;stop&#xff09;进程9SIGKILL无条件终止&#xff08;terminate&#xff09;…...

Diffusion Transformer(DiT)——将扩散过程中的U-Net换成ViT:近频繁用于视频生成与机器人动作预测(含清华PAD详解)

前言 本文最开始属于此文《视频生成Sora的全面解析&#xff1a;从AI绘画、ViT到ViViT、TECO、DiT、VDT、NaViT等》 但考虑到DiT除了广泛应用于视频生成领域中&#xff0c;在机器人动作预测也被运用的越来越多&#xff0c;加之DiT确实是一个比较大的创新&#xff0c;影响力大&…...

144:vue+leaflet 使用canvas绘制不同方向、不同颜色的模仿船只三角形

作者: 还是大剑师兰特 ,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,canvas,echarts等技术开发,欢迎加微信(gis-dajianshi),一起交流。 查看本专栏目录 - 本文是第 144个示例 文章目录…...

c# 快捷键模块

文章目录 命名空间和类类成员静态成员 静态方法GenerateHotkeyIdWndProcGetWindowHandleAndSourceRegisterUnregister 静态方法&#xff08;外部调用&#xff09;RegisterHotKey 和 UnRegisterHotKey 委托HotKeyCallbackHandler 枚举HotkeyModifiers 应用示例 using System; us…...

npm install 安装选项 -d -s -g

在使用 npm install 时&#xff0c;-d、-g 和 -s 是不同的选项&#xff0c;它们分别代表不同的安装模式或行为。以下是它们的详细解释&#xff1a; 1. -d&#xff1a;--save-dev 含义&#xff1a;将包安装为开发依赖&#xff08;devDependencies&#xff09;。使用场景&#…...

【每日学点鸿蒙知识】worker线程数量、判断用户是否进行权限决定、图片上传类型错误、request锁释放时机、H5问题

1、HarmonyOS 怎么判断worker线程创建了几个&#xff1f; 因为有数量限制&#xff0c;所以想查询当前的worker数量&#xff0c;避免创建失败&#xff0c;还有&#xff0c;是同时运行的worker数量有限制&#xff0c;还是同一个应用能创建的worker线程有限制 1、查询当前的work…...

0xc0000020错误代码怎么处理,Windows11、10坏图像错误0xc0000020的修复办法

“0xc0000020”是一种 Windows 应用程序错误代码&#xff0c;通常表明某些文件缺失或损坏。这可能是由于系统文件损坏、应用程序安装或卸载问题、恶意软件感染、有问题的 Windows 更新等原因导致的。 比如&#xff0c;当运行软件时&#xff0c;可能会出现类似“C:\xx\xxx.dll …...

智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之7 附件(文档)

为AI聊天工具添加一个知识系统 Part1 人性化&去中心化 前情提要 这一次我们暂时抛开前面对“智能工厂的软件设计”的考虑--其软件智能 产品就是 应用程序。直接将这些思维方式和方法论 运用在其具体应用场景中。本文是其中的一个应用场景。 今天用了 一个新的AI助手工具…...

智能化人才招聘系统是怎样的?

随着企业规模的扩大和业务范围的拓展&#xff0c;人才招聘成为了企业发展的关键环节。然而&#xff0c;市面上的人才招聘系统琳琅满目&#xff0c;质量参差不齐&#xff0c;许多企业发现&#xff0c;并非所有系统都能满足他们的需求&#xff0c;特别是智能化的需求。今天&#…...

memcached的基本使用

memcached是一种基于键值对的内存数据库&#xff0c;一般应用于缓存数据&#xff0c;提高数据访问速度&#xff0c;减轻后端数据库压力。 安装 这里以Ubuntu为例&#xff0c;其他系统安装方法请看官方文档。 sudo apt-get update sudo apt-get install memcached启动 memca…...

【Unity3d】C#浮点数丢失精度问题

一、float、double浮点数丢失精度问题 Unity3D研究院之被坑了的浮点数的精度&#xff08;一百零三&#xff09; | 雨松MOMO程序研究院 https://segmentfault.com/a/1190000041768195?sortnewest 浮点数丢失精度问题是由于大部分浮点数在IEEE754规范下就是无法准确以二进制…...

机器学习中回归预测模型中常用四个评价指标MBE、MAE、RMSE、R2解释

在机器学习中&#xff0c;评估模型性能时常用的四个指标包括平均绝对误差&#xff08;Mean Absolute Error, MAE&#xff09;、均方误差&#xff08;Mean Squared Error, MSE&#xff09;、均方根误差&#xff08;Root Mean Squared Error, RMSE&#xff09;和决定系数&#xf…...

2412d,d语言中写汇编

原文 嗨,我只是想共享该要点,它展示了如何在ASM中用D编写你好. D中写汇编非常方便!这是我写的: extern(C) int main() {auto hip "hello D\n".ptr;size_t len 8;//write(1,消息,长度)asm {mov RDX, len;//缓冲长度mov RSI, hip;//消息缓冲mov EDI, 1;//Stdout文描…...

急救复试英语口语第一招:confidence

自信与内容是成功的关键 复试是每个考生面临的一道重要关卡&#xff0c;尤其是对于需要进行英语口语测试的同学而言&#xff0c;如何在有限的时间内展示自己的英语能力和综合素质&#xff0c;成为了一个关键问题。今天我们将从两个方面为大家分享应对复试英语口语的技巧&#…...

深入理解 pytest Fixture 方法及其应用

在 Python 自动化测试领域&#xff0c;pytest 是当之无愧的王者。提到 pytest&#xff0c;不得不说它的一大核心功能——Fixture。Fixture 的强大&#xff0c;让复杂的测试流程变得井井有条&#xff0c;让测试代码更加灵活和可复用。 那么&#xff0c;pytest 的 Fixture 究竟是…...

python实现自动登录12306抢票 -- selenium

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 python实现自动登录12306抢票 -- selenium 前言其实网上也出现了很多12306的代码&#xff0c;但是都不是最新的&#xff0c;我也是从网上找别人的帖子&#xff0c;看B站视频&…...

@RestControllerAdvice注解

RestControllerAdvice 是 Spring 4 引入的一个组合注解&#xff0c;它结合了 ControllerAdvice 和 ResponseBody&#xff0c;专门用于处理 RestController 类型的控制器中的全局异常、全局数据绑定和全局模型属性等问题。在 Spring Boot 中&#xff0c;RestControllerAdvice 通…...

04-微服务02

我们将黑马商城拆分为5个微服务&#xff1a; 用户服务 商品服务 购物车服务 交易服务 支付服务 由于每个微服务都有不同的地址或端口&#xff0c;相信大家在与前端联调的时候发现了一些问题&#xff1a; 请求不同数据时要访问不同的入口&#xff0c;需要维护多个入口地址…...

离线语音识别+青云客语音机器人(幼儿园级别教程)

1、使用步骤 确保已安装以下库&#xff1a; pip install vosk sounddevice requests pyttsx3 2、下载 Vosk 模型&#xff1a; 下载适合的中文模型&#xff0c;如 vosk-model-small-cn-0.22。 下载地址&#xff1a; https://alphacephei.com/vosk/models 将模型解压后放置在…...

Llama 3 后训练(三)

目录 4. 后训练 4.1 建模 图表解读 4.1.1 聊天对话格式 4.1.2 奖励建模 4.1.3 监督微调&#xff08;Supervised Finetuning&#xff09; 4.1.4 直接偏好优化&#xff08;Direct Preference Optimization&#xff09; 4.1.5 模型平均&#xff08;Model Averaging&#x…...

Cobbler+kickstart实现批量全自动装机

cobbler简介   cobbler 是一个系统启动服务boot server,可以通过pxe得方式用来快速安装&#xff0c;重装系统&#xff0c;支持安装不同linux发行版和windows。这个工具是用python开发&#xff0c;方便小巧&#xff0c;15k行代码&#xff0c;使用简单得命令完成pxe网络安装环境…...

【Pandas】pandas Series at

Pandas2.2 Series Indexing, iteration 方法描述Series.get()用于根据键&#xff08;索引标签&#xff09;从 Series 中获取值Series.at用于快速访问标量值&#xff08;单个元素&#xff09;的访问器Series.iat用于快速访问标量值&#xff08;单个元素&#xff09;的访问器 …...

optimum-habana 安装 optimum安装

目录 git地址: 运行必须参数设置 git地址: https://github.com/huggingface/optimum-habana git clone 以后 cd optimum-habana pip install . 运行必须参数设置 hpu改为 cuda, output = subprocess.run("pip list | grep habana-torch-plugin",shell=True…...

AutoDL服务器深度学习使用过程

前期准备 Xshell,Xftp,Pycharm专业版 step 1:实例开机&#xff08;无卡or有卡&#xff09;&#xff0c;Xshell连接 新建xshell会话&#xff1a; 登录指令格式为&#xff1a; ssh -p 38076 rootregion-1.autodl.com 在ssh -p 38076 rootregion-1.autodl.com命令中&#xff0…...

微信小程序:定义页面标题,动态设置页面标题,json

1、常规设置页面标题 正常微信小程序中&#xff0c;设置页面标题再json页面中进行设置&#xff0c;例如 {"usingComponents": {},"navigationBarTitleText": "标题","navigationBarBackgroundColor": "#78b7f7","navi…...

LeetCode算法题——有序数组的平方

题目描述 给你一个按非递减顺序排序的整数数组nums&#xff0c;返回每个数字的平方组成的新数组&#xff0c;要求也按非递减顺序排序。 题解 解法一&#xff1a;暴力解法 思路&#xff1a; 该题目可通过暴力解法解决&#xff0c;即利用for循环遍历数组&#xff0c;对数组每…...

【MyBatis-Plus 核心接口】BaseMapper 和 IService 深度解析

在使用 MyBatis-Plus&#xff08;简称 MP&#xff09;进行开发时&#xff0c;BaseMapper 和 IService 接口是我们老朋友了&#xff0c;不知道你会不会跟我一样好奇&#xff1a;为什么实现了 BaseMapper 或 IService 接口&#xff0c;我们就能轻松操作数据库&#xff1f;这背后有…...

SQL 建表语句详解

SQL 建表语句详解 在 SQL 中&#xff0c;创建表&#xff08;Table&#xff09;是数据库设计的基础。表是存储数据的基本单位&#xff0c;每个表由行和列组成。创建表的过程涉及到定义表的结构&#xff0c;包括列名、数据类型、约束等。本文将详细介绍 SQL 中的建表语句&#x…...