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

https://blog.csdn.net/qq_37220238/article/details/82933525

六种排序算法
一、冒泡排序
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

第一趟排序:

第二趟排序:

第三趟排序:

第四趟排序

第五趟排序:

  五趟结束后,6个整数就已经排序完成。排序过程中,大数慢慢的往后,相当于气泡上升,所以叫冒泡排序。
3.程序实现
**程序实现方法:**用两层循环完成算法,外层循环i控制每轮要进行多少次的比较,第1轮比较n-1次,第2轮比较n-2次,……,最后一轮比较1次。内层循环j控制每轮i次比较相邻两个元素是否逆序,若逆序就交换这两个元素。

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

#include<iostream>
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4.改进后的冒泡排序
  对于有些数据,我们发现,不一定要n-1次才能排完。例如1 5 2 3 4 6,我们发现只需一趟排序就可以将整个序列排完,于是,我们可以设置一个布尔变量,判断是否有进行交换,如果没有交换,说明已经排序完成,进而减少几趟排序。

#include<iostream>
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
二、选择排序
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<=500)
第二行:n个带排列的数(用空格隔开)

#include<iostream>
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
三、插入排序
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<=500)
第二行:n个带排列的数(用空格隔开)

#include<iostream>
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
四、桶排序
1.基本思想
桶排序的思想是若待排序的值在一个明显有限范围内(整型)时,可设计有限个有序桶,待排序的值装入对应的桶(当然也可以装入若干个值),桶号就是待排序的值,顺序输出各桶的值,将得到有序的序列。

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

#include<iostream>
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
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
五、快速排序
1.基本思想
快速排序是对冒泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

2.排序过程


3.程序实现
程序输入:
第一行:一个整数n(0<n<=500)
第二行:n个带排列的数(用空格隔开)

程序1(以最左边的元素为基准数)
#include <cstdio>
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
程序2(以中点元素为基准数)
#include<iostream>
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
六、归并排序
1.基本思想
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

2.排序过程
有8个数据需要排序:10 4 6 3 8 2 5 7
归并排序主要分两大步:分解、合并。

合并过程为:比较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<=500)
第二行:n个带排列的数(用空格隔开)

#include<iostream>
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
七、各种排序算法的比较
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较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序

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

  堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
--------------------- 
作者:柚子将军 
来源:CSDN 
原文:https://blog.csdn.net/qq_37220238/article/details/82933525 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/u014535295/article/details/84937993