各种排序算法

排序算法总结:直接插入排序、冒泡排序、快速排序、简单选择排序、堆排序。

以下均是对数组a[n](a[0…n-1])按升序排序,如果需要降序,类似.


原创文章,转载请注明出处:http://blog.csdn.net/fastsort/article/details/10050565

1、直接插入排序

思路:初始时认为a[0]是有序的,然后依次将a[1]…a[n-1]插入到序列中。

/**@brief  对数组a[n]进行插入排序(升序)
 ** @note  a[n] : a[0...n-1]
 */
void    Sort_Insert(int a[], int n)
{
   for(int i=1; i<n; i++)
   {
       for(int j=i;j>0 && a[j-1]>a[j];j--)
       {
            swap(a[j-1],a[j]);
       }
   }
}

复杂度:时间O(n2),空间O(1),

最坏情况:逆序,移动次数和比较次数均达到最大。

最好情况:正序,移动0次,比较n-1次。

稳定性:稳定

 

可优化:前m个已经排序好,第m+1个插入时可以使用二分查找插入位置。这时仅仅是减少了关键字比较次数,移动次数不变。

 

2、冒泡排序

思路:从第0个元素开始,如果某个元素大于它后面的元素,则交换。第一次循环完成后,最大的元素移动到最后一个位置。下一轮排序过程,仍然从第0个元素开始,直到倒数第二个元素,本轮排序完成后第二大的元素再倒数第二个位置上。

如果某一轮没有任何元素交换,则结束排序过程。(所谓的优化)

/**@brief 对数组a[n]进行冒泡排序
 */
void    Sort_Bubble(int a[], int n)
{
   for(int i=0; i<n-1; i++)
   {
       int flag=false;
       for(int j=0; j<n-i-1; j++)
       {
           if(a[j]>a[j+1])
           {
                swap(a[j+1],a[j]);
                flag = true;
           }
       }
       if(flag==false) break;
   }
}

复杂度:时间O(n2),空间O(1),

最坏情况:逆序,移动次数和比较次数均达到最大。

最好情况:正序,移动0次,比较n-1次,只需一轮。

稳定性:稳定


3、快速排序

快速排序的思路:通过一轮排序将记录分成两个独立的部分,其中一部分的关键字均比另一部分小,然后再分别对这两部分记录继续排序,直到整个记录有序。

其使用了“分治”的思想,将问题的规模分解为两个小规模的问题。显然,当一轮排序只有一个元素时为出口,这时直接返回。

 

代码:

 

/**@brief 对数组a[l...u]进行划分
 ** @return 返回划分的支点的下标
 ** @note  a[l...q-1]<=a[q]<=a[q+1...u]
 */
int     Partition(int a[], int l, int u)
{
    int t=a[l];
    while(l<u)
    {
        while(l<u && a[u]>=t)   u--;
        a[l] = a[u];
        while(l<u && a[l]<=t)   l++;
        a[u] = a[l];
    }
    a[l] = t;
    return l;
}
/**@brief 辅助函数,实际的快排 */
void     qSortHelper(int a[], int l, int u)
{
    if(l>=u)    return;///递归出口
    int q = Partition(a,l,u);
    qSortHelper(a,l,q-1);
    qSortHelper(a,q+1,u);
}
/**@brief 对外调用的快速排序
 ** @note 对数组a[n]进行快排
 */
void    Sort_Quick(int a[], int n)
{
    qSortHelper(a,0,n-1);
}

       其中Partition函数将数组a[l…u]划分为两个部分,返回的即为这两个部分的分界点q,划分后,a[l…q-1]≤a[q] ≤a[q+1…u]。

 

复杂度:时间O(nlogn),空间O(logn).

最坏情况:正序或基本有序,退化为冒泡排序,时间复杂度O(n2),栈空间变为O(n)。

最好情况:数组随机乱序

 

稳定性:不稳定

 

优化:选取支点时随机选取,或者选取三个元素中的中间值,可改善最坏情况下的性能。也可以在基本有序时(递归出口不再是l>=u,而是u-l<C,C为某个比较小的常数),调用插入排序。


4、简单选择排序

思路:第一次从第0个元素开始选取最小的元素,与第0个元素交换,第二次从第1个元素开始选取最小的元素与第1个元素交换,直到最后一个元素。

 

/**@brief 对数组a[n]进行选择排序
 */
void    Sort_Select(int a[], int n)
{
   for(int i=0; i<n; i++)
   {
       int min = i;///第i个最小的数的下标
       for(int j=i+1; j<n; j++)
       {
           if(a[min]>a[j])
            min = j;
       }
       if(min!=i)
        swap(a[min],a[i]);
   }
}


复杂度:时间O(n2),空间O(1),

最坏情况:逆序,移动n次,比较O(n2)次。

最好情况:正序,移动0次,比较O(n2)次。

 

稳定性:这里的代码是稳定的,但是一般认为是不稳定的,要看具体的实现方式,比如这里将if(a[min]>a[j])改成if(a[min]>=a[j])则变成不稳定的了。

关于不稳定的排序:可以即为“(希尔)望(快排)点(选择)(堆排序)象”。


5、堆排序

       堆排序是在选择排序的基础上优化而来。

       堆的定义:略,

其实就是一颗特殊的完全二叉树:对于大根堆,任何一个节点都大于其左右孩子节点,这样堆顶元素就是最大的。对于小根堆,则堆顶元素是最小的。

下面讨论大根堆,小根堆的情况于此类似。


       我们使用数组存储堆,数组a[n]的元素范围为a[0…n-1],则对于元素a[i],其左孩子为a[2i+1],右孩子为a[2i+2],父节点为a[(i-1)/2] ,其中i=0…n-1。

       堆排序的过程可以归纳为:

              1、建立大根堆

              2、将堆顶元素与倒数第一个元素交换,这时最大元素在倒数第一个位置

              3、将数组长度缩小1个,重新调整堆为大根堆。重复2-3,直到数组只有一个元素为止。


       实质上就是堆的不断调整和删除的过程。所谓删除就是将堆顶元素(最大)和换到未排序的数组的末尾,从而实现排序。

       而建堆过程也可以是看做不断插入和调整的过程:对叶子节点,已经是堆,所以从第一个非叶子节点开始调整,直到根节点。


       假设数组a[n]中,a[s-1…m]已经满足堆的性质,那么将a[s]插入堆,使a[s…m]仍然是堆的过程就是:

将t=a[s]和其左右孩子节点比较,如果t大于左右孩子节点,那么这时a[s…m]已经满足堆的性质,直接返回;

否则沿着a[s]较大的孩子节点j(2s+1或者2s+2),将a[j]向上移动;然后再以j为根节点,继续向下调整,直到最后一个节点。

代码如下:

/**@brief 数组a[s+1...m]满足大根堆要求
 ** 现从s节点开始调整堆,使a[s...m]成为大根堆
 ** 数组从0开始,i节点左右孩子节点分别为2i+1,2i+2
 */
void    HeapAdjust(int a[],int s,int m)
{
    int temp = a[s] ;
    for(int j=2*s+1; j<=m; j=2*j+1)///沿较大孩子节点向下筛选
    {
        if(j<m &&a[j]<a[j+1])  j++;///j取左右孩子较大的那个
        if(temp>=a[j])  break;   ///已经满足堆,跳出循环
        a[s] = a[j];///较大的元素向上移动
        s = j;///更新s
    }
    a[s] = temp;///a[s]的最终位置
}
 

有了这个函数,堆排序就简单了:

第一步,从第一个非叶子节点开始,逐步插入到堆中,构建一个大根堆

然后就是不断的删除堆顶元素和调整堆的过程:

void    Sort_Heap(int a[],int n)
{   ///第一个非叶节点为n/2-1
    for(int i=n/2-1; i>=0; i--)///创建大根堆
        HeapAdjust(a,i,n-1);
 
    for(int i=n-1;i>0;i--)///堆排序
    {
        swap(a[0],a[i]);///将大根堆堆顶元素交换到最后
        HeapAdjust(a,0,i-1);///调整使其再次成为大根堆
    }
}


复杂度:最好和最坏情况下都是时间O(nlogn),空间O(1),

但是对于n较小时性能不明显,因为其常数较大。

稳定性:不稳定。

 

6、求第k个数的问题,以及top-k问题。

       不需要排序。参考快速排序的划分。如果当前划分返回的就是k,则这个元素就是待求的;否则看k和返回的kn大小关系,以确定在前部分继续查找还是在后部分继续查找。

int     topHelper(int a[],int l,int u,int k)
{
    int kn=Partition(a,l,u);
    if(kn==k)  return  a[kn];
    if(kn>k )    return topHelper(a,l,kn-1,k);
    else       return  topHelper(a,kn+1,u,k);
}
/**@brief 返回数组a[n]中第k小的数
 ** @param 0<=k<n.
 */
int     KMin(int a[], int n, int k)
{
    return topHelper(a,0,n-1,k);
}

求得第k个数后,再便利一遍数组,即可得到top-k。

 

其他相关问题:参考《编程珠玑》第11章。




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值