大话桶排序 基数排序和计数排序

来源:互联网 发布:淘宝第一层级什么意思 编辑:程序博客网 时间:2024/05/22 04:32

(0) 相关排序

排序 之 归并排序(二路)

大话快排 和 归排的渊源

各种排序算法的效率比较(bubblesort,quicksort,insertsort)

堆排序 之 c 和 c++函数模板(初谈模板类)

队列优先 之 插入排序实现(插入思想)

外部排序 之 概念篇

STL 二分查找三兄弟(lower_bound(),upper_bound(),binary_search())

二分查找的改进 差值查找(low<=high,这与快排low<high)

一:计数排序

(1)当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。 
(2)算法的步骤如下:
 1.找出待排序的数组中最大和最小的元素
 2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
 3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
 4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

(3)代码

#include <ctime>#include <iostream>#include <cstdlib>#include <cstring>using namespace std;const int NUM_RANGE = 100;const int RATE = 10; // 进制// output the arrvoid print_arr(const int *arr,const int &n){int i;for(i=0; i<n; i++){if(!i){cout << arr[i];}else{cout << " " << arr[i];}}       printf("\n");}// 计算最长的位数int counting_digits(int *arr,const int &n){int digits = 0;int MyMax = arr[0];int i;for(i=1; i!=n; ++i){if(MyMax < arr[i])MyMax = arr[i];}while(MyMax){++digits;MyMax /= RATE;}return digits;}// sort by counting 计数void counting_sort(int *ini_arr,int *sorted_arr,const int &n){//int digits = counting_digits(arr,n); // 用不到的int *count_arr = (int *)malloc(sizeof(int)*NUM_RANGE);// 相当于bitMap统计数量类似int i,j,k;memset(count_arr,0,sizeof(int)*NUM_RANGE);for(i=0; i!=n; ++i){//分别计数count_arr[ini_arr[i]]++;// 以实际的 元素值 为count_arr的下标}for(i=1; i!=NUM_RANGE; ++i){// 计算排序后的位置count_arr[i] += count_arr[i-1];}for(i=n-1;i>=0;--i){int index = count_arr[ini_arr[i]]-1;//排序后的下标sorted_arr[index] = ini_arr[i];count_arr[ini_arr[i]]--;// 这是为了处理重复的数据}free(count_arr);}int main(int argc, char *argv[]){int n;if(argc < 2){n = 10;}else{n = atoi(argv[1]);}int i;int *arr = (int *)malloc(sizeof(int)*n);int *sorted_arr = (int *)malloc(sizeof(int)*n);srand(time(0));for(i=0; i<n; i++){arr[i] = rand() % NUM_RANGE;}printf("ini_array: ");print_arr(arr, n);counting_sort(arr, sorted_arr, n);printf("sorted_array: ");print_arr(sorted_arr, n);free(arr);       free(sorted_arr);return 0;}

二:基数排序

(1)  基数排序
上面的问题是多关键字的排序,但单关键字也仍然可以使用这种方式。比如字符串“abcd” “aesc” "dwsc" "rews"就可以把每个字符看成一个关键字。另外还有整数 425、321、235、432也可以每个位上的数字为一个关键字。
基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。比如下面的待排序列:
278、109、063、930、589、184、505、269、008、083
我们将每个数值的个位,十位,百位分成三个关键字: 278 -> k1(个位)=8 ,k2(十位)=7 ,k3=(百位)=2。
然后从最低位个位开始(从最次关键字开始),对所有数据的k1关键字进行桶分配(因为,每个数字都是 0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列。
930、063、083、184、505、278、008、109、589、269
再对上面的序列接着进行针对k2的桶分配,输出序列为:
505、008、109、930、063、269、278、083、184、589
最后针对k3的桶分配,输出序列为:
008、063、083、109、184、269、278、505、589、930
(2)  性能分析
很明显,基数排序的性能比桶排序要略差。每一次关键字的桶分配都需要O(N)的时间复杂度,而且分配之后得到新的关键字序列又需要O(N)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2N) ,当然d要远远小于N,因此基本上还是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。一般来说N>>M,因此额外空间需要大概N个左右。
但是,对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下,桶内多个数据必须进行基于比较操作的排序。因此,在实际应用中,基数排序的应用范围更加广泛。

(3)代码

#include <ctime>#include <iostream>#include <cstdlib>#include <cstring>using namespace std;const int NUM_RANGE = 100;const int RATE = 10; // 进制// output the arrvoid print_arr(const int *arr,const int &n){int i;for(i=0; i<n; i++){if(!i){cout << arr[i];}else{cout << " " << arr[i];}}printf("\n");}// 计算最长的位数int counting_digits(int *arr,const int &n){int digits = 0;int MyMax = arr[0];int i;for(i=1; i!=n; ++i){if(MyMax < arr[i])MyMax = arr[i];}while(MyMax){++digits;MyMax /= RATE;}return digits;}// sort by counting 计数void radix_sort(int *ini_arr,const int &n){int digits = counting_digits(ini_arr,n); int *cnt = (int *)malloc(sizeof(int)*RATE);// 0-9基数的个数啦,进制数int *sorted_arr = (int *)malloc(sizeof(int)*n);int i,j;int divide = 1;for(i=0;i!=digits;++i){memset(cnt,0,sizeof(int)*RATE);for(j=0;j!=n;++j){// 统计计数sorted_arr[j] = ini_arr[j];int index = (sorted_arr[j]/divide)%RATE;//取某一位作为下标cnt[index]++;}for(j=1; j!=RATE; ++j){cnt[j] += cnt[j-1];// 计算新的排名后的下标}// 非常类似于计数排序,逆序排列for(j=n-1; j>=0; --j){int index = (sorted_arr[j]/divide)%RATE;//取某一位作为下标ini_arr[cnt[index]-1] = sorted_arr[j];cnt[index]--;// 处理重复数据}divide *= RATE;}free(sorted_arr);free(cnt);}int main(int argc, char *argv[]){int n;if(argc < 2){n = 10;}else{n = atoi(argv[1]);}int i;int *arr = (int *)malloc(sizeof(int)*n);srand(time(0));for(i=0; i<n; i++){arr[i] = rand() % NUM_RANGE;}printf("ini_array: ");print_arr(arr, n);radix_sort(arr, n);printf("sorted_array: ");print_arr(arr, n);free(arr);return 0;}


三:桶排序

(1)桶排序的基本思想
假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]....B[M]中的全部内容即是一个有序序列。
假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。
(2)桶排序代价分析
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。
对N个关键字进行桶排序的时间复杂度分为两个部分:
1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。
2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。
很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。
2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。
对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。
总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
我个人还有一个感受:在查找算法中,基于比较的查找算法最好的时间复杂度也是O(logN)。比如折半查找、平衡二叉树、红黑树等。但是Hash表却有O(C)线性级别的查找效率(不冲突情况下查找效率达到O(1))。大家好好体会一下:Hash表的思想和桶排序是不是有一曲同工之妙呢?
 参考自 :http://blog.csdn.net/quietwave/article/details/8008572   http://www.cnblogs.com/hxsyl/p/3214379.html

(3)代码

#include <ctime>#include <iostream>#include <cstdlib>#include <cstring>#include <cmath>using namespace std;const int NUM_RANGE = 100;const int RATE = 10; // 进制struct Node{int data;struct Node *next;};// output the arrvoid print_arr(const int *arr,const int &n){int i;for(i=0; i<n; i++){if(!i){cout << arr[i];}else{cout << " " << arr[i];}}printf("\n");}void destory(Node arr[]){;}// sort by counting 计数void bucket_sort(int *ini_arr,const int &n){int i,j;Node *t_node,*cur_node;Node *arr = (Node *)malloc(sizeof(Node)*n);for(i=0;i!=n;++i){arr[i].data = -1;arr[i].next = NULL;}//划分桶并填元素for(i=0;i!=n;++i){int tmp = (int)floor(ini_arr[i]/10);if(NULL == arr[tmp].next){//cout << "test" << endl;t_node = (Node *)malloc(sizeof(Node));t_node->data = ini_arr[i];t_node->next = NULL;arr[tmp].next = t_node;}else// 插入排序{if(arr[tmp].next->data >= ini_arr[i]){t_node = (Node *)malloc(sizeof(Node));t_node->data = ini_arr[i];t_node->next = arr[tmp].next;arr[tmp].next = t_node;}cur_node = arr[tmp].next;// cur_node已经比较过 了while(cur_node->next)// 从arr[tmp].next开始{if(cur_node->next->data < ini_arr[i]){cur_node = cur_node->next;}else{t_node = (Node *)malloc(sizeof(Node));t_node->data = ini_arr[i];t_node->next = cur_node->next;cur_node->next = t_node;break;// 插入了}}// while到了尾巴if(NULL == cur_node->next){t_node = (Node *)malloc(sizeof(Node));t_node->data = ini_arr[i];t_node->next = NULL;cur_node->next = t_node;}}// end else}// end for // 排序后的结果for(i=0;i!=n;++i){int j = 0;cur_node = arr[i].next;// 从arr[tmp].next开始while(cur_node)// 从arr[tmp].next开始{ini_arr[j++] = cur_node->data;cur_node = cur_node->next;}// 到了尾巴}destory(arr);}int main(int argc, char *argv[]){int n;if(argc < 2){n = 10;}else{n = atoi(argv[1]);}int i;int *arr = (int *)malloc(sizeof(int)*n);srand(time(0));for(i=0; i<n; i++){arr[i] = rand() % NUM_RANGE;}printf("ini_array: ");print_arr(arr, n);bucket_sort(arr, n);printf("sorted_array: ");print_arr(arr, n);free(arr);return 0;}

四:草图分析

(1)基数排序


五:美文分享

题记

今天发现要学的东西好多啊,自己学得又慢,快没信心了。转一篇文章,激励下自己,重要是掌握好学习方法。

也可以参考我之前的一篇文章 http://blog.csdn.net/whuslei/article/details/6661435 。

以下是原文,原始链接:http://blog.csdn.net/heiyeshuwu/article/details/6920880


一、想学的技术非常多怎么办?

区分重点,选定目标,循序渐进。技术是无边际的,随着知识的不断增加,会发现未知的东西更多。我们能做的是:

1、找重点:区分感兴趣的技能和必须技能,将重点放在必须技能上。如实在区分不开,可以这样问自己:不学这个技术会现在的工作是不是干不下去了?(必要技能和感兴趣的技能,不学此技能是不是就把自己的饭碗给砸了?)

2、定目标:在一段时间(至少半年)选定一个方向,深精下去(选定一个方向,至少研究半年,否则就像习武,半途而废,切记,切记!!!)

3、不断向前:当某一种技术掌握之后,再开始另一门技术的研究

4、找圈子:找一个适合自己的圈子,讨论问题的朋友、博友、qq好友、室友等。(技术blog,关注大牛的blog,找技术大牛请教问题,找同一层次的人讨论问题,技术的路上,是孤独的,所以一定要有自己的圈子)

5、360行,行行出状元;万事开头难……需要找一个带路者。。。

6、独立解决问题:相信每一程序员,最兴奋的时刻,是查找了3天的bug终于找到,并解决了。因此,一定要有自己独立思考、独立研究、独立解决问题的时间,一定要保持这种兴奋。

二、想学技术,但目前项目工作太多,没时间展开,一直拖着,怎么办?

生存状况和工作性质注定了我们不能像在学校和外企那样有大把的可支配时间,所以要追求技术进步只能自己找时间,也即"白天求生存,晚上谋发展"的道理,我一般会将晚上和周末的时间拿出一部分用来学习。(还是那句古话:时间就像海绵里的水,……生存的竞争和工作的任务要平衡好,不管有多么忙,多么的没有时间,总该一周抽出一段空闲的时间,总结一下,并坚持下去。)

三、选定一个技术方向后该如何展开?

学习一门技术一般分为三个阶段:入门>掌握>精通。一般情况下达到熟练程度即可,要实现至少需要一年的积累

可以分下面几步:

1、知其然:了解该技术的基本原理和应用场景,并进行简单的实践。这个阶段,官方文档是最好的教材;

2、站在巨人的肩膀上:找该技术业界成熟的开源类库和应用系统进行学习,精读源代码

3、在问答中进步:去相关论坛学习,提问或者回答问题,并尝试独立解决论坛中已有问题,www.stackoverflow.com是最好的选择。

4、躬身而行:实践相关技术,达到能独立完成中等规模应用的程度;

5、知其所以然:深入了解该技术的运行机制、周边技术、性能优化、深度知识等;

6、善用资源:在公司,高手很多,你想了解的任何一门技术,差不多都能找到相应的牛人,多和这些人沟通,会事半功倍。

以PHP为例:
熟悉语法&api-> 阅读zend、ci、wordpress源码 -> 用PHP独立开发项目 -> 深入了解PHP引擎实现细节、扩展编写、性能优化等

四、外界干扰太多,静不下心来学习,怎么办?

刚来百度的时候,前辈给过我一个建议:关掉QQ(注:我觉得有些时候应该关掉浏览器!!^_^),结合在学校时的体会,我深知这个建议的重要性,欣然接受了。其实,静心是做好任何事的前提,关掉QQ只是一个表象,最本质的是我们要给自己营造一个好的学习环境。做技术需要一个安静的环境和一套方法论,方法论可以找到很多(论语第一篇讲的最好),环境必须我们自己营造。所以我的建议是:忘掉QQ、适度娱乐,努力使自己沉迷到工作和学习中。虽然不太容易做到,但做到了对提高效率、找出学习时间都很有帮助。

一些复杂的技术会涉及很多方向,每个方向也可以按照上边的方式分解。技术学习可以触类旁通,当你完全吃透一门技术的时候,其它的再学起来就快了;

以上内容归根结底就是要我们:懂得选择,学会放弃,耐得住寂寞,经得起诱惑。

做技术如同做学问,关于如何做学问,王国维有段话我高中以来一直非常喜欢:

古今之成大事业、大学问者,罔不经过三种之境界:“昨夜西风凋碧树。独上高楼,望尽天涯路。”此第一境界也。“衣带渐宽终不悔,为伊消得人憔悴。”此第二境界也。“众里寻他千百度,蓦然回首,那人却在灯火阑珊处。”此第三境界也。

 

今天有点小郁闷,本来好不容易把webx的基本流程熟悉了,但是突然被告知改用SpringMVC模式。虽然思想上差不多,但是用法上还是有差异的,特别是Spring2以后采用了annotation。纠结的地方来了!一知半解,心里真不踏实啊!人们不是常说,"知道了怎么做,还需要知道为什么这么做!"。所以就想到一个问题,面对新的知识,我们应该怎么去学习?

    第一反应,Google or Baidu ?不错,很多人第一选择应该就是这个。但是结果是好坏各一半吧网上的资源,很多都是带你入门的,比如一些小实例啊,贴些代码解释下呀,写得好的文章就稍微顺带讲点原理啊!开始接触新的东西,确实需要一些实例去理解,实践。但是是不是这样就够了?

    不一定!对于一些工具的用法,如SVN之类的,可以参考一些网上的文章,知道怎么用,以及用了之后会出现什么后果就可以了。但是像Spring这种成熟的框架,想在网上找两篇文章就想理解它?很难!!特别是理解它的思想!这个时候,我觉得理智的做法是去搜下有什么相关的好书!成熟的技术,一般都有很多经典的书,真的需要耐心去读读看。第一遍不懂,没关系。实践之后,做几个小例子后再看,你会有意想不到的收获!

    看完之后,最好对照自己的例子,或者参考网上的文章,将自己对这些技术的想法写下来,一方面,检验下自己是否真的明白了;另一方面,也许可以帮助到别人!时间长了,对自己也很有帮助。

    写这篇文章的目的,一是理清下思路,我写东西的时候心很静,我很喜欢这种感觉;二是希望可以和大家分享下我的个人理解。

  【注意】本文的标题起的不太好,没有真的告诉你怎么去学,只是表达了一些个人关于学习方面的理解,如有不对,虚心接受意见。



4 0
原创粉丝点击