排序总结

排序的概念:
所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。当待排序记录的关键字都不相同时,排序结果是惟一的,否则排序结果不惟一。

稳定性:
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。

排序的主要分类:
插入排序;
选择排序;
交换排序;
归并排序;
基数排序;

(一)插入排序
插入排序的基本思想是每步将一个待排序的记录按其排序码值的大小,插到前面已经排好的文件中的适当位置,直到全部插入完为止。插入排序方法主要有直接插入排序和希尔排序。

①.直接插入排序(稳定)
接插入排序的过程为:在插入第i个记录时,R1,R2,..Ri-1已经排好序,将第i个记录的排序码Ki依次和R1,R2,..,Ri-1的排序码逐个进行比较,找到适当的位置。使用直接插入排序,对于具有n个记录的文件,要进行n-1趟排序。

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int s[10005],n;
void Dir_Insert()
{
    int t,j;
    for(int i=1;i<n;i++)
    {
        t=s[i],j=i-1;
        while(s[j]>t)
        {
            s[j+1]=s[j];
            j--;
        }
        s[j+1]=t;
    }
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          s[i]=(rand()*maxn)/RAND_MAX+1;   //产生随机数,取值范围1~maxn
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
        Dir_Insert();
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
    }
    return 0;
}

②.希尔排序(不稳定):
希尔(Shell)排序的基本思想是:先取一个小于n的整数d1作为第一个增量把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取得第二个增量d2

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int s[10005],n;
void Shell1()                        //直接根据插入排序的思想的希尔排序
{
    for(int gap=n/2;gap>0;gap/=2)    //步长
      for(int i=0;i<gap;i++)         //直接插入排序
      {
          for(int j=i+gap;j<n;j+=gap)
            if(s[j]<s[j-gap])
            {
                int temp=s[j],k=j-gap;
                while(k>=0&&s[k]>temp)
                {
                    s[k+gap]=s[k];
                    k-=gap;
                }
                s[k+gap]=temp;
            }
      }
}

void Shell()                      //改进之后的希尔排序
{
    for(int gap=n/2;gap>0;gap/=2)
      for(int i=gap;i<n;i++)
        for(int j=i-gap;j>=0&&s[j]>s[j+gap];j-=gap)
          swap(s[j],s[j+gap]);    //每次交换,类似于把数据前移
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          s[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
        Shell();
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
    }
    return 0;
}

二.选择排序
选择排序的基本思想是每步从待排序的记录中选出排序码最小的记录,顺序存放在已排序的记录序列的后面,直到全部排完。选择排序中主要使用直接选择排序和堆排序。

①.直接选择排序(不稳定)
直接选择排序的过程是:首先在所有记录中选出序码最小的记录,把它与第1个记录交换,然后在其余的记录内选出排序码最小的记录,与第2个记录交换……依次类推,直到所有记录排完为止。
无论文件初始状态如何,在第i趟排序中选出最小关键字的记录,需要做n-i次比较,因此,总的比较次数为n(n-1)/2=O(n^2)。当初始文件为正序时,移动次数为0;文件初态为反序时,每趟排序均要执行交换操作,总的移动次数取最大值3(n-1)。直接选择排序的平均时间复杂度为O(n^2)。直接选择排序是不稳定的。

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int s[10005],n;
void Dir_Choose()
{
    for(int i=0;i<n-1;i++)
    {
        int k=i;
        for(int j=i+1;j<n;j++)
        {
            if(s[k]>s[j])
              k=j;
            if(k!=i)
              swap(s[k],s[i]);
        }
    }
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          s[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
        Dir_Choose();
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
    }
    return 0;
}

②.堆排序(不稳定)
堆排序是一种树形选择排序,是对直接选择排序的有效改进。n个关键字序列
K1,K2,…,Kn称为堆,当且仅当该序列满足(Ki<=K2i且Ki<=K2i+1)或(Ki>=K2i且Ki>=K2i+1),(1<=i<=n/2)。根结点(堆顶)的关键字是堆里所有结点关键字中最小者,称为小根堆;根结点的关键字是堆里所有结点关键字中最大者,称为大根堆。
若将此序列所存储的向量R[1..n]看作是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
堆排序的关键步骤有两个:一是如何建立初始堆;二是当堆的根结点与堆的最后一个结点交换后,如何对少了一个结点后的结点序列做调整,使之重新成为堆。堆排序的最坏时间复杂度为O(nlog2n),堆排序的平均性能较接近于最坏性能。由于建初始堆所需的比较 次数较多,所以堆排序不适宜于记录较少的文件。堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
void HeapAdjust(int *a,int i,int size)   //调整堆
{
    int lchild=2*i;       //i的左孩子节点序号
    int rchild=2*i+1;     //i的右孩子节点序号
    int max=i;            //临时变量
    if(i<=size/2)         //如果i不是叶节点就不用进行调整
    {
        if(lchild<=size&&a[lchild]>a[max])
          max=lchild;
        if(rchild<=size&&a[rchild]>a[max])
          max=rchild;
        if(max!=i)
        {
            swap(a[i],a[max]);
            HeapAdjust(a,max,size);      //避免调整之后以max为父节点的子树不是堆
        }
    }
}

void BuildHeap(int *a,int size)      //构建堆
{
    for(int i=size/2;i>=1;i--)     //非叶节点最大序号值为size/2
      HeapAdjust(a,i,size);
}

void HeapSort(int *a,int size)      //堆排序
{
    BuildHeap(a,size);
    for(int i=size;i>=1;i--)
    {
        swap(a[1],a[i]);      //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面
        BuildHeap(a,i-1);      //将余下元素重新建立为大顶堆
        HeapAdjust(a,1,i-1);   //重新调整堆顶节点成为大顶堆
    }
}

int main()
{
    int a[100];
    int size;
    srand((int)time(NULL));
    while(cin>>size)
    {
        if(size==0) break;
        for(int i=1;i<=size;i++)
          a[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=1;i<=size;i++)
          cout<<a[i]<<" ";
        cout<<endl;
        HeapSort(a,size);
        for(int i=1;i<=size;i++)
          cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}

三.交换排序
交换排序的基本思想是:两两比较待排序记录的排序码,并交换不满足顺序要求的那写偶对,直到满足条件为止。交换排序的主要方法有冒泡排序和快速排序.

①.冒泡排序(稳定的)
冒泡排序将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为ki的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R;凡扫描到违反本原则的轻气泡,就使其向上”漂浮”。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。
冒泡排序的具体过程如下:
第一步,先比较k1和k2,若k1>k2,则交换k1和k2所在的记录,否则不交换。继续对k2和k3重复上述过程,直到处理完kn-1和kn。这时最大的排序码记录转到了最后位置,称第1次起泡,共执行n-1次比较。
与第一步类似,从k1和k2开始比较,到kn-2和kn-1为止,共执行n-2次比较。
依次类推,共做n-1次起泡,完成整个排序过程。
若文件的初始状态是正序的,一趟扫描即可完成排序。所需关键字比较次数为n-1次,记录移动次数为0。因此,冒泡排序最好的时间复杂度为O(n)。
若初始文件是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1<=i<=n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较次数达到最大值n(n-1)/2=O(n^2),移动次数也达到最大值3n(n-1)/2=O(n^2)。因此,冒泡排序的最坏时间复杂度为O(n^2)。
虽然冒泡排序不一定要进行n-1趟,但由于它的记录移动次数较多,故平均性能比直接插入排序要差得多。冒泡排序是就地排序,且它是稳定的。

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int s[10005],n;
void QP()
{
    for(int i=0;i<n;i++)
      for(int j=i+1;j<n;j++)
        if(s[i]>s[j])
          swap(s[i],s[j]);
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          s[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
        QP();
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
    }
    return 0;
}

②.快速排序:(不稳定的)
快速排序采用了一种分治的策略,通常称其为分治法,其基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
快速排序的具体过程如下:
第一步,在待排序的n个记录中任取一个记录,以该记录的排序码为准,将所有记录分成两组,第1组各记录的排序码都小于等于该排序码,第2组各记录的排序码都大于该排序码,并把该记录排在这两组中间。
第二步,采用同样的方法,对左边的组和右边的组进行排序,直到所有记录都排到相应的位置为止。

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int s[10005],n;
void Quick_Sort(int l,int r)
{
    if(l<r)
    {
        int i=l,j=r,x=s[l];       //把左边第一个数填到坑x里
        while(i<j)
        {
            while(i<j&&s[j]>=x)    //从右向左找到第一个小于x的数
              j--;
            if(i<j)
              s[i++]=s[j];
            while(i<j&&s[i]<x)   //从左到右找到第一个大于等于x的数
              i++;
            if(i<j)
              s[j--]=s[i];
        }
        s[i]=x;          //把坑里面的交换过的数填到i中,腾出位置进行下一轮填数
        Quick_Sort(l,i-1);
        Quick_Sort(i+1,r);
    }
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          s[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
        Quick_Sort(0,n-1);
        for(int i=0;i<n;i++)
          cout<<s[i]<<" ";
        cout<<endl;
    }
    return 0;
}

四.归并排序
归并排序是将两个或两个以上的有序子表合并成一个新的有序表。初始时,把含有n个结点的待排序序列看作由n个长度都为1的有序子表组成,将它们依次两两归并得到长度为2的若干有序子表,再对它们两两合并。直到得到长度为n的有序表,排序结束。
归并排序是一种稳定的排序,可用顺序存储结构,也易于在链表上实现,对长度为n的文件,需进行log2n趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlog2n)。归并排序需要一个辅助向量来暂存两个有序子文件归并的结果,故其辅助空间复杂度为O(n),显然它不是就地排序。

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
#define maxn 1000+1
int a[10005],b[10005],n;
void MemeryArray(int a[],int b[],int l,int mid,int r)
{
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r)                     //比较排序合并两个数列
    {
        if(a[i]<=a[j])
          b[k++]=a[i++];
        else
          b[k++]=a[j++];
    }
    while(i<=mid)                          //处理较长的一个数列
      b[k++]=a[i++];
    while(j<=r)
      b[k++]=a[j++];
    for(i=0;i<k;i++)                       //最后合并的时候把数据全部放到a中
      a[l+i]=b[i];
}

void mergesort(int a[],int b[],int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)/2;
        mergesort(a,b,l,mid);                   //左边有序
        mergesort(a,b,mid+1,r);                 //右边有序
        MemeryArray(a,b,l,mid,r);               //将两个有序数列合并
    }
}

int main()
{
    srand((int)time(NULL));
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
          a[i]=(rand()*maxn)/RAND_MAX+1;
        for(int i=0;i<n;i++)
          cout<<a[i]<<" ";
        cout<<endl;
        mergesort(a,b,0,n-1);
        for(int i=0;i<n;i++)
          cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}

五.基数排序
设单关键字的每个分量的取值范围均是C0<=Kj<=Crd-1(0<=j<=rd),可能的取值个数rd称为基数.基数的选择和关键字的分解因关键字的类型而异.
  (1).若关键字是十进制整数,则按个、十等位进行分解,基数rd=10,C0=0,C9=9,d为最长整数的位数.
  (2).若关键字是小写的英文字符串,则rd=26,C0=’a’,C25=’z’,d为最长字符串的长度.
  基数排序的基本思想是:从低位到高位依次对待排序的关键码进行分配和收集,经过d趟分配和收集,就可以得到一个有序序列.

排序时间列表:

                名称         复杂度              说明                                               备注
冒泡排序     Bubble Sort     O(N*N)   将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮    
插入排序     Insertion sort  O(N*N)   逐一取出元素,在已经排序的元素序列中从后向前扫描,放到适当的位置    起初,已经排序的元素序列为空
选择排序                     O(N*N)   首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此递归。    
快速排序     Quick Sort  O(n *log2(n))  先选择中间值,然后把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使用这个过程(递归)。     
堆排序       HeapSort    O(n *log2(n)) 利用堆(heaps)这种数据结构来构造的一种排序算法。堆是一个近似完全二叉树结构,并同时满足堆属性:即子节点的键值或索引总是小于(或者大于)它的父节点。    近似完全二叉树
希尔排序     SHELL       O(n1+£)0<£<1   选择一个步长(Step) ,然后按间隔为步长的单元进行排序.递归,步长逐渐变小,直至为1.    
箱排序      Bin Sort          O(n)      设置若干个箱子,把关键字等于 k 的记录全都装入到第k 个箱子里 ( 分配 ) ,然后按序号依次将各非空的箱子首尾连接起来 ( 收集 ) 。 分配排序的一种:通过" 分配 " 和 " 收集 " 过程来实现排序。
桶排序     Bucket Sort        O(n)      桶排序的思想是把 [0 , 1) 划分为 n 个大小相同的子区间,每一子区间是一个桶。    

时间比较

排序法 平均时间    最差情形    稳定度    额外空间               备注
冒泡   O(n2)      O(n2)       稳定       O(1)              n小时较好
交换   O(n2)      O(n2)       不稳定     O(1)              n小时较好
选择   O(n2)      O(n2)       不稳定     O(1)              n小时较好
插入   O(n2)      O(n2)       稳定       O(1)             大部分已排序时较好
基数   O(logRB)   O(logRB)    稳定       O(n)         B是真数(0-9),R是基数(个十百)
希尔   O(nlogn)   O(ns) 1<s<2 不稳定     O(1)             s是所选分组
快速   O(nlogn)   O(n2)       不稳定     O(nlogn)         n大时较好

排序是处理数据的入门课,分析排序的方法也是分析算法的思想,寓教于乐,希望以后不会忘了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值