三种线性排序算法
- 基数排序、计数排序和桶排序都是特定情况下时间复杂度为
O(N)
O
(
N
)
的算法(对于特定算法可能需要加上常数项),他们都属于非比较的排序算法。但是他们的空间复杂度都不是
O(1)
O
(
1
)
(部分基于比较的排序算法可以做到
O(1)
O
(
1
)
的空间复杂度)。
基数排序
- 基于比较的排序方法无法做到在
O(N)
O
(
N
)
的时间复杂度内完成对数据的排序,但是基数排序不是基于比较的排序方法。
- 基于排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
- 基数排序的平均时间复杂度为
O(dN)
O
(
d
N
)
,d是数据的位数,空间复杂度是
O(kN)
O
(
k
N
)
,k为一个基数所具有的可能的取值个数(10进制的话,就是k=10)
- 参考链接
- 注意:虽然基数排序的时间复杂度很小,但是一方面他的空间复杂度很大(基于比较的排序一般空间复杂度都是
O(1)
O
(
1
)
),另一方面很多需要排序的数据没有基数,因此无法使用基数排序。
代码
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
int dirs[8][2] = { 1, 1, 1, 0, 1, -1, 0, 1, 0, -1, -1, 1, -1, 0, -1, -1 };
// 获取最大位数
int maxBit(const vector<int>& data)
{
int d = 1;
int p = 10;
int ret = 0;
for (int i = 0; i < data.size(); ++i)
{
d = 1;
p = 10;
while ( data[i] >= p )
{
p *= 10;
++d;
}
ret = max( ret, d );
}
return ret;
}
// 获取数字num在第pos位的数值
int getDigit( const int& num, int pos )
{
int p = 1;
for (int i = 1; i < pos; i++)
{
p *= 10;
}
return (num / p) % 10;
}
// 从低位开始的基数排序
void LSDRadixSort(vector<int>& nums)
{
if (nums.size() <= 1)
return;
int bits = maxBit( nums );
vector<int> bucket(nums.size(), 0);
vector<int> count(10, 0);
for (int k = 1; k <= bits; ++k)
{
for (int i = 0; i < 10; ++i)
count[i] = 0;
for (int i = 0; i < nums.size(); ++i)
++count[getDigit(nums[i], k)];
// count[i]表示第i个桶的右边界index
for (int i = 1; i < 10; ++i)
{
count[i] += count[i - 1];
}
// 从右向左扫描,保证排序的稳定性
// 当前位不同时,能够保证后面的数比前面的数大
for (int i = nums.size() - 1; i >= 0; --i)
{
int j = getDigit( nums[i], k );
// 将nums[i]从到左依次放置
bucket[count[j] - 1] = nums[i];
// 右边界向左减一
--count[j];
}
// 保存当前的排序顺序
// 注意:这一步保证第k-1位的都已经被正确排序,并且之后该顺序不会被打乱
for (int i = 0; i < nums.size(); ++i)
nums[i] = bucket[i];
}
}
// 从高位开始的基数排序的递归调用,
// 加入了begin和end,方便对区域子桶中的数据进行进一步的排序
void mergeMSD(vector<int> &nums, int begin, int end, int bits)
{
vector<int> count(10, 0);
vector<int> bucket( end-begin, 0 );
for (int i = begin; i < end; ++i)
++count[getDigit(nums[i], bits)];
for (int i = 1; i < 10; i++)
{
count[i] += count[i - 1];
}
for (int i = end - 1; i >= begin; --i)
{
int j = getDigit( nums[i], bits );
bucket[count[j] - 1] = nums[i];
--count[j];
}
for (int i = begin; i < end; ++i)
{
nums[i] = bucket[i];
}
// 递归对每个子桶中的数据进行排序
for (int i = 0; i < 9; i++)
{
int p1 = begin + count[i];
int p2 = begin + count[i+1];
if (p1 + 1 < p2 && bits > 1)
mergeMSD( nums, p1, p2, bits-1 );
}
}
// 从高位开始的基数排序
void MSDRadixSort(vector<int> &nums)
{
if (nums.size() <= 1)
return;
int bits = maxBit(nums);
mergeMSD(nums, 0, nums.size(), bits);
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {23, 18, 98, 67, 101};
LSDRadixSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
nums = { 23, 18, 98, 67, 101 };
MSDRadixSort(nums);
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}
计数排序
- 对于一些数据非常集中的整数,可以使用这种方法进行排序
代码
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
void countSort( vector<int> &nums )
{
if (nums.size() <= 1)
return;
int maxV = nums[0];
int minV = nums[0];
for (int i = 1; i < nums.size(); ++i)
{
maxV = max( maxV, nums[i] );
minV = min(minV, nums[i]);
}
// 建立help数组,存储每个值(减去最小值之后)出现的次数
vector<int> help( maxV-minV+1, 0 );
for (int i = 0; i < nums.size(); ++i)
++help[ nums[i] - minV ];
int index = 0;
for (int i = 0; i < help.size(); ++i)
{
while (help[i] > 0)
{
nums[index++] = i + minV;
--help[i];
}
}
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {6,6,4,3,5,7,9,3,20};
countSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}
桶排序
- 数据数据偏差很大的情况(最大值与最小值偏差很大),此时使用计数排序十分浪费空间,我们可以使用桶排序。
- 建立K个桶,每个桶中放置一个子数组,保证靠前的数组中的最大值小于靠后的数组中的最小值,然后对桶内数据进行排序,最后再合并即可。
- 使用桶排序时,我们假定数据是均匀随机分布的,否则如果数据被分到同一个桶内,会造成消耗大量内存空间的基础上(空间复杂度),排序的时间复杂度也不是
O(N)
O
(
N
)
。
代码
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
void bucketSort( vector<int> &nums )
{
if (nums.size() <= 1)
return;
int maxV = nums[0];
int minV = nums[0];
for (int i = 1; i < nums.size(); ++i)
{
maxV = max(maxV, nums[i]);
minV = min(minV, nums[i]);
}
// 桶的数量,这里可以随意指定,但是对于具体情况,建议具体分析
int bucketNumber = (maxV - minV) / 10 + 1;
vector<vector<int>> bucketNums(bucketNumber);
for (int i = 0; i < nums.size(); i++)
{
int num = (nums[i] - minV) / 10;
bucketNums[num].push_back( nums[i] );
}
// 对于每个桶中使用的排序算法的复杂度就是桶排序的最坏时间复杂度
for (int i = 0; i < bucketNums.size(); i++)
{
std::sort(bucketNums[i].begin(), bucketNums[i].end());
}
int index = 0;
for (int i = 0; i < bucketNums.size(); i++)
{
for (auto &num : bucketNums[i])
nums[index++] = num;
}
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {6,6,4,3,5,7,9,3,20};
bucketSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}