CLRS 思考题 8.3 非比较排序算法的综合运用
Description
1) 给定一个整数数组,其中不同的整数中包含的数字个数可能不同,但是该数组中,所有整数中总的数字数为n。说明如何在O(n)时间内对该数组进行排序
2)给定一个字符串数组,其中不同的串包含的字符个数可能不同,但所有串中总的字符个数为n。说明如何在O(n)时间内对该数组进行排序(注意此处的顺序是指标准的字母顺序,例如,a < ab < b)
思路
读完题后,发现如果想要达到O(n)的排序算法且利用数字位数这个信息,就需要使用非比较排序中的基数排序。我想了一个基于计数排序与桶思想的更加优化时间与空间的基数排序算法,给大家分享一下。
我的想法是这样的,与其暴力使用基于stable_sort的基数排序,不如利用上数字位数这个信息对需要数据先进行分块(桶思想),然后再对每个块进行基数排序,这样能够避免补0比较从而大幅度降低算法的时间常数。举个例子,如下:
一共有三个数据: 32 5 30 1.以位数为桶对数据进行分块,一位的数据一个桶,两位的数据一个桶 所以有 5 (桶1),32 30 (桶2) 2.利用基于计数排序的基数排序算法对每个桶进行排序 所以有 5 (桶1,一轮完成),30 32(桶2,两轮完成)
遇到比较棘手的问题就是如何降低排序中的空间开销,我想的办法是利用两个数组加上一个一维的辅助数组实现计数排序,由于是静态链表实现的桶,所以在边界处理问题上出了很多bug,捣鼓了好久才解决。我觉得核心技巧有二:一是利用变量 left、right、sum 记录当前桶的左边界、右边界与桶内数据总和;二是利用变量 sorted_sum 记录之前已经排序好的桶的所有元素的个数,用于输出到新数组中的地址计算。
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<vector> int num; //数组中整数的个数 int n; //所有整数中总的数字数n int max_d; //数组中具有位数最多的数字的位数 struct Number { int val; int d; int tmp; //tmp中暂时存储指定位数的数字 //加入tmp属性方便排序记录 }; std::vector<Number> nums; std::vector<Number> cnums; int main(void) { printf("请输入数组中记录的个数与所有整数中总的数字数n\n"); while(scanf("%d%d", &num, &n) == 2 && num != 0) { nums.clear(); nums.resize(num+1); for (int i = 1; i <= num; i++) { int tmp; scanf("%d", &tmp); nums[i].val = tmp; } //通过位数确定桶的个数,并以位数对记录进行计数排序 int max_d = 0; for (int i = 1; i <= num; i++) { int cnt = 0; int tmp = nums[i].val; while(tmp != 0) { tmp /= 10; ++cnt; } nums[i].d = cnt; if (max_d < cnt) max_d = cnt; } int c[max_d+1]; memset(c, 0, sizeof(c)); for (int i = 1; i <= num; i++) c[nums[i].d]++; for (int i = 1; i <= max_d; i++) c[i] += c[i-1]; int cc[max_d+1]; memcpy(cc, c, sizeof(c)); //将c拷贝给cc,记录每个桶的下标 cnums.clear(); cnums.resize(num+1); for (int i = num; i > 0; i--) { cnums[c[nums[i].d]] = nums[i]; //cnums是计数排序后分好块的数据集 c[nums[i].d]--; } //由于下面的基数排序使用了nums当作每轮计数排序的中间数组 //且是静态链表实现桶,每个桶的范围一定,所以需要对没有 //预分类的nums进行处理 for (int i = 1; i <= num; i++) nums[i] = cnums[i]; //对max_d个桶进行基数排序 //all_sum记录已经排序好的桶的元素的总个数 //用于初始化计数排序的临时数组ct的首元素以实现不同桶空间的隔离 int sorted_sum = 0; int ct[10]; for (int i = 1; i <= max_d; i++) { int left = cc[i-1] + 1, right = cc[i]; //桶的左边界与右边界 int sum = right -left + 1; //当前桶内的数据数量 //对第i个桶的各个位数上的数字进行排序(低位优先) for (int j = 1; j <= i; j++) { //取出整数中第j位的数字 for (int k = 0; k < sum; k++) { int tmp = cnums[left + k].val; for (int kk = 1; kk < j; kk++) tmp /= 10; cnums[left + k].tmp = tmp % 10; } //以关键字为第j位数字对桶内元素进行计数排序(或其他stable_sort) //nums作为中间数组 for (int k = 0; k < 10; k++) ct[k] = 0; ct[0] = sorted_sum; for (int k = 0; k < sum; k++) ct[cnums[left + k].tmp]++; for (int k = 1; k < 10; k++) ct[k] += ct[k-1]; for (int k = sum-1; k >= 0; k--) { //计数排序输出到nums数组,只改变当前桶内元素在当前桶中的位置 nums[ct[cnums[left+k].tmp]] = cnums[left + k]; ct[cnums[left+k].tmp]--; } for (int i = 1; i <= num; i++) cnums[i] = nums[i]; } //当前桶的元素已排序完毕,将当前桶的元素个数加入sorted_sum sorted_sum += sum; } //输出 for (int i = 1; i <= num; i++) printf("%d ", cnums[i].val); printf("\n\n"); printf("请输入数组中记录的个数与所有整数中总的数字数n\n"); } return 0; }
第二道题我再想想...
————全心全意投入,拒绝画地为牢