zoukankan      html  css  js  c++  java
  • 排序总结

    引入

    在待排序的文件中,可能存在着多个具有相同排序码的记录。如果一个排序算法对于任意具有相同排序码的多个记录在排序之后,这些具有相同排序码的记录的相对次序仍保持不变,则称该排序算法为“稳定的”,否则称该排序算法是不稳定的。下面将介绍一些比较熟知的排序算法:

    插入排序

    基本思想:每次选择带排序的记录序列的第一个记录,按照排序码的大小将其插入已排序的记录序列中的适当位置,直到所有的记录全部排序完毕。

    直接插入排序

    //用直接插入排序法对A[i](i取0到n-1)进行排序
    template <class T>
    void DirectInsertionSort(T A[],int n)
    {
    int i,j;
    T temp;
    for(i=1;i<n;i++)
      {
    //记录A[0]到A[i-1]已排序,当前要插入的记录是A[i]
      j=i-1;
      temp=A[i];
     //若temp的排序码小于A[j]的排序码且还未到记录A[0],则继续扫描
     while(j>=0&&temp.key<A[j].key)
         {
          //右移当前记录
          A[j+1]=A[j];
          j--;
         }
     //找到位置,将temp插入     
      A[j+1]=temp.key;
      }
    }
    

    算法复杂度为O((n^{2})),且排序是稳定的。

    折半插入排序

    //用折半插入排序法对A[i]进行排序
    template <class T>
    void BinayInsertSort(T A[],int n)
    {
      int i,k,r;
      T temp;
      for(int i=1;i<n;i++)
       {
       temp=A[i];
       //采用折半法在已排序的A[0]~A[i-1]之间找A[i]的插入位置
       k=0;r=i-1;
       while(k<=r)
       {
       int m;
       m=(k+r)/2;
       if(temp.key<A[m].key)
         {
         //在前半部分继续找插入位置
         r=m-1;
         }
        else 
        {
        //在后半部分继续找插入位置
        k=m+1;
        }
       }
       // 找到插入位置k,先将A[k]~A[i-1]右移一个位置
       for(r=i;r>k;r--)
         {
         A[r]=A[r-1];
         }
        //将temp插入
        A[k]=temp;
     }
    }
    

    使用折半插入排序时,需进行的比较次数与记录的初始状态无关,仅依赖与记录的个数。时间复杂度为O((nlog_{2}n))

    Shell排序(希尔排序法)

    //用Shell排序法对A[0]~A[n-1]进行排序
    template <class T>
    void ShellSort(T A[],int n,int s)
    {
     int i,j,k;
     T temp;
     //分组排序,初始增量为s,每循环一次增量减半,直到增量为0时结束
     for(k=s;k>0;k>>=1)  //k>>1即右移一位,等价于/2即减半。
     {
     //分组排序
     for(i=k;i<n;i++)
        {
        temp=A[i];
        j=i-k;
        //组内排序,将temp直接插入组内合适的记录位置
        while(j>=0&&temp.key<A[j].key)
         {
         A[j+k]=A[j];
         j-=k;
         }
        A[j+k]=temp;
        }
     }
    }
    

    时间复杂度为O((n^{3/2})),且排序是不稳定的。

    选择排序

    基本思想:每次从待排序的记录中选出排序码最小的记录,然后在剩下的记录中选出次最小的记录,重复这个过程,直到完成全部排序。

    直接选择排序

    template <class T>
    void DirectSelectSort(T A[],int n)
    {
    int i,j,k;
    T temp;
    for(i=0;i<n-1;i++)
      {
      k=i;
      //只负责找到位置,用k来记录位置
      for(j=i+1;j<n;j++)
        {
        if(A[j].key<A[k].key)
          k=j;
        }
        //这步才是交换的操作
       if(i!=k)
       {
        temp=A[k];
        A[k]=A[i];
        A[i]=temp;
       }
      }
    }
    

    时间复杂度为O((n^{2})),选择排序是不稳定的

    树形选择排序

    交换排序

    基本思想:每次将排序文件中的两个记录的排序码进行比较,如果不满足排序要求,则交换这两个记录在文件中的顺序,直到文件中任意两个记录之间都满足排序要求未知。

    冒泡排序

    //用冒泡排序法对A[0]~A[n-1]排序
    template <class T>
    void BubbleSort(T A[],int n)
    {
    int i,j;
    boolean flag;
    T temp;
    for(i=n-1,flag=(boolean)1;i>0&&flag;i--)
     {
    //设置未交换标志
      flag=FALSE;
      for(j=0;j<i;j++)
      {
      if(A[j+1].key<A[j].key)
        {
        //有交换法神,置标志
        flag=TRUE;
        //交换
        temp=A[j+1];
        A[j+1]=A[j];
        A[j]=temp;
        }
      }
     }
    }
    

    冒泡排序是稳定的

    快速排序

    //用快速排序法对文件中的一组记录A[low]~A[high]进行排序
    template <class T>
    void QuickSort(T A[],int low,int high)
    {
    int i,j;
    T temp;
    if(low>=high) return;
    i=low;j=high;temp=A[i];
    while(i<j)
      {
      //从后往前进行比较,直到当前记录的排序码小于等于中心值
      while(i<j&&A[i].key<=temp.key) j--;
      if(i<j)
       {
       //将排序码小于等于中心值的记录,交换到前面当前空出来的记录位置
       A[i++]=A[j];
       }
      //从前往后进行比较,直到当前记录的排序码大于等于中心值
      while(i<j&&A[i].key<=temp.key) i++;
      if(i<j)
      {
      //将排序码大于中心值的记录交换到后面当前空出的记录位置
      A[j--]=A[i];
      }
     }
     //找到中心值对应的记录所在的位置,写入中心值对应的记录
     A[i]=temp;
     //递归处理排序码小于等于中心值的那组记录
     QuickSort(A,low,--j);
     //递归处理排序码大于等于中心值的那组记录
     QuickSort(A,++i,high);
    }
    

    时间复杂度为O((log_{2}n)),快速排序算法是不稳定的。

    分配排序

    基本思想:先分配,后收集

    基数排序

    //用基数排序法对一单链表形式存储的文件中的一组记录进行排序
    template <class T>
    void RadixSort(T *pData,int Clow,int Chigh,int d)
    {
    typedef struct
      {
      T *pHead;
      T *pTail;
      }TempLink;
      int r=Chigh-Clow+1;
      //分配队列
      TempLink *tlink;
      T *p;
      //为分配队列分配内存
      tlink=new TempLink[r];
      for(int i=d-1;i>=0;i--)
       {
       //初始化分配队列指针
       for(int j=0;j<r;j++)
       {
       Tlink[j].pHead=tlink[j].pTail=NULL;
       }
       //将记录分配到r个队列中
       for(p=pData->next;p!=NULL;p=p->next)
       {
       j=p->key[i]-Clow;
       if(tlink[j].pHead==NULL)
        {
        Tlink[j].pHead=tlink[j].pTail=p;
        }
       else
       {
       Tlink[j].pTail->next=p;
       Tlink[j].pTail=p;
       }
       }
     //收集分配到r个队列中的记录
     for(j=0,p=pData;j<r;j++)
     {
     if(tlink[j].pHead!=NULL)
       {
       p->next=tlink[j].pHead;
       p=tlink[j].pTail;
       }
     }
     //将单链表尾部结点的指针置为空
     p->next=NULL;
    }
    //释放分配队列占用的内存
    delete[]tlink;
    }
    

    时间复杂度为O((n)),基数排序算法是稳定的。

    归并排序

    基本思想:将已经排序的子文件进行合并,得到完全排序的文件。合并是只要比较各自文件的第一个记录的排序码,排序码最小的那个记录就是排序后的第一个记录,去除这个记录,然后继续比较各自文件的第一个记录。便可找出排序后的第二个记录。如此反复,对每个字文件警告过一趟扫描,便可得到最终的排序结果。

    //用二路归并排序对A[0]~A[n-1]进行排序
    template <class T>
    void MergeSort(T A[],int n)
    {
    int k;
    T *B=new T[n];
    //初始子文件长度为1
    k=1;
    while(k<n)
      {
      //将A中的子文件经过一趟归并存储到数组B中
      OnePassMerge(B,A,k,n);
      //归并后子文件长度增加1倍
      k<<=1;
      if(k>=n)
       {
       //一归并排序完毕,但结果在临时数组B中
       //调用标准函数memcpy()将其复制到A中
       //memcpy()包含在头文件<Mmemory.h>或<string.h>中
       
       memcpy(A,B,n*sizeof(T));
       }
       else
       {
       //将B中的子文件经过一趟归并存储到数组A中
       onePassMerge(A,B,k,n);
       //归并后子文件长度增加一倍
       k<<=1;
       }
      }
      //释放临时数组占用的内存空间
      delete []B;
    }
    
    //一趟归并函数,jiangSrc中部分排序的多个子文件鬼领导Dst中
    //子文件的长度为Len
    void OnePassMerge(T Dst[],T Src[],int Len,int n)
    {
    for(int i=0;i<n-2*Len;i+=2*Len)
      {
      //执行两两归并,将Src中长度为Len的子文件归并成长度为2*Len的子文件,结果存放在Dst中
      TwoWayMerge(Dst,Src,i,i+Len-1,n-1);
      }
      else
      {
      //尾部之多还有一个子文件,直接复制到Dst中
      memcpy(&Dst[i],&Src[i],(n-1)*sizeof(T));
      }
    }
    
    //两两归并函数,将Src中从s到e1的子文件和从e1+1到e2的子文件进行归并,结果存放到Dst中从s开始的位置
    void TwoWayMerge(T Dst[],T Src[],int s,int e1,int e2)
    {
    for(int s1=s,s2=e1+1;s1<=e1&&s2<=e2;)
      {
       if(Src[s1].key<=Src[s2].key)
         {
         //第一个子文件的最前面的记录器排序码小,将其归并到Dst中
         Dst[s++]=Src[s1++];
         }
         else
         {
         //第二各自文件的最前面的记录其排序码小,将其归并到Dst中
          Dst[s++]=Src[s2++];
         }
      }
      if(s1<=e1)
      {
      //第一个子文件未归并完,将其直接复制到Dst中
      memcpy(&Dst[s],&Src[s1],(e1-s1+1)*sizeof(T));
      }
      else
      {
       //第二个子文件未归并完,将其直接复制到Dst中
      memcpy(&Dst[s],&Src[s2],(e2-s2+1)*sizeof(T));
      }
    }
    

    总的时间复杂度为O((n*log_{2}n)),归并排序算法需要n个附加存储空间。

    作者:YunLambert

    -------------------------------------------

    个性签名:一名会音乐、爱健身的不合格程序员

    可以Follow博主的Github哦(っ•̀ω•́)っ✎⁾⁾

  • 相关阅读:
    C语言之内存分配函数
    C语言const
    【Hihocoder 1167】 高等理论计算机科学 (树链的交,线段树或树状数组维护区间和)
    【HDU 1828】 Picture (矩阵周长并,线段树,扫描法)
    【 HDU 1255】 覆盖的面积(矩阵面积交,线段树,扫描法)
    【HDU 1542】Atlantis 矩形面积并(线段树,扫描法)
    【BZOJ 2333 】[SCOI2011]棘手的操作(离线+线段树|可并堆-左偏树)
    【20161108】总结
    【BZOJ 3110】 [Zjoi2013]K大数查询(整体二分)
    【Bzoj 3295】 动态逆序对(树套树|CDQ分治)
  • 原文地址:https://www.cnblogs.com/yunlambert/p/7869060.html
Copyright © 2011-2022 走看看