《算法导论》学习总结——第二部分5基数排序、桶排序

来源:互联网 发布:网络电视飞鱼直播下载 编辑:程序博客网 时间:2024/05/21 23:54

       以前学严蔚敏老师的数据结构的时候,看到基数排序,只是当按位排序,至于最终能不能排出所有的正确值,还是不清楚的,总感觉,就是按照个需要的关键字去排下,最终会和的部分还是没去细心注意的。

        基数排序是一种分配排序,其基本思想是:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性O(n)。基数排序所做的事情,是对N位分别进行排序。从直觉上来看,人们可能会觉得应该首先按最高有效位进行排序,不过这点与我们的直觉相反,基数排序首先对最低有效位数字进行排序。如果我们每次比较r bits,则需要进行b/r趟,每趟进行计数排序需要O(n+2^r),则总的时间复杂度为O(b/r(n+2^r))。

       关于基数排序的效果演示,可以参考:http://blogimg.chinaunix.net/blog/upfile/070912120349.swf的动画,和

http://www.cnblogs.com/xiaosuo/archive/2010/03/12/1654353.html的图以及代码。

       书上给的伪代码:


      问题是,按照int算,总共就那么5位,每一位,就0-9,也不好意思用其他稳定排序啊,果断换计数吧。

       实现代码如下:

//==============================================================// Name        : Radix.cpp// Author      : xia// Copyright   : NUAA// Description : 基数排序的实现//==============================================================#include <iostream>#include <vector>#include <ctime>#include <iomanip>#include <fstream>using namespace std;const int MAX=200;  //数字个数const int NUM=3 ;   //位数,也可以通过计算获得void RadixSort(vector<int> &v,vector<int> &B){int i,j;//i,j循环遍历变量int rate=1,temp=0;//rate变化以统计每一位,temp则是每一位的数for (i=1 ; i<=NUM ;i++ , rate *= 10){//对i位的排序vector<int> C(10); //放循环里面,每次初始化为0for ( j=1 ; j<=9 ; j++)C[j] += C[j-1];for (j=0;  j<MAX ; j++){temp = (v[j]/rate)%10;//当前排的位C[temp] ++; //C[i]包含等于i的个数}for (j=1 ; j<10 ; j++)C[j] += C[j-1];for ( j=MAX-1 ; j >= 0 ; j--){temp = (v[j]/rate)%10 ;C[temp] -- ;B[C[temp]] = v[j];}//以上为分配,基本与计数排序相同 //此时B存储为某位排好序的数for (j=0 ; j< MAX ; j++)//收集v[j] = B[j];}}int main(int argc ,char **argv){vector<int> v; int i;srand((unsigned)time(NULL));for ( i=0 ; i<MAX ; i++)v.push_back(rand()%1000);//让产生的数最多3位vector<int> result(v);//保存排序结果RadixSort(v,result); for (i=0 ; i<MAX ; i++){cout << setw(5) << result[i] ;if ( (i+1) % 15 == 0)cout << endl;}cout << endl;return 0;}
运行结果如下:


        那基数排序和快排比的速度怎么样呢?

        虽然根据常见情况,有b=O(lgn),基数排序运行时间为Θ(n),看上去比快排的平均Θ(nlgn)更好吧。不过,隐含在Θ符号中的常数因子是不同的。对于要处理的n个关键字,尽管基数排序执行的遍数比快排少,但每一遍的时间要长,所以,哪一个排序更好,取决于底层机器的实现特性(比如,快排通常可以比基数更好的利用硬件缓存),同时还取决于输入数据。此外,利用计数排序作为中间稳定排序的基数排序不是原地排序,而很多 Θ(nlgn)的比较排序算法则可以做到原地排序。因此,内存容量宝贵时,像快排这样的原地排序算法也许更可贵。

        以我机器为例,VC6编译器,在100w数量级时,快排1457,基数排序870,快的不明显啊。

        另外,可以参考一个C语言实现的版本:http://yikongzi.blog.163.com/blog/static/69643936201031642952796/,严蔚敏老师的也是用C语言实现,不过是链表,代码太长,就不贴了,有兴趣的同学可以参考下,本文不少也参考自:http://www.wutianqi.com/?p=2378 。

        目前就是这样了,曾经没写的基数排序,不过感觉还不如直接计数得了,也许适用的地方,是比如打牌这种,实际生产生活当中吧。

  

       桶排序

       个人感觉,只是计数排序的二维扩充版。其基本思想:将区间[0,1]划分为n个相同大小的子区间,也叫桶,然后将n个输入分布到桶中。由于输入均匀,所以,一般不会有一个桶有很多数的情况。输出时,先将每个桶进行排序,然后依次输出各桶元素。其伪代码如下:

        

       参照http://www.bioisland.com/Algorithm/ShowArticle.asp?ArticleID=161示意图,每个桶可以采用链表,也可以采用二维数组实现。与计数排序的区别,个人感觉,只是计数排序是一维展开,每个元素为一个桶;桶排序,则是均匀子空间,一般按10个,这样,每个桶再一维。所以,只能算是对计数排序的扩充(个人理解)。http://www.byvoid.com/blog/sort-radix/有一个实现版本,不过他实现的桶排序,没体现桶,毕竟伪代码第4、5行,对桶内排序,是没有的,只是个计数排序。

       对桶排序的分析,可参考:http://anwj336.blog.163.com/blog/static/8941520920109535025216/ ,习题8.4-2,桶排序最坏情况下运行时间,在桶的数量只有1个时,退化为插入排序,O(n^2),如果需要在保证线性时间的同时,保证最坏情况下为O(nlgn),则将每个桶内的排序改为最坏O(nlgn)算法,如归并排序即可。当然,持续增大桶的数量,当桶数量等于元素最大值时,就是计数排序了。(说到这个,如果计数排序情况下,最大元素与第二大元素有很大差距,则会造成空间的极大浪费啊,也许这才是桶排序提出的关键,充分节省空间)。

      习题8-3可参考:http://blog.csdn.net/zhanglei8893/article/details/6285689,没细看,跳过


       菜鸟goes on ~~~