信息学奥赛中常用的六种排序算法

六种排序算法

  • 一、冒泡排序
    • 1.基本思想
    • 2.排序过程
    • 3.程序实现
    • 4.改进后的冒泡排序
  • 二、选择排序
    • 1.基本思想
    • 2. 排序过程
    • 3.程序实现
  • 三、插入排序
    • 1.基本思想
    • 2.排序过程
    • 3.程序实现
  • 四、桶排序
    • 1.基本思想
    • 2.程序实现
  • 五、快速排序
    • 1.基本思想
    • 2.排序过程
    • 3.程序实现
      • 程序1(以最左边的元素为基准数)
      • 程序2(以中点元素为基准数)
  • 六、归并排序
    • 1.基本思想
    • 2.排序过程
    • 3.程序实现
  • 七、各种排序算法的比较
    • 1.稳定性比较
    • 2.时间复杂性比较
    • 3.辅助空间的比较
    • 4.其它比较

一、冒泡排序

1.基本思想

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。

2.排序过程

有6个元素需要排序: 6 5 3 4 1 2

  1. 第一趟排序:
    信息学奥赛中常用的六种排序算法_第1张图片
  2. 第二趟排序:
    信息学奥赛中常用的六种排序算法_第2张图片
  3. 第三趟排序:
    信息学奥赛中常用的六种排序算法_第3张图片
  4. 第四趟排序
    信息学奥赛中常用的六种排序算法_第4张图片
  5. 第五趟排序:
    在这里插入图片描述
      五趟结束后,6个整数就已经排序完成。排序过程中,大数慢慢的往后,相当于气泡上升,所以叫冒泡排序。

3.程序实现

**程序实现方法:**用两层循环完成算法,外层循环i控制每轮要进行多少次的比较,第1轮比较n-1次,第2轮比较n-2次,……,最后一轮比较1次。内层循环j控制每轮i次比较相邻两个元素是否逆序,若逆序就交换这两个元素。

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数(用空格隔开)

#include
using namespace std;
const int maxn=505;
int n,a[maxn];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=n-1;i>=1;i--)//第i轮排序
    {
        for(int j=1;j<=i;j++)
            if(a[j]>a[j+1])//比较相邻元素大小
                swap(a[j],a[j+1]);
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

4.改进后的冒泡排序

  对于有些数据,我们发现,不一定要n-1次才能排完。例如1 5 2 3 4 6,我们发现只需一趟排序就可以将整个序列排完,于是,我们可以设置一个布尔变量,判断是否有进行交换,如果没有交换,说明已经排序完成,进而减少几趟排序。

#include
using namespace std;
const int maxn=505;
int n,a[maxn];
bool flag;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=n-1;i>=1;i--)//第i轮排序
    {
        flag=0;
        for(int j=1;j<=i;j++)
            if(a[j]>a[j+1])//比较相邻元素大小
            {
                flag=1;
                swap(a[j],a[j+1]);
            }
        if(flag==0)//没有发生交换,说明已经排好序
            break;
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

二、选择排序

1.基本思想

  每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。

2. 排序过程

对以下序列进行排序:
49 38 65 97 76 13 27 49
第一趟排序后 13 [38 65 97 76 49 27 49]
第二趟排序后 13 27 [65 97 76 49 38 49]
第三趟排序后 13 27 38 [97 76 49 65 49]
第四趟排序后 13 27 38 49 [76 97 65 49]
第五趟排序后 13 27 38 49 49 [97 65 76]
第六趟排序后 13 27 38 49 49 65 [97 76]
第七趟排序后 13 27 38 49 49 65 76 [97]
最后排序结果 13 27 38 49 49 65 76 97

3.程序实现

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数(用空格隔开)

#include
using namespace std;
const int maxn=505;
int n,a[maxn],t,MIN,MINA;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n-1;i++)
    {
        MIN=i,MINA=a[i];
        for(int j=i+1;j<=n;j++)
        {
            if(a[j]<MINA)
            {
                MIN=j;//找出后面的最小值和最小值的坐标
                MINA=a[j];
            }
        }
        swap(a[i],a[MIN]);//把最小值放在前面
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

三、插入排序

1.基本思想

当读入一个元素时,在已经排序好的序列中,搜寻它正确的位置,再放入读入的元素。但不该忽略一个重要的问题:在插入这个元素前,应当先将将它后面的所有元素后移一位,以保证插入位置的原元素不被覆盖。

2.排序过程

例如:设n=8,数组a中8个元素是: 36,25,48,12,65,43,20,58,执行插入排序程序后,其数据变动情况:
第0步:[36] 25 48 12 65 43 20 58
第1步:[25 36] 48 12 65 43 20 58
第2步:[25 36 48] 12 65 43 20 58
第3步:[12 25 36 48] 65 43 20 58
第4步:[12 25 36 48 65] 43 20 58
第5步:[12 25 36 43 48 65] 20 58
第6步:[12 20 25 36 43 48 65] 58
第7步:[12 20 25 36 43 48 58 65]

3.程序实现

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数(用空格隔开)

#include
using namespace std;
const int maxn=505;
int n,a[maxn],tmp;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        int j;
        for(j=i-1;j>=1;j--)
            if(a[j]<a[i])//找到第一个比a[i]小的元素位置j,插入位置为j+1
                break;
        if(j!=i-1)
        {
            tmp=a[i];//存储插入元素的值
            for(int k=i-1;k>=j+1;k--)//j+1到i-1的元素后移
                a[k+1]=a[k];
            a[j+1]=tmp;//插入
        }
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

四、桶排序

1.基本思想

桶排序的思想是若待排序的值在一个明显有限范围内(整型)时,可设计有限个有序桶,待排序的值装入对应的桶(当然也可以装入若干个值),桶号就是待排序的值,顺序输出各桶的值,将得到有序的序列。

2.程序实现

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数a[i](-10000<=a[i]<=10000)(用空格隔开)

#include
using namespace std;
const int maxn=10005;
int n,a[2*maxn],MAXA=-1,num;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>num;
        a[num+maxn]++;//把相对应的整数放在对应的桶中
        MAXA=max(MAXA,num+maxn);//求出所有桶中数的最大值,排除空桶
    }
    for(int i=0;i<=MAXA;i++)
    {
        while(a[i]!=0)
        {
            cout<<i-maxn<<" ";
            a[i]--;
        }
    }
    return 0;
}

五、快速排序

1.基本思想

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

2.排序过程

3.程序实现

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数(用空格隔开)

程序1(以最左边的元素为基准数)

#include 
int a[505],n;//定义全局变量,这两个变量需要在子函数中使用
void quicksort(int left,int right)
{
    int i,j,t,temp;
    if(left>right)
       return;

    temp=a[left]; //temp中存的就是基准数
    i=left;
    j=right;
    while(i!=j)
    {
                   //顺序很重要,要先从右边开始找
                   while(a[j]>=temp && i<j)
                            j--;
                   //再找右边的
                   while(a[i]<=temp && i<j)
                            i++;
                   //交换两个数在数组中的位置
                   if(i<j)
                   {
                            t=a[i];
                            a[i]=a[j];
                            a[j]=t;
                   }
    }
    //最终将基准数归位
    a[left]=a[i];
    a[i]=temp;

    quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程
    quicksort(i+1,right);//继续处理右边的 ,这里是一个递归的过程
}
int main()
{
    int i,j,t;
    //读入数据
    scanf("%d",&n);
    for(i=1;i<=n;i++)
                   scanf("%d",&a[i]);
    quicksort(1,n); //快速排序调用

    //输出排序后的结果
    for(i=1;i<=n;i++)
        printf("%d ",a[i]);
    return 0;
}

程序2(以中点元素为基准数)

#include
using namespace std;
const int maxn=505;
int n,a[maxn];
void qsort(int l,int r)
{
    int i,j,mid;
    i=l,j=r;//i、j为当前序列的左右端点
    mid=a[(l+r)/2];//mid为此序列的基准数
    while(i<j)
    {
        while(a[i]<mid)i++;//从左往右找到第一个大于等于基准数的元素a[i]
        while(a[j]>mid)j--;//从右往左找到第一个小于等于基准数的元素a[j]
        if(i<=j)
        {
            swap(a[i],a[j]);//交换两个数
            i++;//继续寻找
            j--;
        }
    }
    if(l<j)//基准数左边序列
        qsort(l,j);
    if(i<r)//基准数右边序列
        qsort(i,r);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    qsort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

六、归并排序

1.基本思想

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

2.排序过程

有8个数据需要排序:10 4 6 3 8 2 5 7
归并排序主要分两大步:分解、合并。
信息学奥赛中常用的六种排序算法_第5张图片
合并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

3.程序实现

程序输入:
第一行:一个整数n(0 第二行:n个带排列的数(用空格隔开)

#include
using namespace std;
const int maxn=505;
int n,a[maxn],b[maxn];//b为辅助数组
void msort(int l,int r)
{
    if(l==r)//递归出口
        return;
    int mid=(l+r)/2;
    msort(l,mid);//左边序列
    msort(mid+1,r);//右边序列
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)//左右序列进行合并
    {
        if(a[i]<=a[j])//把小的元素放在前面
            b[k]=a[i++];
        else
            b[k]=a[j++];
        k++;
    }
    while(i<=mid)//如果有剩余序列,直接加在辅助数组最后面
        b[k++]=a[i++];
    while(j<=r)
        b[k++]=a[j++];
    for(int i=l;i<=r;i++)//赋值已经排好序的序列给a数组
        a[i]=b[i];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    msort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

七、各种排序算法的比较

1.稳定性比较

**稳定排序:**插入排序、冒泡排序、二叉树排序、二路归并排序及其他线形排序是稳定的;
**不稳定排序:**选择排序、希尔排序、快速排序、堆排序是不稳定的。

2.时间复杂性比较

  插入排序、冒泡排序、选择排序的时间复杂性为O(n^2);快速排序、堆排序、归并排序的时间复杂性为O(nlog2n);桶排序的时间复杂性为O(n);

  若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它算法的最好情况同平均情况相同;若从最坏情况考虑,则快速排序的时间复杂度为O(n2),直接插入排序和冒泡排序虽然平均情况相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。

  由此可知,在最好情况下,直接插入排序和冒泡排序最快;在平均情况下,快速排序最快;在最坏情况下,堆排序和归并排序最快。

3.辅助空间的比较

  桶排序、二路归并排序的辅助空间为O(n),快速排序的辅助空间为O(log2n),最坏情况为O(n),其它排序的辅助空间为O(1);

4.其它比较

  插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种情况下,快速排序反而慢了。

  当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

  若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。

  当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

  当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序

  快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

  堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。

你可能感兴趣的:(排序算法)