一、不基于比较的排序算法
1.1、计数排序
这是一种另类排序,它不是基于比较的排序算法。比较小众,根据数据的分布情况,即频率。
1.2、基数排序
数据结构不统一,一般采用队列,先进先出。
比如[13,17,26,72,100],先找最高位有几位,有3位,进行补齐
|
|
↓
013,017,026,072,100\],然后对每位准备一个队列,从个位开始存放
\|
\|
↓
0位队列存放:100 2位队列存放:072 3位队列存放:013 6位队列存放:026 7位队列存放:017
然后依次从左到右取出数据
\|
\|
↓
\[100,072,013,026,017\],然后准备十位队列,同理
\|
\|
↓
0位队列存放:100 1位队列存放:013 017 2位队列存放:026 7位队列存放:072
然后依次从左到右取出数据
\|
\|
↓
\[100,013,017,026,072\],同理百位
\|
\|
↓
0位队列存放:013,017,026,072 1位队列存放:100
然后依次从左到右取出数据
\[013,017,026,072,100\]有序
理由:
1. 基数排序是基于计数排序的扩展,是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。
要求:
要排序的东西**必须要有进制** ,比如十进制、二进制等。
核心点:
***按位分配收集***
```cpp
// 大致情况如下:
void radixSort(vector
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3
解题思路:
- 题目要求时间复杂度在线性时间内,就等同于O(n),所以不能使用O(nlogn)的排序算法,比如快速排序、归并排序、堆排序等。
- 那么基数排序刚好满足时间复杂度O(n)
- 可以先排序数组,然后遍历排序后的数组,计算相邻差值并找出最大值。
cpp
class Solution {
public:
int maximumGap(vector<int>& nums) {
if (nums.size() < 2) return 0;
int ret = 0;
radixSort(nums);
for (int i = 0, j = 1; j < nums.size(); i++, j++) {
ret = std::max(ret, abs(nums[i] - nums[j]));
}
return ret;
}
static int maxBits(const vector<int>& nums)
{
if (nums.empty()) return 0;
int maxVal = INT_MIN;
for (int num : nums) {
maxVal = max(num, maxVal);
}
// 处理最大值为0的情况
if (maxVal == 0) return 1;
int res = 0;
// 处理负数:取绝对值
int temp = abs(maxVal);
while (temp != 0) {
res++;
temp /= 10;
}
return res;
}
static void radixSortImpl(vector<int>& nums, int begin, int end, int digit)
{
const int radix = 10; // 基数,这里是十进制
int i = 0, j = 0;
// 辅助数据,用于存储排序结果
vector<int> bucket(end - begin + 1);
// 对每一位进行排序
for (int d = 1; d <= digit; ++d) {
// 计数数组,记录每个数字出现的次数
vector<int> count(radix, 0);
// 统计当前位上每个数字出现的次数
for (i = begin; i <= end; ++i) {
j = getDigit(nums[i], d);
count[j]++;
}
// 将计数转换为前缀和,表示每个数字在输出数组中的最后位置
// 注意:这里从 i=1 开始累加,count[0] 保持不变
for (i = 1; i < radix; ++i) {
count[i] += count[i - 1];
}
// 从后向前遍历,保持稳定性
for (i = end; i >= begin; --i) {
j = getDigit(nums[i], d);
// count[j] - 1 是当前数字应该放入的位置
bucket[count[j] - 1] = nums[i];
count[j]--;
}
// 将桶中的数据复制回原数组
for (i = begin, j = 0; i <= end; ++i, ++j) {
nums[i] = bucket[j];
}
}
}
// 获取数字x的第d位数(从个位开始,d=1表示个位)
static int getDigit(int x, int d) {
// 处理负数:先取绝对值
int num = abs(x);
// 计算第d位的数字
return (num / static_cast<int>(pow(10, d - 1))) % 10;
}
// 基数排序的入口函数
void radixSort(vector<int>& nums) {
if (nums.empty() || nums.size() < 2) return;
// 获取最大位数
int digit = maxBits(nums);
// 调用实际的排序函数
radixSortImpl(nums, 0, nums.size() - 1, digit);
}
};
虽然能pass了,但基本思想还是要将数组进行完全排序之后,再重新计算一遍相邻差值,为了找最大间隔而完成了全部排序;其实,我们只需要找到最大间隔,不需要完全排序,所以可以优化一下,只对最大间隔的元素进行排序,这样时间复杂度会变得更低,虽然都是O(n)。