其他排序算法:快速、归并、堆排序(top N问题)

来源:互联网 发布:网络新媒体加盟 编辑:程序博客网 时间:2024/06/17 17:41

快速排序

快速排序是一种分治排序算法,采用了递归的方法。

原理:
1.先从数列中取出一个数作为基准数。
2.分区过程:将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.对左右区间重复第二步,直到各区间只有一个数。

代码实现为:

int partition(Item a[],int l,int r) {    int i=l-1,j=r;    Item v=a[r];    for(;;) {        while(less(a[++i],v));        while(less(v,a[--j]))             if(j==l) break;     //当基准数是最小元素时,跳出while循环        if (i>=j) break;        //如果i和j重合了,就跳出for循环        exch(a[i],a[j]);        //正常情况下,交换a[i]和a[j],使for循环继续    }    exch(a[i],a[r]);//将基准数放到正确的位置上    return i;//返回基准数的位置}void quick(Item a[],int l,int r){    int i;    if (r<=l) return;//如果子区间为0或1个元素,就可以结束递归调用    i=partition(a,l,r);    quick(a,l,i-1);    quick(a,i+1,r);}

快速排序的性能特征

平均情况下,快速排序使用大约2NlgN次比较。
最坏情况下,快速排序使用大约N^2/2次比较。

归并排序

原理:基本思路是不断将两个有序的序列合并为一个大的有序序列。具体操作时,首先将序列分为两部分,然后对每一部分进行循环递归,再逐个将结果进行归并。归并排序是一种 稳定排序

归并排序依赖于归并操作,归并操作的具体过程如下:

  1. 申请空间,使其大小为两个已经排序序列之和,然后将待排序数组复制到该数组中
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较复制数组中两个指针所指向的元素,选择相对小的元素放入到原始待排序数组中,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到原始数组末尾

    与快速排序的联系

    快速排序包括一个选择操作过程(确定a[r]的正确位置),后跟两个递归调用。
    归并排序的执行过程和快速排序相反,两个递归调用之后是一个归并过程。


归并操作的代码实现为:

Item aux[maxN]; //辅助数组void partion(Item a[],int l,int m,int r) {    int i,j,k;    for (i=l;i<=r;i++) aux[i]=a[i];    //将分别排好序的a[1~m]和a[m+1~r]放入辅助数组中    i=l,j=m+1;    for (k=l;k<=r;k++)        if (less(aux[i],aux[j]))            a[k]=aux[i--];        else             a[k]=aux[j++];        if(i>=m) { //如果前半个数组已经全部处理完了            for(;k<=r;k++) a[k]=aux[j++];            break;        }        else if(j>=r) { //如果后半个数组已经全部处理完了            for(;k<=r;k++) a[k]=aux[i++];            break;        }       }

有了归并操作,就可以自顶而下地实现归并排序:将给定序列平分成两个子序列。若两个子序列长度大于1,则再递归划分下去,直到两个子序列都只有1个元素(或者一个有1个元素,另一个没有元素),这时候他们显然是各自有序的。然后进行归并操作,并逐层向上返回。这样层层归并,等到第一层递归调用返回时,再进行最后一次归并,排序就完成了。

代码实现如下:

void merge(Item a[],int l,int r)  {        if (l>=r) return;       //当子序列只有1个元素或为空时,结束递归调用,返回        int m=(l+r)/2;        merge(a,l,m);        merge(a,m+1,r);        partion(a,l,m,r);    }

对6 5 3 1 8 7 24 进行归并排序的动画效果如下:

归并排序举例

归并操作的视觉效果如下所示:

归并排序原理

归并排序的性能特征

归并排序使用大约N lgN 次比较。
归并排序使用与N成正比的额外内存空间。


堆排序

定义:
优先队列:是一种数据结构,其数据项中带有关键字,它支持两种基本操作:向优先队列中插入一个新的数据项,删除优先队列中关键字最大的数据项。
堆有序:如果一棵树中每个节点的关键字都大于或等于所有子节点中的关键字(如果子节点存在),就称树是堆有序的。
堆:本质上是完全二叉树,用数组(vector)表示。

自底向上堆化

在增加一个节点的优先级时,为了恢复堆的条件,我们向上移动,需要时交换位置k处的节点与其父节点a[k/2],只要a[k/2]<a[k],就继续这一过程,或到达堆顶。

fixUp(Item a[],int k){    while(k>1 && less(a[k/2],a[k])) {    exch(a[k],a[k/2];    k=k/2;    }}

自顶向下堆化

在降低一个节点的优先级时,为了恢复堆的条件,我们向下移动,需要时交换位置k处的节点与其子节点中较大的那个,如果在k处的节点不小于它的任何一个子节点,或到达树底,则停止这一过程。

fixDown(Item a[],int k,int N){    int j;    while(2*k <= N)//如果没到达树底    {        j=2*k;        if(j<N && less(a[j],a[j+1]))             j++;  //选出子节点中较大的那个        if(less(a[j],a[k]))             break; //如果当前节点不小于它的两个子节点        exch(a[k],a[j]);        k=j;    }}

基于堆的优先队列

利用自顶向下和自底向上的堆化操作,可以实现优先队列的插入和删除操作。
插入时,在堆尾加入新元素,然后使用fixUp来恢复堆的条件。

void PQinsert(Item v) {    pq[++N] = v;    fixUp(pq,N);}

删除顶上元素时,取出该顶上元素(根节点),然后把当前堆的最后一个元素临时放在根节点上,然后使用fixDown来恢复堆的条件。

Item PQdelmax() {    exch(pq[1],pq[N]); //交换根节点和最后元素    fixDown(pq,1,N-1); //从顶向下调整堆    return pq[N--]; //返回即将删除的顶上元素}

使用堆进行排序

使用堆对数组a[]进行排序,首先是依次把元素插入到堆中,然后从大到小,依次递减取出当前最大元素,从而完成排序。

void PQsort(Item a[],int l,int r) {    int k;    PQinit();    for (k=l;k<=r;k++) PQinsert(a[k]);    for (k=r;k>=l;k--) a[k]=PQdelmax();}

topN问题

给定n个元素,要求其中最大/小的N个元素,即为topN问题。
思路:
最大topN问题可以用大小为N的最小堆实现:首先建立起一个大小为N的最小堆;然后从N+1个元素开始直到最后一个元素,插入一个元素,紧接着弹出堆顶元素(最小的那个),最后剩下的堆中元素即为n个元素中最大的N个。

简单修改下上一节的代码:

void PQsort(Item a[],int l,int r) {    int k;    PQinit();    for (k=l;k<l+N;k++) PQinsert(a[k]);    for(k=l+N;k<r;k++) {        PQinsert(a[k]);        PQdelmax();    }    for(int i=1;i<=N;i++)        cout<<pq[i];}

最小topN问题则类似。

0 0
原创粉丝点击