zoukankan      html  css  js  c++  java
  • 所有排序总结(内排序)(续)——线性时间排序

     

    一、前言

          这里稍微证明一下基于比较的排序算法的下界,采用决策树模型。下图是一个含三个元素的输入序列,采用插入排序算法的决策树。每一个结点里面包含(i,j)。左子树代表i<=j的情况,右子树代表i>j的情况。

          各位,如果有跟我一样,半天看不懂的话,我就稍微解释一下。拿(1,2,3)这个序列的得出,稍作说明。在根节点处,1和2比较(输入位置为1的元素与输入位置为2的元素比较,不是1和2比较,下同)。 1<2,进入到左子树。  2和3比较,  2<3,还是到左子树   得出排序后的序列(1,2,3)。

          有一点要说明的是:任何一个正确的排序算法,都能产生全部n!个序列。这n!个序列对应决策树中的所有叶节点。

          从这个模型明显可以得出结论:从根节点到任意一个叶节点之间最长路径的长度,也即决策树的高度(每一本书所说的高度概念不一致,这里取图中的高度为3),表示排序算法在最坏情况下的比较次数。

          利用决策树模型对基于比较的排序算法时间下界的证明:

          考虑一个高为 h,  叶节点个数为l的  决策树。  输入序列为n。  显然  n!<=l。(说明:如果不考虑n个元素有相等的情况,n!应该等于l).
    
          又,一个高度为h的完全二叉树的叶节点的数目为2h。   l <= 2h 
    
          即      n!<=l<=2h.
    
          取对数可得到   h>=lg(n!)。
    
          又, lg(n!) = Θ(nlgn);
          得到   h>=Ω(nlgn);

          说明一下这个:lg(n!) = Θ(nlgn);

         (注:好长时间没看这些,应该是从来没有认真看过==...  都没有搞清楚  Ο  Θ   Ω  这个几个符号的意思。翻了导论前面几章才算明白。   这个的证明要用到斯特灵公式。    我感觉已经超出我的能力范围之外了,不想花时间再去研究这玩意儿了 。)

          斯特林公式参看这里 http://zh.wikipedia.org/wiki/%E6%96%AF%E7%89%B9%E9%9D%88%E5%85%AC%E5%BC%8F

          简单说就是:

          f(n) = Θ(g(n)) 当且仅当f(n) =  Ω(nlgn)和f(n) = Ο(nlgn)。  这里的“=”并不是等号的意思。  f(n) = Θ(g(n)) 是 f(n) 属于 Θ(g(n)) 的意思,Θ(g(n)) 是一个集合。

          lg(n!) = Θ(nlgn);  就是说存在  c1和c2  所有的n>=n0,有 0<=c1(nlgn) <= lg(n!) <= c2(nlgn) 。

    二、三种线性时间排序

     

    1、计数排序

         计数排序就是根据元素的值统计元素的个数,然后直接把元素放到合适的位置,以达到Θ(n)的目的。思路也很简单,废话不多说,直接上代码

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 void countingSort(int *A,int n,int *B,int *C,int k)
     4 {
     5     //A为待排序的数组;
     6     //n为A数组的长度;
     7     //B为保存的结果;
     8     //C为临时存储区  长度为k
     9     //k为n个数据输入的范围; 0~~k-1
    10     for(int i=0;i<k;i++)
    11        C[i] = 0;
    12     for(int j=0;j<n;j++)
    13        C[A[j]] += 1;      //统计每个位置的个数
    14     for(int i=1;i<k;i++)
    15        C[i] += C[i-1];   //c[i]表示小于等于i的个数
    16     for(int j = n-1;j>=0;j--)//放到合适的位置,因为是从0开始  要减1
    17     {
    18         B[C[A[j]]-1] = A[j]; //c[i]表示小于等于i的个数  B[]从0开始  c[i]-1 表示在B中的位置
    19         C[A[j]] -= 1;
    20     }
    21 }
    22 int main()
    23 {
    24     int n=11;//待排序的长度
    25     int k=11;//数字范围 为 0~~k-1
    26     int a[n] ;
    27     int b[n] ;
    28     int c[k] ;
    29     for(int i=0;i<n;i++)
    30        a[i] = rand()%k ;
    31     for(int i=0;i<n;i++)
    32        printf("%d ",a[i]);
    33     printf("\n");
    34     countingSort(a,n,b,c,k);
    35     for(int i=0;i<n;i++)
    36      printf("%d ",b[i]);
    37     return 0;
    38 }

           写代码的时候,习惯从0开始了。导致第四个for循环的时候忘记减1了,纠结了好久。。。。显然可以看出上面的计数排序的Θ(n+k)。好吧,我一开始也不知道这个是怎么来的。一共四个循环,Θ(2n+2k),去掉常数就是Θ(n+k),n表示待排序数组,k表示要排序的的值的范围。这样,计数排序就很有局限了,k的值注定了这种排序的适用范围很小。

    2、桶排序

         也许看完上面的计数排序,你会发现既然C数组已经统计完了每个值的个数,那么直接扫描一遍C数组,依次序输出也就OK了。这个就算是桶排序了,这里的每个桶只能存储一个值,算作是特殊的桶排序吧。将上面的代码稍作改变就可以得到:

     1 void BucketSort(int *A,int n,int *B,int *C,int k)
     2 {
     3     //A为待排序的数组;
     4     //n为A数组的长度;
     5     //B为保存的结果;
     6     //C为临时存储区  长度为k
     7     //k为n个数据输入的范围; 0~~k-1
     8     for(int i=0;i<k;i++)
     9        C[i] = 0;
    10     for(int j=0;j<n;j++)
    11        C[A[j]] += 1; //统计每个位置的个数
    12     int j=0;
    13     for(int i=0;i<k;i++)
    14     {
    15         while(C[i]!=0)
    16         {
    17             B[j++] = i;
    18             C[i]--;
    19         }
    20     }
    21 }

          一般桶排序的操作步骤就是:

          1、确定要排序数组的取值范围,并划分成M个区间;

          2、给这个M区间划分M个桶,并对每个桶内进行排序;

          3、再依次序把这M个桶的元素串接起来。

          这里上一幅导论里面的图,它讲100划分成10个区间,设计10个桶。然后将数组里面的元素放到相应的桶里面去。

         附上我写的代码(实际是参考别人写的==!  一个链表操作写了好长时间,还被别人鄙视了无数遍。。。。。。。。。)

    #include <stdio.h>
    #include <stdlib.h>
    struct Node
    {
        int value;
        Node *next;
    };
    void BucketSort(int *array,int n,int low,int high,int interval)
    {//low和high分别代表  元素的最小值 和最大值
     //n为待排序数组长度,interval间隔
        int BucketNum = (high-low)/interval;//有多少个桶
        Node *BucketArray = new Node[BucketNum];
        Node *pre,*cur;
        int count=0;
        for(int i=0;i<10;i++)
        {
            BucketArray[i].value = 0;
            BucketArray[i].next = NULL;  //初始化。。。。
        }
        for(int i=0;i<n;i++)
        {
            Node *insert = new Node();
            insert->value = array[i];    //待插入的节点
            int num = array[i]/interval;
            if(BucketArray[num].next == NULL)
            {
                BucketArray[num].next = insert;   //每个桶的第一个节点保持空
            }
            else
            {
                pre = &BucketArray[num];
                cur = BucketArray[num].next;
                while(cur!=NULL && cur->value<=array[i])//找到合适的位置。
                {
                    cur = cur->next;
                    pre = pre->next;
                }
                insert->next = cur;
                pre->next = insert;
    
            }
        }
        for(int i=0;i<BucketNum;i++)
        {
            cur = BucketArray[i].next;
            if(cur == NULL)
                continue;
            while(cur!=NULL)
            {
                array[count++] = cur->value;
                cur = cur->next;
            }
        }
    }
    int main()
    {
        int a[10];
        for(int i=0;i<10;i++)
        {
            a[i] = rand()%100;//随机数
        }
        BucketSort(a,10,0,100,10);
        for(int i=0;i<10;i++)
        {
            printf("%d ",a[i]);
        }
        return 0;
    } 

         这段代码里面,设元素有N个,M个桶,平均每个桶里面有N/M个元素。在桶里面的,我没有采用其他的排序算法,简单的插入。因此桶里面为Θ(N/M)。总的时间复杂度为   Θ(N * N/M)=Θ(N2/M) ,可以看出来,当桶的数量越多。。。趋近于N时,时间复杂度就为Θ(N)了。

    3、基数排序

          基数排序是对多个关键字的排序。两种方式,一是从首要关键字到次要关键字,另外一种是从次要关键字到首要关键字。导论上面说的是第二种。如果是从首要关键字开始,就要对首要关键字相同的进行分堆,再根据次要关键字排序。增加开销,有点桶排序的味道了。

          这里我以技术排序为基础,从次要关键字开始,写了这个基数排序:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <math.h>
     4 void countingSort(int *A,int n,int *C,int k,int d)
     5 {
     6     //A为待排序的数组;
     7     //n为A数组的长度;
     8     //B为保存的结果;
     9     //C为临时存储区  长度为k
    10     //k为n个数据输入的范围; 0~~k-1
    11     int B[n];
    12     int key=0;
    13     for(int i=0;i<k;i++)
    14        C[i] = 0;
    15     for(int j=0;j<n;j++)
    16     {
    17         key = (A[j]/(int)(pow(10,d)))%10;//分离位数用。。。。
    18         C[key] += 1;      //统计每个位置的个数
    19     }
    20     for(int i=1;i<k;i++)
    21        C[i] += C[i-1];   //c[i]表示小于等于i的个数
    22     for(int j = n-1;j>=0;j--)//放到合适的位置,因为是从0开始  要减1
    23     {
    24         key = (A[j]/(int)(pow(10,d)))%10;
    25         B[C[key]-1] = A[j]; //c[i]表示小于等于i的个数  B[]从0开始  c[i]-1 表示在B中的位置
    26         C[key] -= 1;
    27     }
    28     for(int i=0;i<n;i++)
    29     {
    30         A[i]=B[i];
    31     }
    32 }
    33 void radixSort(int A[],int n,int d)
    34 {
    35     int k=10;//数字范围 为 0~~k-1
    36     int C[k] ;
    37     for(int i=0;i<d;i++)
    38     {
    39         countingSort(A,n,C,k,i);
    40     }
    41 }
    42 int main()
    43 {
    44     int A[10];
    45     for(int i=0;i<10;i++)
    46        A[i] = rand()%999;
    47     for(int i=0;i<10;i++)
    48        printf("%d ",A[i]);
    49     printf("\n");
    50     radixSort(A,10,3);
    51     for(int i=0;i<10;i++)
    52      printf("%d ",A[i]);
    53     return 0;
    54 }

          有人跟我说,代码里面key对 位数的获取 ,可以用位操作,我想了想,需要BCD码,没有想到特别好的解决方案,还是这样易懂。我总觉得硬性搞成位操作反而有点麻烦了,没有多大意义。没有让人眼睛一亮的解决方案。上面的代码时间复杂度为Θ(d(n+k));

    三、总结

         显然,线性时间排序有很大的局限,对数据值范围有要求。

         另外,写这三个排序,原本以为半天多都可以搞定,结果弄了两三天。遭到了无数的鄙视。。最关键还是自己不够熟悉。。

         关于排序,暂时就到这里吧。开学了,下学期专业课压死人。每学期好多门课,但是都感觉没一门学得扎实。

         以后如果有新的认识,我会再继续写写的。我觉得还会有续的,就是不知道多久了。。。。

         持续关注数据结构算法,一来,它很重要,二来,还是有点兴趣。

    最后,推荐一篇文章   挺有启发意义的。

    http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/

      

  • 相关阅读:
    POJ 3660 Cow Contest (floyd求联通关系)
    POJ 3660 Cow Contest (最短路dijkstra)
    POJ 1860 Currency Exchange (bellman-ford判负环)
    POJ 3268 Silver Cow Party (最短路dijkstra)
    POJ 1679 The Unique MST (最小生成树)
    POJ 3026 Borg Maze (最小生成树)
    HDU 4891 The Great Pan (模拟)
    HDU 4950 Monster (水题)
    URAL 2040 Palindromes and Super Abilities 2 (回文自动机)
    URAL 2037 Richness of binary words (回文子串,找规律)
  • 原文地址:https://www.cnblogs.com/xibaohe/p/2648136.html
Copyright © 2011-2022 走看看