排序算法 (4)

2016/12/09 C

常见的排序算法

排序算法 (4)

其他排序

简单选择排序

概念

简单选择排序:就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换

C实现


    void SelectSort(int a[],int len)
    {
        int i,j,min;
        for (i=0;i<len;i++)
        {
          min=i;
          for (j=i+1;j<len;j++)
          {
              if (a[min]>a[j])
                  min=j;
          }
          if (i!=min)
              swap(&a[min],&a[i]);
        }
    }

分析

简单选择排序复杂度分析:它的比较次数一定:n(n-1)/2。也因此无论在序列何种情况下,它都不会有优秀的表现,可见对数据的有序性不敏感。它虽然比较次数多,而对于交换次数而言,当最好的时候,交换为0次,最坏的时候交换次数为n-1次,所以数据交换量却很少,基于时间复杂度是比较与交换次数的总和,因此总的时间复杂度依然为O(n^2)。尽管与冒泡排序算法的时间复杂度相同,但是简单选择排序的性能上还是略优于冒泡排序。

堆排序

概念

首先将待排序列构建为大顶堆(大顶堆:每个结点的值大于或等于其左右孩子结点的值)。此时整个序列的最大值就是堆顶的根结点。将它移走(其实就是讲其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个子序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便得到一个有序序列。

C实现


    void HeapAdjust(int a[],int s,int m)
    {
          int temp,j=0;
          temp=a[s];
          for ( j=2*s+1; j<m; j=j*2+1 )   
          {
            if (j<m && a[j]<a[j+1])
                ++j;
            if (j<m && temp>=a[j])
                break;
            a[s]=a[j];
            s=j;
          }
          a[s]=temp;
    }
    void HeapSort(int a[],int len)
    {
          int i;
          for (i=(len)/2-1;i>=0;i--)
               HeapAdjust(a,i,len);
          for (i=len-1;i>=1;i--)
          {
               swap(&a[0],&a[i]);
               HeapAdjust(a,0,i-1);
          }
    }

分析

在构建堆的过程中,因为是完全二叉树从最下层最后边的非终端结点开始构建,将它与其孩子进行比较和若有必要的交换,对于每个非终端结点来说,其中最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。.

在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根节点的距离为[logi]+1),并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。

所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此无论最好、最坏平均时间复杂度均为O(nlogn)。

归并排序

思想

假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度的长度为1,然而两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,…..,如此重复,直至得到一个长度为n的有序序列为止。

C实现


      /*将有序的SR归并到TR中*/
    void Merge(int SR[],int TR[],int i,int m,int n)
    {
          int j,k,l;
          for (j=m+1,k=i;i<=m && j<=n;k++)  /*对1...m与m+1...n中等长的数组比较大小,并且保存较小元素*/
          {
              if (SR[i]<SR[j])
                  TR[k]=SR[i++];
              else 
                  TR[k]=SR[j++];
          }
          if (i<=m)  /*将SR[1...m]中多余中的部分添加到TR中*/
          {
              for (l=0;l<=m-i;l++)
                  TR[k+l]=SR[i+l];
          }
          if (j<=n)    /*将SR[m+1...n]中多余中的部分添加到TR中*/
          {
              for (l=0;l<=n-j;l++)
                  TR[k+l]=SR[j+l];
          }
    }
    /*将SR归并排序为TR1*/
    void MSort(int SR[],int TR1[],int s,int t)
    {
        int m=0;
        int TR2[length+1]={0};
        if (s==t)
            TR1[s]=SR[s];
        else
        {
            m=(s+t)/2;
            MSort(SR,TR2,s,m);
            MSort(SR,TR2,m+1,t);
            Merge(TR2,TR1,s,m,t);
        }
    }

分析

一趟归并需要将SR[0]~SR[n-1]中相邻的长度为h的有序序列进行两两归并。并将结果放到TR[0]~TR[n-1]中,这需要将待排序列中的记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行[log(n)]次,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。并且归并排序是一种稳定的排序算法。对数据的有序性不敏感。若数据节点数据量大,那将不适合。但可改造成索引操作,效果将非常出色。

按平均时间将排序分为类:

  • (1) 平方阶(O(n2))排序   各类简单排序,例如直接插入、简单选择和冒泡排序;
  • (2) 线性对数阶(O(nlog2n))排序   如快速排序、堆排序和归并排序;
  • (3) O(n1+§))排序   §是介于0和1之间的常数。希尔排序便是一种;

排序方法的选择

  • 因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法很重要

(1)若n较小,可采用直接插入或直接选择排序。

当记录规模较小时,直接插入排序较好,它会比选择更少的比较次数; 但当记录规模较大时,因为直接选择移动的记录数少于直接插人,所以宜用选直接选择排序。 这两种都是稳定排序算法。

(2)若文件初始状态基本有序(指正序),

则应选用直接插人、冒泡或随机的快速排序为宜 (这里的随机是指基准取值的随机,原因见上的快速排序分析); 这里快速排序算法将不稳定。

(3)若n较大,则应采用时间复杂度为O(nlog2n)的排序方法: 快速排序、堆排序或归并排序序。

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

堆排序虽不会出现快速排序可能出现的最坏情况。但它需要建堆的过程。这两种排序都是不稳定的。

  归并排序是稳定的排序算法,但它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

sort - 1

Show Disqus Comments

Search

    Post Directory