C++排序算法

来源:互联网 发布:网络爬虫软件下载 编辑:程序博客网 时间:2024/06/08 13:29

C++排序

排序算法是编程中常用的算法之一,实际上,排序大概是最最常用的了,下面,我们来介绍一下c++的排序算法,不过介绍之前,我们可以先看一个非常容易理解的函数:

template<class t>inline size_t size(t ax[]){    return sizeof(ax)/sizeof(t);}//简单的数组大小内联函数

该函数大部分时候可以帮我们干许多事,尽管并不要求一定需要,我们用这个函数还是挺方便的

冒泡排序(Bubble Sort)

好吧,这是一种所蕴含的思想极其朴素的一种算法,其主要思想是:给定一个数集(在c++里可以理解成数组之类的)并将第二个数与第一个数比较,找出小的,然后把比较的小的换到前边,之后把现在的第二个数与第三个数再一次比较,并找到小的,再换,以此类推,在比较完最后一个数后,我们就找到了最小的,把最小的放在第一个,然后,我们便可以在剩下的数中再一次进行一轮比较,又一次,又一次,最终,我们就按照升序排序好了一个数集。
不过在看代码前,您可能想知道冒泡排序为什么叫做冒泡排序:

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。(来源自维基)

以下是代码:

#include<stdio.h>using namespace std;template<class t>t[] BubbleSort(t[])int main(){}template<class t>void BubbleSort(t ax[],size_t len)//升序的冒泡排序,直接改变数组内的值,其中len是排序数量{    size_t i, j;    t temp;    for (i = 0; i < len - 1; i++)    {        for (j = 0; j < len - 1 - i; j++)        {            if (ax[j] > ax[j + 1])            {                temp=ax[j];                ax[j]=ax[j+1];                ax[j+1]=temp;//交换位置            }        }    }}

以上就是冒泡排序的代码,当然,main函数啥也没干,我加上main函数只是为了您以后的测试。
下边介绍下冒泡函数的性质,引自维基百科

在最坏的情况,冒泡排序需要 O(n^2)次交换

在最好情况下,冒泡需要O(n)次比较。
当然,冒泡排序可以被改进改进,这会在下边讲,而现在,我们先来看一看下一个算法:

插入排序(Insertion Sort)

又一种简单的算法,不过,该算法需要一点额外的空间,但是,如果你家电脑不是dos机的话,这些额外的内存非常划算。
首先,我们先看一个例子:
{2,8,1,35,6}
我们用插入排序的思想来排序:
首先,我们从头开始,先认为2,也就是开头的数字是已经被排序好的。
然后,我们看下一个数,8,从该数往前依次比较,直到找到一个小于或等于8的数,然后,把这个数与8交换,然而找了一圈,并没有。
然后,我们可以继续按照上边的方法排序,直到找到一个数比前边排序好的数列小的,然后,将该数插入进小于等于其的数的后边,并把后边的排序好的数列往后移动一次。
之后,我们就可以不断这样,便可以排序好了。
以下的代码引自维基百科:

#include<stdio.h>template<typename T> //整数或浮点数皆可使用,若要使用class,需有定义>运算符  void insertion_sort(T* arr, int len) {    size_t i, j;    T temp;    for (i = 1; i < len; i++) {        temp = arr[i];        j = i - 1; // 如果将赋值放到下一行的for循环内, 会导致在第10行出现j未声明的错误        for (; j >= 0 && arr[j] > temp; j--)            arr[j + 1] = arr[j];//最后一次执行该语句后,跳出当前for循环前,会再一次执行j--        arr[j + 1] = temp;//执行完上一个语句(即for语句)后,跳出的位置保存在j中,此时arr[j]的值是没有经过移动的,不能替换,应该替换的是arr[j+1]    } } //以下是添加进来的啥也不干的main函数 void main() { } ``` 值得说明的一点是插入排序的复杂度与冒泡排序一样,时间复杂度最坏情况下为O(N^2)最好情况下为O(n)但是,俩算法的交换次数不同,最坏时,冒泡排序需要O(n^2)次交换,而插入排序则只有O(n)次,也就是说,插入排序的最坏情况所需时间应该会比冒泡排序好的多。 当然,此排序算法也可以改进,以后再说。## 选择排序(Selection sort)选择排序,其道理简单易懂,先找数组中最小的,找到后便会把这个数与第1个数交换,之后,在剩下的数中再一次找到最小的,并且与第2个数交换,然后是第3个,第4个,就这样,不停的交换之后,就排序成功了。以下代码依然引自维基百科:``include<stdio.h>template<typename T> //整数或浮点数皆可使用,若要使用class,需有定义>运算符void selection_sort(T arr[]){    size_t size=sizeof(arr)/sizeof(T)-1;    T temp;    for (size_t i = 0; i < size; i++)    {        int min = i;        for (int j = i + 1; j < arr.size(); j++)        {            if (arr[j] < arr[min])            {                min = j;            }        }        temp=ax[j];        ax[j]=ax[j+1];        ax[j+1]=temp;//交换位置    }}void main(){}

选择排序的比较次数为O(n^2)而交换次数为O(n),由于交换需要的时间较多,因此,当n较小时,选择排序比冒泡排序好那么一丁点,而且,选择排序能够原地操作。
如果你觉得上边的都很好理解,那么,下边这个就不怎么好理解了:

桶排序(Bucket sort)

何谓之桶?一种能存液体的容器,而如果我们给定一个数组ax[]并明确的知道所要排序的上下限,我们就可以分配许多空间(总量就是ax的大小),并且把这些空间看着一个个桶,然而,我们的每个桶都是可以容纳下许多数据的类似数组的东西(其实实现上就是数组)而且这些桶也有明确的上下界(这主要是为了之后的排序)然后呢,我们就可以根据规则(这可以自己定,但是千万别搞那么多麻烦事情)来把数据一个个分配给桶,然后,分配之后进行一次桶内的排序,在所有数据分配完之后,再把数据一个个返回,这样,由于每个桶内已经确定了上下界,并且每个桶被已经是排序好了的,我们就可以愉快的把所有数据直接放回去。
听着的确很麻烦,其实实现也极其麻烦,下边是我做的桶排序:

//我们假设已经知道了上下界,分别是1和1000,当然,如果不知道上下界也没关系//我们可以先找到最小数据,并找到最大数据,在数据规模极大时,这点操作有没有都一样,然后//我们在运行时确定自己要多少桶——桶的数量并非越多越好,这是与数据规模有极大关系的,当数据规模//较小时,俩桶就可以了,然而当数据规模极大时,桶的数量应该与数据规模成正比//当然,我十分确信您找不到那些需要几千个桶的数据。//下边的算法有俩桶,范围分别为1-500和501-1000#include<stdio.h>#include<vector>template<class t>inline void donothing(){}void Bucket_sort(t ax[],size_t num)//ax是排序的数组,num是排序的数据的个数(从前数)该排序是升序排序{    //首先,我们把数据分配进桶里边,因此得有俩桶再说,我知道用vector非常浪费内存(vector需要预先申请额外内存别告诉我这你都不知道)和时间(vector在额外空间满的时候会删除现有内存,并且申请更多内存空间,把现有数据复制过去,尽管内存是如此到底高效以至于vector基本上不会浪费多少时间,但是在现在这种程度上,vector还不如t bx[500]强    std::vector<t> a,b;    size_t times=0;    for(size_t i=0;i<num;i++)    {        if(ax[i]<501)//标记3        {            //此处使用一种代码将桶内进行一次排序,尽管可以自己选,但是我还是为了方便自己给您加了。(实际上,如果把vector改成forward_ist会极其极其高效)            times=a.size();            while(times>0)            {                if(a[times]>ax[i])                {                size_t sdh=a.size();                a.push_back({});                //此处删去a[times]以后的所有元素向后移动一位                while(sdh>=times)                {                    a[sdh+1]=a[sdh];                    sdh--;                }                a[times]=ax[i];                goto loop;                }                times--;            }            if(times=0)            {            size_t sdh=a.size();            a.push_back({});                while(sdh>=0)                {                    a[sdh+1]=a[sdh];                    sdh--;                }                a[0]=ax[i];                goto loop;            }        }        else        {            //同样,这里也加入一次排序,是对于容器b的,但是由于上边有就不复制了,另外,你应该把上边的代码的全部容器a改成容器b        }        loop:donothing();//此处添加一行无用函数donothing,有些编译器不允许直接goto到空行    }    //现在我们得到了桶和排序好的数据,现在只要按照顺序把a中数据安放回去就可以了    //标记1    size_t times=0;    while(times<a.size())    {        ax[times]=a[times];        times++;    }    times=0;    while(times<b.size())    {        ax[times+a.size()]=b[times];        times++;    }    //标记2    //现在,我们就得到了一个排序好了的数组,然而,以上算法只是有两个桶的桶排序,对于需要多个桶的算法,我们可以在标记1和2之间套上一层循环以进行桶的放入,同时,把在标记3处的if语句改成switch语句,这样就可以进行多个桶的运算当然,如果想要在运行时确定桶的数量,可以将switch改成一个while循环,循环动作是满足x-y区间内的整数放到桶n中如果不满足,就把判断条件改成别的由你定义的另一个区间,进行新一轮循环,不过,这样会大大提升运行时间,还是别这样最好}

桶排序的时间复杂度很难说,最坏时和冒泡一样,是O(n^2),最好情况时由于受桶的数量的影响,很难确定具体时间(大概是O(n))但平均时间是O(n+k),空间复杂度是O(n*k),桶排序由于极其复杂,而且使用时面向的目标单一,通常情况下还是别用桶排序比较好。
然而,桶排序的确是比冒泡排序好很多的,实际上,因为桶排序把众多数组数据分为一些较小的数据,造出来每次排序的时间较少,因此,桶排序在大部分时候比冒泡排序更加优秀。

希尔排序(Shell Sort)

这是一种由Donald Shell于1959年公布的算法,其名字就是提出者的名字(shell)而其思想跟桶排序差不多,实际上给递归的“分而治之”想法也差不多,其主要思想如下:
首先定义一个“增量”,其意义是每个元素间的距离,我们用增量把数组分成好几个较小的数组(数组的总数有增量个,而每个小数组的元素是由原来数组中相隔增量个元素构成的,列如,数组{5,2,9,3,52,21}中,若定义增量为2,则有两个小数组,分别为{5,9,52}和{2,3,21},这俩小数组中的元素之间都是原来素组中相隔2个元素的元素,有时可能有一个数据个数比其他数组小的数组,这是因为有时增量不能整除原数组数据个数),之后,我们可以对这些小数组进行排序(通常是插入排序,因为数组不如原来数组的元素多,因此插入排序在最坏情况下所使用时间较少)排序完了之后,我们缩小增量,再一次进行一次上述排序,(此时,每一轮过后数组都会更加有序,而对于有序的数组,直接插入排序非常快)之后,到增量为1时,在进行最后一次排序后,数组被排序完了。
至于增量是如何减少的?很简单,大部分情况下除2就可以了,而若增量是奇数,根据c++整型除法规则,会忽略小数位,也就是把小数部分去掉,此时的结果等价于(增量-1)/2
希尔排序的实现如下,其增量为2:

#define step 2//增量(有时也叫步长)为2template<class t>void shell_sort(t ax[],size_t size){   size_tt ta, tb, times;      for (times = n / 2; times > 0; tiems /= step) //步长          for (ta = 0; ta < gap; ta++)        //直接插入排序          {              for (tb = ta + times; tb < size; tb += times)                   if (ax[j] < ax[tb - times])                  {                      size_t temp = a[j];                      size_t k = tb - times;                      while (k >= 0 && a[k] > temp)                      {                          a[k + times] = a[k];                          k -= times;                      }                      a[k + times] = temp;                  }          }  }

也许你觉得希尔排序与桶排序差不多——的确差不多,实际上,他们都是把一个数组分成多个部分来处理的,然而,桶排序把数组分成不定的数组,而希尔排序则是确定每个数组分配多少,然后再进行排序的,至于复杂度,最坏时需要O(n^2)的时间,最好时,要O(n)的时间,这个算法也可以改进,具体的我们以后再讲。
如果你觉得上边的算法都不咋地,那么你可以看看下边这个十分优秀的算法,该算法并非比较算法,实际上该代码极其极其极其快:

计数排序(Counting sort)

以下引自维基百科

当输入的元素是n个0到k之间的整数时,它的运行时间是Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序算法中,能够更有效的排序数据范围很大的数组。
通俗地理解,例如有10个年龄不同的人,统计出有8个人的年龄比A小,那A的年龄就排在第9位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去1的原因。算法的步骤如下:
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组 C 的第 i 项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

代码如下:
(也引自维基,稍作修改)

#define num 100//要比较的范围,0-100void counting_sort(int *ini_arr, int *sorted_arr, int n) {    size_t *count_arr = new(sizeof(size_t) * num);    size_t i, j, k;    for (k = 0; k < 100; k++)    {        count_arr[k] = 0;    }    for (i = 0; i < n; i++)    {        count_arr[ini_arr[i]]++;    }    for (k = 1; k < 100; k++)    {        count_arr[k] += count_arr[k - 1];    }    for (j = n; j > 0; j--)    {        sorted_arr[--count_arr[ini_arr[j - 1]]] = ini_arr[j - 1];       }    delete count_arr;}

额,我知道我引用的有点多,不过本人的确找不到比维基更好的解释了,而且令人感到十分愉悦的一点是,该算法的最优/最坏/空间/平均时间复杂度全部为O(n+k)(当排序n个0-k之间的数时)
然而这个算法在维基中的介绍可能引起了你另一个兴趣:
何为基数排序?
下边就是:

基数排序(Radix sort)

哦,这个算法其实是你的小学老师教给你的,他不是能直接比较的一种算法,而是把数分为好几位:
列如,当你的小学老师教你如何比较两个整数的时候,他会这样讲:
“同学们,我们先把他们位的数量数一下,列如,123有三位,12有两位,因此,123大(位多的比位小的大)而如果是345,和123的位数一样,因此,我们从高位开始比,3>1,所以123比345小。”
“而如果是346和345,我们就从高位开始比,一直比到某个数的某一位比另一个大,那么那个数就大”
“如果都一样,那么他们就相等”
以下代码引自维基:

int maxbit(int data[], int n) //辅助函数,求数据的最大位数{    int maxData = data[0];      ///< 最大数    /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。    for (int i = 1; i < n; ++i)    {        if (maxData < data[i])            maxData = data[i];    }    int d = 1;    int p = 10;    while (maxData >= p)    {        //p *= 10; // Maybe overflow        maxData /= 10;        ++d;    }    return d;/*    int d = 1; //保存最大的位数    int p = 10;    for(int i = 0; i < n; ++i)    {        while(data[i] >= p)        {            p *= 10;            ++d;        }    }    return d;*/}void radixsort(int data[], int n) //基数排序{    int d = maxbit(data, n);    int *tmp = new int[n];    int *count = new int[10]; //计数器    int i, j, k;    int radix = 1;    for(i = 1; i <= d; i++) //进行d次排序    {        for(j = 0; j < 10; j++)            count[j] = 0; //每次分配前清空计数器        for(j = 0; j < n; j++)        {            k = (data[j] / radix) % 10; //统计每个桶中的记录数            count[k]++;        }        for(j = 1; j < 10; j++)            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中        {            k = (data[j] / radix) % 10;            tmp[count[k] - 1] = data[j];            count[k]--;        }        for(j = 0; j < n; j++) //将临时数组的内容复制到data中            data[j] = tmp[j];        radix = radix * 10;    }    delete []tmp;    delete []count;}

要注意的是,该算法有时适用于一些特殊的数据结构(列如按位储存数的类)那时,该算法极其好用,当然,大部分时候您老用不到:
最坏时,O(nk),空间复杂度为O(n+k)

归并排序(Merge sort,或mergesort,这其实完全没区别对吧?)

哦,该算法是由电脑的老祖宗,冯·诺依曼于1945年提出的,其核心思想是分洽法,步骤如下:
1.申请空间,使其大小为两个已经排序数组之和,该空间用来存放合并后的数组cx
2.设定两个指针,最初位置分别为两个已经排序数组的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间cx,并移动指针到下一位置,也就是指针自增
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾
下面是代码,显然引自维基

> template<typename T> //整数或浮点数皆可使用,若要使用class时要有<运算符void merge_sort(T arr[], int len) {    T* a = arr;    T* b = new T[len];    for (int seg = 1; seg < len; seg += seg) {        for (int start = 0; start < len; start += seg + seg) {            int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);            int k = low;            int start1 = low, end1 = mid;            int start2 = mid, end2 = high;            while (start1 < end1 && start2 < end2)                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];            while (start1 < end1)                b[k++] = a[start1++];            while (start2 < end2)                b[k++] = a[start2++];        }        T* temp = a;        a = b;        b = temp;    }    if (a != arr) {        for (int i = 0; i < len; i++)            b[i] = a[i];        b = a;    }    delete[] b;}

堆排序(Heap Sort)

如果你直接看堆排序的维基介绍,那么恭喜,你99%不知道堆排序到底怎么做的:

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

使你最迷惑的恐怕就是“二叉树”
何为二叉树?
二叉树(Binary tree),形象的来说,是一种树——数有根茎,根茎上有枝干,枝干上有树枝,树枝上有叶子。
而树(Tree structure)这种结构就是如此,从第一个节点(Node),也就是“根茎”开始,有好几个相连的结点,而这些结点上也有好几个相连的结点,然后还有,还有,当然到了一定程度时,树就到了末端,没有节点与之相连。
准确的来说,除了开头的根(root)以外,其他的数据都有统称——叶子,而根节点之外的节点,叫做子节点(child),没有连结到其他子节点的节点,称为叶节点(Leaf)。。
而二叉树就是树中的一种特殊结构,所有节点与之相连的子节点的数量都不超过2,也就是说,每个叶子的下一层子节点顶多有俩或一个,或没有。
而且,二叉树具有左右次序,不能颠倒,左边的子节点叫左子树,右边的,右子树。
二叉树的第i层至多拥有 2^(i-1)个节点数;深度为 k的二叉树至多总共有 2^k-1}个节点数(定义根节点所在深度 k=0,每一层的深度都是原来一层深度+1),而总计拥有节点数匹配的,称为“满二叉树”;深度为k有n个节点的二叉树,当且仅当其中的每一节点,都可以和同样深度k的满二叉树,序号为1到n的节点一对一对应时,称为“完全二叉树”。;对任何一棵非空的二叉树T,如果其叶片(终端节点)数为n0,分支度为2的节点数为n2,则n0 = n2 + 1。
好吧,完全二叉树还是不懂对吧?再来一个更全面的介绍:
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
一棵二叉树至多只有最下面的一层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
点这看我介绍树结构的博文
懂了吧,现在,我们回过头来看wiki关于堆排序的介绍,可别告诉我你不知道啥是堆。
然而上边bb那么多,二叉树到底和堆有什么关系?
实际上是和完全二叉树有何关系,客观来讲,每个堆都是一个完全二叉树,怎么讲?因为完全二叉树除了底层说不定不是满的,其余层都是,当然,这表明我们从根节点开始编号,第k层的第一个的编号从左往右,那么很愉快的,我们得到了一个被编好号了的堆,如:
下边二叉树 下边堆
1 1 2 3 4 5
2 3
4 5
如上,每个数据就是其编号,堆的编号也都就是这些数据,很显然,这其实是数组。
根据以上内容,可以推导出一些特殊的节点的编号:
- 父节点i的左子节点在位置(2*i+1);
- 父节点i的右子节点在位置(2*i+2);
- 子节点i的父节点在位置floor((i-1)/2);
其中,floor函数是向下取整函数,包含在cmath库中。
以下为算法:(引自wiki)

void max_heapify(int arr[], int start, int end) {    //建立父节点指针和子节点指针,这里指针的含义其实是标记    int dad = start;    int son = dad * 2 + 1;    while (son <= end) { //若子節點指標在範圍內才做比較        if (son + 1 <= end && arr[son] < arr[son + 1]) //先毕竟俩子节点大小,选择最大的            son++;        if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完,直接跳出函數            return;        else { //否则交换父子內容再继续子节点和孙节点比较            swap(arr[dad], arr[son]);            dad = son;            son = dad * 2 + 1;        }    }}void heap_sort(int arr[], int len) {    //初始化,i从最后一個父节点开始调整    for (int i = len / 2 - 1; i >= 0; i--)        max_heapify(arr, i, len - 1);    //先將第一个元素和已经排好的元素前一位做交换,再重新調整(刚调整的元素之前的元素),直到排序完    for (int i = len - 1; i > 0; i--) {        swap(arr[0], arr[i]);        max_heapify(arr, 0, i - 1);    }}

堆排序非常好,其最优最坏时间复杂度全都是O(n log n)

快速排序(Quicksort)

这将是本文最后一个算法,当然还有好多算法没讲,主要因为他们并不常用,以后我应该也会推出一些算法的讲解。

又称划分交换排序(partition-exchange sort),一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序n个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

不用说你也知道我引自那,这个算法极其简单,但在大部分(你可以认为所有)情况下非常快。

1.从数列中挑出一个元素,称为”基准”(pivot),
2.重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
以下为实现:

//参考:http://www.dutor.net/index.php/2011/04/recursive-iterative-quick-sort/struct Range {    int start, end;    Range(int s = 0, int e = 0) {start = s, end = e;}};template<typename T> //整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)、"大於"(>)、"不小於"(>=)的運算子功能,这句话我实在懒得翻译了,自己看得懂吧void quick_sort(T arr[], const int len) {    if (len <= 0) return; //避免len等于负值时宣告堆宕机    //r[]模拟堆,p为數量,r[p++]为push,r[--p]为pop且取得元素    Range r[len]; int p = 0;    r[p++] = Range(0, len - 1);    while (p) {        Range range = r[--p];        if(range.start >= range.end) continue;        T mid = arr[range.end];        int left = range.start, right = range.end - 1;        while (left < right) {            while (arr[left] < mid && left < right) left++;            while (arr[right] >= mid && left < right) right--;            std::swap(arr[left], arr[right]);        }        if (arr[left] >= arr[range.end])            std::swap(arr[left], arr[range.end]);        else            left++;        r[p++] = Range(range.start, left - 1);        r[p++] = Range(left + 1, range.end);    }}

好吧好吧,实际上该算法和介绍有点不一样,尽管如此,其主要思想差不多,这也是一种好方法。
最坏时间O(n^2),最优O(n log n)

好吧,到这就完了,如果有问题,欢迎提问呦!

原创粉丝点击