排序算法

排序算法

我们常常使用数据排序,但如果暴力算法,时间复杂度太大,需要用到排序算法

排序是指将一组原本无序的数据元素,按照具体要求,排列成有规律的按关键字有序(从小到大或从大到小)的序列。

一、基于比较的排序

\(O(n^2)\)家族 选择排序、插入排序、冒泡排序

\(O(n\log n)\)家族 堆排序、归并排序、快速排序

冒泡排序

算法思想:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数”冒泡”至数列的顶端。

算法步骤:
(1)从数组中第一个数开始,依次与下一个数比较并次交换比自己小的数,直到最后一个数。如果发生交换,则继续下面的步骤,如果未发生交换,则数组有序,排序结束,此时时间复杂度为\(O (n)\)
(2)每一轮”冒泡”结束后,最大的数将出现在乱序数列的最后一位。重复步骤(1)。

稳定性:稳定排序。

时间复杂度: \(O (n)\)\(O (n^2)\),平均复杂度为\(O (n ^ 2)\)

最好的情况:如果待排序数据序列为正序,则一趟冒泡就可完成排序,排序码的比较次数为n-1次,且没有移动,时间复杂度为\(O (n)\)

最坏的情况:如果待排序数据序列为逆序,则冒泡排序需要 n-1 次趟起泡,每趟进行 n-i 次排序码的比较和移动,即比较和移动次数均达到最大值:
移动次数等于比较次数,因此最坏时间复杂度为O(n^2)。

for(int i=1;i<=n;i++)
   for(int j=n;j>=i+1;j--)
     if(a[j]<a[j-1])
       swap(a[j],a[j-1]); //交换   

优化:

for(int i=1;i<=n-1;i++)
    {  
       int ok=1;
       for(int j=n;j>=i+1;j--)
          if(a[j]<a[j-1])
            {
               ok=0;
               swap(a[j],a[j-1]);
             }
        if(ok==1) break;
     }

选择排序

每一趟从序列中选出最小(或最大)的数放到待排序的序列最前,直到全部排完

算法实现:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的 n-1 个记录中再选出关键码最小的记录与第二个记录换;
以此类推,
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,直到整个序列按关键码有序。

for(int i=1;i<n;i++)
{
   int k=i;
   for(int j=i+1;j<=n;j++)
       if(a[j]<a[k]) k=j;
   if(k!=i) swap(a[i],a[k]);
}

插入排序

思想:将一个记录插入到已排序好的有序表中,从而得到一个新记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。要点是设立哨兵,作为临时存储和判断数组边界之用

for(i=0;i<n;i++)
    {
       for(j=i-1;j>=0;j--)//在前面有序区间找合适的插入位置 
           if(a[j]<a[i]) break;//找到比a[i]小的位置插入其后 
       if(j!=i-1)
          {
             temp=a[i];
             for(k=i-1;k>j;k--)
                a[k+1]=a[k];//空出空位,让给a[i] 
             a[k+1]=temp;
           }     
    }

优点:支持插入元素,每插入一个元素,可在O(n)的时间里,找到其位置,返回数组;

快速排序

快速排序采用的思想是分治思想。

算法原理:
(1)从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;

(2)把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;

(3)然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。

稳定性:不稳定排序。

时间复杂度: O(nlog2n)至O(n^2),平均时间复杂度为O(nlgn)。

最好的情况:是每趟排序结束后,每次划分使两个子文件的长度大致相等,时间复杂度为O(nlog2n)。

最坏的情况:是待排序记录已经排好序,第一趟经过n-1次比较后第一个记录保持位置不变,并得到一个n-1个元素的子记录;第二趟经过n-2次比较,将第二个记录定位在原来的位置上,并得到一个包括n-2个记录的子文件,依次类推 ,最坏O(n^2)

void qsort(int l,int r)
{
    int i,j,mid,p;
    i=l;j=r;
    mid=a[(l+r)/2];
    do
    {
        while(a[i]<mid)i++;
        while(a[j]>mid)j--;
        if(i<=j)
        {
            p=a[i];a[i]=a[j];a[j]=p;
            i++;j--;
        }
    }while(i<=j);
    if(l<j) qsort(l,j);
    if(i<r) qsort(i,r);
}

归并排序

基于分治的思想,一个问题可能在小范围之内有确定的结果,逐步扩大范围,递推出去,得到结果。

首先不断分解数组,直到数组有序(内有一个元素)(二分),按数组下标分治,将两个有序的数组归并成一个有序数组;

void msort(int s,int t)
{
    if(s==t) return;
    int mid=(s+t)/2;
    msort(s,mid);
    msort(mid+1,t);
    int i=s,j=mid+1,k=s;
    while(i<=mid&&j<=t)
    {
        if(a[i]<=a[j])
        {
            r[k]=a[i];k++;i++;
        }
        else
        {
            r[k]=a[j];k++;j++;
        }
    }
    while(i<=mid)
        {
            r[k]=a[i];k++;i++;
        }
    while(j<=t)
        {
            r[k]=a[j];k++;j++;
        }
    for(int i=s;i<=t;i++) a[i]=r[i];
    
}

应用:求逆序对。总的逆序对个数=左半部分逆序对个数+右半部分逆序对个数+左边的数大于右边的数的个数

void msort(int s,int t)
{
    if(s==t) return;
    int mid=(s+t)/2;
    msort(s,mid);
    msort(mid+1,t);
    int i=s,j=mid+1,k=s;
    while(i<=mid&&j<=t)
    {
        if(a[i]<=a[j])
        {
            r[k]=a[i];k++;i++;
        }
        else
        {
            r[k]=a[j];k++;j++;
            ans+=mid-i+1;//统计  
        }
    }
    while(i<=mid)
        {
            r[k]=a[i];k++;i++;
        }
    while(j<=t)
        {
            r[k]=a[j];k++;j++;
        }
        for(int i=s;i<=t;i++) a[i]=r[i];
    
  }

堆排序

堆排序基于堆这个数据结构,堆是一个完全二叉树,分为大(小)根堆,性质是父节点比子节点大(小),根节点最大 (小)
思想:每次从根节点取出最大(小),再将二叉树调整为堆,继续用堆的性质

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1000010
using namespace std;
int n,a[N];
void up(int x)///UP操作 
{
    while(x>1)
    {
        int y=x/2;
        if(a[y]>a[x])
        {
            swap(a[x],a[y]);
            x=y;
        }
        else break;
    }
}
void down(int x)//down操作 
{
    while(x*2<=n)
    {
        int y=x*2;
        if(x*2+1>n)
        {
            if(a[y]<a[x])
            {
                swap(a[x],a[y]);
                x=y;
            }
            else break;
        }
        else
        {
            int z=y+1;
            if(a[y]<a[z])
            {
                if(a[y]<a[x])
                {
                    swap(a[x],a[y]);
                    x=y;
                }
                else break;
            }
            else
            {
                if(a[z]<a[x])
                {
                    swap(a[x],a[z]);
                    x=z;
                }
                else break;
            }
        }
    }
}
void insert(int x)//添加 
{
    a[++n]=x;
    up(n);
}
void del(int x)//删去 
{
    a[x]=a[n];
    n--;
    if(x!=1&&a[x]<a[x/2]) up(x); 
    else down(x);
}
void build()//建堆 
{
    for(int i=n/2;i;i--) down(i);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    build();
    int m=n;
    for(int i=1;i<=m;i++)
    {
        int x=a[1];
        del(1);
        cout<<x<<' ';
    }
    cout<<endl;
}

二、非基于比较的排序

桶排序

思想:

若待排序的记录的关键字在一个明显有限范围内(整型)时,可设计有限个有序桶,每个桶装入一个值(当然也可以装入若干个值),顺序输出各桶的值,将得到有序的序列。

算法实现:

1.把每个待排序的数放入相应的桶内。统计每个桶内数据的个数。
2.从第0号桶开始依次输出桶内的数。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int main()
{
    int b[101],n,i,j,k,m=-100;//m表示这组数的最大值
    memset(b,0,sizeof(b));//初始化
    cin>>n;
    for(i=1;i<=n;i++)
    {
        cin>>k;b[k]++;m=max(m,k);
    }
    for(i=0;i<=m;i++) 
       while(b[i]>0)
       {
           cout<<i<<" ";b[i]--; 
        }
    cout<<endl;
}

缺点:当数据过于分散时,大量的时间和空间被浪费。

posted @ 2020-04-25 06:01  蒟蒻WZY  阅读(187)  评论(0)    收藏  举报