今天做了个后缀数组 , 其中有部分代码需要基排思想所以温故一下,
开始讲解吧;
基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r )m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
操作过程
(1)假设有欲排数据序列如下所示:73 22 93 43 55 14 28 65 39 81首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。分配结果(逻辑想象)如下图所示:
(2)分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下仍然无序的数据序列:
81 22 73 93 43 14 55 65 28 39
(3)接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:
算法实现
#include<iostream>
#include<vector>
#include<random>
#include<ctime>
#include<iterator>
#include<algorithm>
#include<cmath>
using namespace std;
/*
基数排序
简化了问题,待排向量为整型,且为三位数
*/
void radixSort(vector<int> & source)
{
const int BUCKETS = 10;
vector<vector<int>> buckets(BUCKETS);
//外层循环是根据数字的位数确定的。因为是三位数,所以从2到0
for (int pos = 0; pos <= 2; ++pos)
{
//pos = 0, 表示个位数
//pos = 1, 表示十位数
//pos = 2, 表示百位数
int denominator = static_cast<int>(pow(10, pos)); // 取某一位数的时候需要用的分母
for (int & tmp : source) // 按数字放入桶中
buckets[(tmp / denominator) % 10].push_back(tmp);
int index = 0;
// 从桶中取出来,放入原来的source序列中,以备下次遍历时使用
for (auto & theBucket : buckets)
{
for (int & k : theBucket)
source[index++] = k;
theBucket.clear();//一定要清空桶中数据
}
}
}
/*输出向量*/
template<typename T>
void printVector(vector<T> & v)
{
copy(v.cbegin(), v.cend(), ostream_iterator<T>(cout, " "));
cout << endl;
}
int main()
{
int n,tmp;
cin>>n;
vector<int> source;
for (int i = 0; i < n; i++){
cin>>tmp;
source.push_back(tmp);
}
radixSort(source);//sort
printVector(source);//print
return 0;
}
算法实现(Java)
// 基数排序的实现,有两种方式,低位优先法,适用于位数较小的数排序 简称LSD
// 高位优先法,适用于位数较多的情况,简称MSD
// 这里实现低位优先法
public static void LSDSort(int a[]) {
int max = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max)
max = a[i];
}
int len = 0; // 存贮最大的数的位数,用来判断需要进行几轮基数排序
while (max > 0) {
max = max / 10;
len++;
}
for (int times = 0; times < len; times++) // 按位数运行几次,每次都分裂成10份,在顺序链接
{
// 以下内容应为每次运行时,分割成0-9 个桶,然后顺序链接
@SuppressWarnings("unchecked")
Vector<Integer> num[] = new Vector[10];
for (int i = 0; i < num.length; i++) {
num[i] = new Vector<Integer>();
}
int k = 1; // 用来取出当前的对应的位数的数
for (int i = 0; i < times; i++) {
k = k * 10;
}
for (int i = 0; i < a.length; i++) // 对每一个数进行判断位数
{
int x = 0; // 用来表示当前的位数上的数的大小
x = Math.abs(a[i]) / k; // 使用绝对值,防止受到正负号的影响
x = x % 10;
num[x].add(new Integer(a[i]));
}
// 将排序的结果顺序连接起来
int count = 0;
for (int i = 0; i < num.length; i++) {
for (int j = 0; j < num[i].size(); j++) {
a[count++] = num[i].get(j).intValue();
}
}
}
// 判断完绝对值大小顺序后,需要判断正负了
Vector<Integer> plus = new Vector<Integer>(); // 储存正数
Vector<Integer> minus = new Vector<Integer>(); // 储存负数
for (int i = 0; i < a.length; i++) {
if (a[i] <= 0) {
minus.add(new Integer(a[i]));
} else {
plus.add(new Integer(a[i]));
}
}
int count = 0;
for (int i = minus.size() - 1; i > 0; i--) // 因为这是按照绝对值大小排列的,所以要倒序
{
a[count++] = minus.get(i);
}
for (int i = 0; i < plus.size(); i++) {
a[count++] = plus.get(i);
}
}
效率分析(复杂度分析)
性能分析:
时间复杂度:
在基数排序中,r为基数,d为位数。则基数排序的时间复杂度为O(d(n+r))。
我们可以看出,基数排序的效率和初始序列是否有序没有关联。
空间复杂度:
对于任何位数上的基数进行“装桶”操作时,都需要n+r个临时空间。
算法稳定度
在基数排序过程中,每次都是将当前位数上相同数值的元素统一“装桶”,并不需要交换位置。所以基数排序是稳定的算法。
日拱一卒,功不唐捐 -2020/7/13