一、计数排序
计数排序假设n个输入元素中的每一个都是介于0到k之间的整数。此处k为某个整数(输入数据在一个小范围内)。
基本思想:
计数排序的基本思想是对每一个输入元素x,确定出小于x的元素的个数。然后再将x直接放置在它在最终输出数组中的位置上。
如下图所示:
由于数组中可能有相等的数,在处理时需要注意。
时间复杂度和空间复杂度分析
算法总时间Θ(k + n)。当k=O(n)时,计数排序的运行时间是Θ(n)。
空间复杂度是O(n+k)。需要两个辅助数组:存放排序结果的数组B[n],存放临时结果的C[k]。
计数排序是稳定的排序算法:具有相同值的元素在输出数组中的相对次序与它们在输入数组中的相对次序相同。
代码实现:
#include<iostream> #include<vector> using namespace std; void countSort(vector<int> &nums , vector<int> &result) { int len = nums.size(); if(len == 0) return; int maxNum = INT_MIN; for(int i = 0 ; i < len ; i++) maxNum = max(maxNum , nums[i]); //找出最大值,用于确定辅助数组的大小 vector<int> tmp(maxNum+1 , 0);//申请辅助数组,注意要+1,因为最大数字的下标对应maxNum(注:这里的maxNum就是上面的k) for(int i = 0 ; i < len ; i++) tmp[nums[i]]++; //统计每个数字出现的次数 for(int i = 1 ; i <= maxNum ; i++) { tmp[i] += tmp[i-1]; //把出现的次数累加 } for(int i = len-1 ; i >= 0 ; i--) { result[tmp[nums[i]]-1] = nums[i];//这里有个-1是因为result是从0开始,与书上不同 tmp[nums[i]]--; } } int main() { int data[] = {2,5,3,0,2,3,0,3}; //int data[] = {2,3,7,1,9,4,4,4,6,0}; int len = sizeof(data) / sizeof(int); vector<int> nums(data , data+len); vector<int> result(len , 0); cout<<"排序前的数组为:"; for(int i = 0 ; i < len ; i++) cout<<nums[i]<<" "; cout<<endl; countSort(nums , result); cout<<"排序后的数组为:"; for(int i = 0 ; i < len ; i++) cout<<result[i]<<" "; cout<<endl; return 0; }
程序执行结果:
[[email protected] cctmp]# ./a.out 排序前的数组为:2 5 3 0 2 3 0 3 排序后的数组为:0 0 2 2 3 3 3 5 [[email protected] cctmp]#
二、基数排序
基本思想:
基数排序是从低位到高位依次对所有的数进行排序。如果所有的数最高位数是d,那么先按最低有效位数字进行排序,得到一个结果。然后往高位重复这个过程。
排序过程如图所示:
需要注意的是,按位排序必须是稳定的排序算法。经常采用上面的计数排序。
时间复杂度和空间复杂度分析
给定n个d位数,每一个数位可能取值中数是k,如果所用的稳定的按位排序时间复杂度是Θ(n+k),基数排序时间复杂度是Θ(d(n+k))。空间复杂度O(n+k)。
当d为常数,k=O(n)时,基数排序有线性时间复杂度。
关于如何将每个关键字分解成若干数位方面,有另外一个定理:
给定n个b维数和任何正整数r<=b,基数排序能在Θ((b/r)(n+2^r))时间内对这些数进行排序。
这里,对一个值r<=b,将每个关键字看做是有d = floor(b/r)个数字,每个数字含有r位,再进行计数排序。
上述式子可以推导得到Θ(n)复杂度。
但是这并不意味着基数排序比基于比较的排序算法比如快排更好!因为隐含在记号中的常数因子是不同的。哪一个排序算法更好取决于底层机器的实现特性,比如快排同==排通常可以更有效地利用硬件缓存。同时还取决于输入数据。而且利用计数排序作为中间稳定排序不是原地排序。
代码实现:
#include<iostream> #include<vector> using namespace std; //求最大数字的位数 int maxbit(vector<int> &nums) { int len = nums.size(); if(len == 0) return 0; int maxNum = nums[0]; for(int i = 1 ; i < len ; i++) maxNum = max(maxNum , nums[i]); int d = 0; while(maxNum > 0) { maxNum /= 10; ++d; } return d; } void radixSort(vector<int> &nums) { int d = maxbit(nums); int len = nums.size(); vector<int> tmp(len , 0); vector<int> count(10 , 0); int radix = 1 ,i , j , k; //从后向前对每一位排序,使用计数排序法 for(i = 1 ; i <= d ; i++) { count.assign(10 , 0);//把count清零 for(j = 0 ; j < len ; j++) { k = (nums[j] / radix) % 10;//k为nums[j]的倒数第i位数 count[k]++; //统计第k位出现的次数 } for(j = 1 ; j < 10 ; j++) count[j] += count[j-1]; for(j = len-1 ; j >= 0 ; j--) { k = (nums[j] / radix) % 10; tmp[count[k]-1] = nums[j];//与计数的区别在这里 count[k]--; } for(j = 0 ; j < len ; j++) nums[j] = tmp[j]; //更新排序后的数组 radix *= 10; } } int main() { int data[] = {17243,5,213,17312,11858,3098,13288}; int len = sizeof(data) / sizeof(int); vector<int> nums(data , data+len); cout<<"排序前的数组为:"; for(int i = 0 ; i < len ; i++) cout<<nums[i]<<" "; cout<<endl; radixSort(nums); cout<<"排序后的数组为:"; for(int i = 0 ; i < len ; i++) cout<<nums[i]<<" "; cout<<endl; return 0; }
程序执行结果:
[[email protected] cctmp]# ./a.out 排序前的数组为:17243 5 213 17312 11858 3098 13288 排序后的数组为:5 213 3098 11858 13288 17243 17312
三、桶排序
基本思想:
将区间[0,1)分成n个相同大小的子区间,或称为桶。然后将n个输入元素分布到各个桶中去。每个桶中的元素用一个链表来存储。
如图所示:
简而言之就是把所有数字放到对应的BUCKET_NUM 个桶中(比如图中采用除数法(/10)),每个桶维持一个链表,对链表排序。然后对这些有序链表合并即可。
时间和空间复杂度分析
时间复杂度是O(n)。空间复杂度是O(n)。需要一个辅助数组来存放桶(链表)。
即使输入不满足均匀分布,桶排序也仍然可以以线性时间运行,只要输入满足这样一个条件:各个桶尺寸的平方和与总的元素呈线性关系。桶排序是稳定排序算法。
代码实现:
#include<iostream> #include<vector> #include<time.h> using namespace std; const int BUCKET_NUM = 10; //桶的个数 const int NUM_SIZE = 20; //数字个数 //桶内的链表结点 struct ListNode { int mData; ListNode *mNext; ListNode(){} ListNode(int data):mData(data),mNext(NULL){} }; ListNode *insert(ListNode * , int); //把点插入链表 ListNode *merge(ListNode * , ListNode *); //合并两个有序链表 //桶排序算法 void bucketSort(vector<int> &nums) { ListNode *buckets[BUCKET_NUM] = {NULL}; //建立BUCKET_NUM个桶(指针数组,数组内的每个元素是一个指向链表的指针) int len = nums.size(); if(len == 0) return; for(int i = 0 ; i < len ; i++) { int index = nums[i] / BUCKET_NUM; //找出该数字插入到哪个桶中 ListNode *head = buckets[index]; //取得该桶的头结点 buckets[index] = insert(head , nums[i]); //把数字插入到桶中 } //合并所有桶(桶内数字有序) ListNode *head = buckets[0]; for(int i = 1 ; i < BUCKET_NUM ; i++) { head = merge(head ,buckets[i]); } //把排好序的数字赋给数组 for(int i = 0 ; i < len ; i++) { nums[i] = head->mData; head = head->mNext; } } //把新结点插入到链表中合适的位置,使链表有序(其实是插入排序) ListNode *insert(ListNode *head , int val) { //如果链表为空,或者val比头结点小,则新结点作为头结点 if(head == NULL || val < head->mData) { ListNode *newHead = new ListNode(val); newHead->mNext = head; head = newHead; return head; } ListNode *pre = head , *cur = head->mNext; ListNode *newnode = new ListNode(val);//新结点 //找到新结点要插入的位置 while(cur != NULL && cur->mData <= val) { pre = cur; cur = cur->mNext; } newnode->mNext = cur; pre->mNext = newnode; return head; } //合并两个有序链表 ListNode *merge(ListNode *head1 , ListNode *head2) { if(head1 == NULL) return head2; if(head2 == NULL) return head1; ListNode dummyNode ; ListNode *p = &dummyNode; ListNode *p1 = head1 , *p2 = head2; while(p1 && p2) { if(p1->mData <= p2->mData) { p->mNext = p1; p1 = p1->mNext; } else { p->mNext = p2; p2 = p2->mNext; } p = p->mNext; } if(p1) p->mNext = p1; if(p2) p->mNext = p2; return dummyNode.mNext; } int main() { srand((unsigned)time(NULL)); vector<int> nums; for(int i = 0 ; i < NUM_SIZE ; i++) { int num = rand() % 100; //这里取每个数字<100 nums.push_back(num); } cout<<"排序前的数组为: "; for(int i = 0 ; i < NUM_SIZE ; i++) cout<<nums[i]<<" " ; cout<<endl; bucketSort(nums); cout<<"排序后的数组为: "; for(int i = 0 ; i < NUM_SIZE ; i++) cout<<nums[i]<<" "; cout<<endl; return 0; }
程序执行结果:
[[email protected] cctmp]# ./a.out 排序前的数组为: 20 89 4 50 96 29 92 81 67 24 16 84 73 83 17 46 71 11 82 9 排序后的数组为: 4 9 11 16 17 20 24 29 46 50 67 71 73 81 82 83 84 89 92 96 [[email protected] cctmp]#
版权声明:转载请注明出处。