排序

(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

//选择排序
#include<cstdio>
using namespace std;
const int maxn = 100005;
int a[maxn],n,cnt;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)
        scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++)
    {
        cnt = i;
        for(int j = i + 1;j <= n;j++)
            if(a[j] < a[cnt])
                cnt = j;
        if(cnt != i)
        {
            int tmp = a[i];
            a[i] = a[cnt];
            a[cnt] = tmp;
        }
    }
    for(int i = 1;i <= n;i++)
        printf("%d ",a[i]);
    return 0;
}

二、冒泡排序

(1)基本思想:

以n个人站队为例

从第1个开始,依次比较相邻的两个是否逆序对(高在前,矮在后)

若逆序就交换这两人,即第1个和第2个比,若逆序就交换两人,接着第2个和第3个比,若逆序就交换两人,接着第3个和第4个比,若逆序就交换两人,……

直到n-1和n比较,经过一轮比较后,则把最高的人排到最后,即将最高的人像冒泡一样逐步冒到相应的位置

原n个人的排序问题,转换为n-1个人的排序问题。

第二轮从第1个开始,依次比较相邻的两个人是否逆序对,若逆序就交换两人,直到n-2和n-1比较。

如此,进行n-1轮后,队列为有序的队列。

(2)排序过程

例如有6个元素需要排序:

          6 5 3 4 1 2

1.第一趟排序

2.第二趟排序

3.第三趟排序

4.第四趟排序

5.第五趟排序

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int n,a[maxn];
int main()
{
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
        scanf("%d",&a[i]);
    for(int i = n-1;i >= 1;i--)
    {
        for(int j = 0;j < i;j++)
        {
            if(a[j] > a[j + 1])
                swap(a[j],a[j + 1]);
        }
    }
    for(int i = 0;i < n;i++)
        printf("%d ",a[i]);
    return 0;
} 

冒泡的优化

对于有些数据,我们发现,不一定要n-1次才能排完。

例如1 5 2 3 4 6,我们发现只需一趟排序就可以将整个序列排完

于是,我们可以设置一个布尔变量,判断是否有进行交换,如果没有交换,说明已经排序完成,进而减少几趟排序。

#include<cstdio>
#include<algorithm> 
using namespace std;
const int maxn = 100005;
int n,a[maxn];
bool opt;
int main()
{
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
        scanf("%d",&a[i]);
    for(int i = n - 1;i >= 1;i--)
    {
        opt = true;
        for(int j = 0;j < i;j++)
        {
            if(a[j] >= a[j + 1])
            {
                swap(a[j],a[j + 1]);
                opt = false;
            }
        }
        if(opt == true)
            break;
    }
    for(int i = 0;i < n;i++)
        printf("%d ",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]

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10005;
int n,a[maxn];
int main()
{
    int i,j,k;
    scanf("%d",&n);
    for(i = 0; i < n; i++)
        scanf("%d",&a[i]);
    for(i = 0; i < n; i++)
    {
        for(j = i - 1; j >= 0; j--)
            if(a[j] < a[i])
                break;
        if(j != i - 1)
        {
            int tmp = a[i];
            for(k = i - 1;k > j;k--)
                a[k + 1] = a[k];
            a[k + 1] = tmp;
        }
    }
    for(int i = 0;i < n;i++)
        printf("%d ",a[i]);
    return 0;
}

四、桶排序

(1)基本思想:

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

#include<cstdio>
#include<algorithm>
using namespace std;
int b[101],n,k;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&k);
        b[k]++;
    }
    for(int i = 0;i <= 100;i++)
        while(b[i])
        {
            printf("%d ",i);
            b[i]--;
        }
    return 0;
}

五、快速排序

(1)基本思想:

通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
(2)排序过程:

假设待排序的序列为{a[L],a[L+1],a[L+2],……,a[R]},首先任意选取一个记录(通常可选中间一个记作为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中,所有关键字大于它的记录都放在右子序列中。由此可以将该“支点”记录所在的位置mid作分界线,将序列分割成两个子序列和。这个过程称作一趟快速排序(或一次划分)。

一趟快速排序的具体做法是:附设两个指针i和j,它们的初值分别为L和R,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于的mid的记录,然后从i所指位置起向后搜索,找到第一个关键字大于mid的记录,将它们互相交换,重复这两步直至i>j为止。

 快速排序的时间的复杂性是O(nlog2n),速度快,但它是不稳定的排序方法。就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法

由以上讨论可知,从时间上看,快速排序的平均性能优于前面讨论过的各种排序方法,但快速排序需一个栈空间来实现递归。若每一趟排序都将记录序列均匀地分割成长度相接近的两个子序列,则栈的最大深度为log(n+1)。

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

六、归并排序

(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]。

#include<cstdio>
#define ll long long 
using namespace std;

const int maxn=5e5+5;
int a[maxn],r[maxn],n;
ll ans=0;

void msort(int s,int t)
{
    if(s==t) 
        return;
    int mid=s+t>>1;
    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++];
        else 
            r[k++]=a[j++],ans+=(ll)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];
}
inline int read()
{
    char ch=getchar();
    int sum=0,k=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') 
            k=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') 
        sum=sum*10+(ch^48),ch=getchar();
    return sum*k;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
        a[i]=read();
    msort(1,n);
    printf("%lld
",ans);
    return 0;
}

各种排序算法的比较

1.稳定性比较

       插入排序、冒泡排序、二叉树排序、二路归并排序及其他线形排序是稳定的。

       选择排序、希尔排序、快速排序、堆排序是不稳定的。

2.时间复杂性比较

插入排序、冒泡排序、选择排序的时间复杂性为O(n2);

快速排序、堆排序、归并排序的时间复杂性为O(nlog2n);桶排序的时间复杂性为O(n);

若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n)

其它算法的最好情况同平均情况相同;

若从最坏情况考虑,则快速排序的时间复杂度为O(n2),直接插入排序和冒泡排序虽然平均情况相

但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。

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

3.辅助空间的比较

桶排序、二路归并排序的辅助空间为O(n)

快速排序的辅助空间为O(log2n),最坏情况为O(n)

其它排序的辅助空间为O(1);

4.其它比较

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

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

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

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

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

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

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

原文地址:https://www.cnblogs.com/darlingroot/p/10802729.html