标签:注意 har 时间 ack 概念 name bucket 空间复杂度 地方
01.计数排序、桶排序与基数排序
并不是所有的排序 都是基于比较的,计数排序和基数排序就不是。基于比较排序的排序方法,其复杂度无法突破\(n\log{n}\) 的下限,但是 计数排序 桶排序 和基数排序是分布排序,他们是可以突破这个下限达到O(n)的的复杂度的。
1. 计数排序
概念
计数排序是一种稳定的线性时间排序算法。计数排序使用一个额外的数组C
,使用 C[i]
来计算 i
出现的次数。然后根据数C
来将原数组A中的元素排到正确的位置。
复杂度
计数排序的最坏时间复杂度、最好时间复杂度、平均时间复杂度、最坏空间复杂度都是O(n+k)
。n为元素个数,k为待排序数的最大值。
优缺点
计数排序不是比较排序,排序的速度优于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加1),这使得对于数组中数据范围很大的数组,需要大量的时间和内存。(简言之,不适于大范围数组)
通俗地理解,例如有10个年龄不同的人,统计出有8个人的年龄比A小,那A的年龄就排在第9位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去1的原因。算法的步骤如下:
- 找出待排序数组中的最大元素和最小元素。
- 统计数组中值为 i 的元素出现的次数,存入数组C的第 i 项。
- 对所有的计数累加(从C中第一个元素开始,每一项和前一项累加)。
- 反向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项, 每放一个元素就将 C[i] 减去1.
C语言实现
#include
#include
#include
void print_arr(int *arr, int n)
{
int i;
printf("%d", arr[0]);
for (i = 1; i 0; j--)
{
int elem = ini_arr[j - 1]; // 取待排序元素
int index = count_arr[elem] - 1; // 取待排序元素在有序数组中的序号
sortrd_arr[index] = elem; // 将待排序数组存入结果数组中
count_arr[elem]--; // 修正排序结果,保证sorted_arr数组中元素的稳定性
}
/*
* 上述句子也可以写为:
* sortrd_arr[--count_arr[ini_arr[j -1]]] = ini_arr[j -1];
*
*/
free(count_arr);
}
int main(int argc, char **argv)
{
int n =10;
int i;
int *arr = (int *)malloc(sizeof(int) * n);
int *sorted_arr = (int *)malloc(sizeof(int) * n);
srand(time(0));
for (i = 0; i
2. 桶排序
概念
桶排序(Bucket sort)或所谓的箱排序,其工作原理是将阵列分到有限数量的桶里。每个桶再分别排序。桶排序是分布排序,不是比较排序,因而不受到比较排序 O(\(n\log{n}\))下限的影响。
复杂度
最坏时间复杂度是O(n^2), 平均复杂度是O(n+k),最坏空间复杂度是O(n*k)。
关键步骤
- 设置一个定量的阵列当作空桶子。
- 寻访序列,并且把项目一个一个放到对应的桶子去。
- 对每个不是空的桶进行排序。可以在放入元素的时候进行插入排序,也可以在写回的时候进行快速排序。
- 从不是空的桶子里把项目放回到原来的序列中。
算法实现
假设数据分布在[0, 100]之间,每个桶内部用链表表示,在数据入桶的同时插入排序。然后把各个桶中的数据合并。
#include
#include
#include
using namespace std;
const int BUCKET_NUM = 10;
// 数据结构的定义,explicit表示不允许构造函数发生隐式转换
struct ListNode {
explicit ListNode(int i = 0): mData(i), mNext(NULL){};
ListNode *mNext;
int mData;
};
ListNode *insert(ListNode *head, int val)
{
ListNode dummyNode; // 节点指针
ListNode *newNode = new ListNode(val); //待插入的新节点
ListNode *pre , *curr;
dummyNode.mNext = head; // 将指针指向链表头部
pre = &dummyNode; // 设置临时指针。pre是当前检查元素的上一个元素
curr = head;
while (NULL != curr && curr->mData mNext;
}
newNode->mNext = curr; // 改变指针指向
pre ->mNext = newNode;
return dummyNode.mNext; // 返回链表头节点
}
ListNode *Merge(ListNode *head1, ListNode *head2) // 将head2合并到head1
{
ListNode dummyNode;
ListNode *dummy = &dummyNode; // 临时指针
while(NULL != head1 && NULL != head2) // 循环直到末尾
{
if (head1->mData mData) // 类似于归并排序
{
dummy->mNext = head1;
head1 = head1 -> mNext;
}else{
dummy->mNext = head2;
head2 = head2->mNext;
}
dummy = dummy->mNext;
}
if (NULL != head1) dummy->mNext = head1;
if(NULL!=head2) dummy->mNext = head2;
return dummyNode.mNext;
}
void BucketSort(int n,int arr[]){
vector buckets(BUCKET_NUM,(ListNode*)(0));
// 将元素分配到桶
for(int i=0;imData;
head = head->mNext;
}
}
3. 基数排序
定义
基数排序是桶排序的扩充,也是一种分布排序。其原理是将整数按位数切割为不同的数字,然后按每个数分别比较。根据比较的方向,基数排序又可以分为MSD(从左到右)和LSD(从右向左)
LSD原理 将所有带比较数值统一为同样的数位长度,数位较短的前面补0,。然后,从最低位开始,进行一次排序,一直到最高位排序完成以后,数列就变成一个有序数列。其思想就是,将待排序数据中的每组关键字依次进行桶分配。
MSD原理
msd算法从左向右遍历字符。其核心思想是分治,我们采用递归的方法来实现。原理如下:
- 首先,使用键索引排序的方法对首字母进行排序,此时排好序的数组已经是首字母有序的数组,并且已经按照首字字母分好了组。
- 按照分好的组,递归的对每个首字母对应的子数组进行排序。
- 重复步骤二。
复杂度
最坏时间复杂度是O(kN),最坏空间复杂度是O(k+N);
LSD实现
/**
* 基数排序:C 语言
*
*
*/
#include
// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 获取数组a中最大值
*
* 参数说明:
* a -- 数组
* n -- 数组长度
*/
int get_max(int a[], int n)
{
int i, max;
max = a[0];
for (i = 1; i max)
max = a[i];
return max;
}
/*
* 对数组按照"某个位数"进行排序(桶排序)
*
* 参数说明:
* a -- 数组
* n -- 数组长度
x* exp -- 指数。对数组a按照该指数进行排序。
*
* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
* (01) 当exp=1表示按照"个位"对数组a进行排序
* (02) 当exp=10表示按照"十位"对数组a进行排序
* (03) 当exp=100表示按照"百位"对数组a进行排序
* ...
*/
void count_sort(int a[], int n, int exp)
{
// 存储"被排序数据"的临时数组
int *output = (int *)malloc(sizeof(int)*n);
int i, buckets[10] = {0};
// 将数据出现的次数存储在buckets[]中
for (i = 0; i = 0; i--)
{
output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
buckets[ (a[i]/exp)%10 ]--;
}
// 将排序好的数据赋值给a[]
for (i = 0; i 0; exp *= 10)
count_sort(a, n, exp);
}
void main()
{
int i;
int a[] = {53, 3, 542, 748, 14, 214, 154, 63, 616};
int ilen = LENGTH(a);
printf("before sort:");
for (i=0; i
msd实现
/*
MSD(Most Significant Digit First) 高位优先的字符串排序
该算法基于键索引计数法的思想,进行了扩展,使得该算法可以
处理不等长的字符串排序,其中涉及两个关键点。
1、采用分治法,从高位向低位的方向,依次选取关键字做为排序
的键字进行排序,每一轮排序后,将字符串组进行拆分,对拆
分后的每个子组分别进行排序,这里子组拆分的依据就是本轮
键索引计数法排序之后的分类组。
如下示例,最初只有一个字符串组,其中组成员数为5个字符串
首先,选择第0列做为排序的链字进行键索引计数法排序,排序
完成后,按分类组划分,此时分为了两组(见第一次后的情况),
这时候对这两组分别进行链索引计数法排序,注意这时每组第
0列已经为相同字符,所以此时选择第1做为排序的链字进行键
索引计数法排序,在第二次排序后,此时已经分为了4组lp字符串
组,依次类推,直到所有子组仅含有一个成员,所有子组排序
处理完后,即整个字符串排序算法完成。
原始: 第一次后: 第二次后:
abcd abcd abcd
ddba acca
daca acca
acca ddba
daab daca daca
daab daab
ddba
2、刚才提到了该算法可以处理不等长的字符串排序,该算法采用一
种比较巧妙的方法,将短字符串长度已经满足不了排序的处理也
做为键值比较处理了,同时如果短字符串长度满足不了排序处理
时,该键值优先级最高,所以就会出现排在最上方。
如下示例,当第2列(字符c的位置)处理完后,开始进行第3列比较
处理,此时第一个条目abcd的第3列键值为d、第二个条目abc的第
3列键值已经不存在,长度已经满足不了排序,但此时键值优先级
为最高,第三个条目abcde的第3列键值为d,所以本轮最终将第二
个条目abc排在了最上面,相同原理,abcd条目就会比abcde条目的
优先级高。
原始: 排序后:
abcd abc
abc abcd
abcde abcde
*/
#include
#include
#include
#include
const int R = 256; // 基数
const int M = 15; // 小数组使用插入排序的阈值
using namespace std;
int charAt(const string& str, int d)
{
if ( d & sVec, int lo, int hi, int d, vector& aux)
{
int i,r;
/*
* 这里存在一个优化:当数组数量较少的时候,我们可以使用插入排序优化算法
*
* 故而下列return语句可以改写为:
* if (hi sVec;
ifstream infile("data.txt");
cout>str)
{
cout aux(n);
MSD_sort(sVec, 0, n-1, 0, aux);
cout
msd算法的性能及其改进
我们从三个方面衡量算法的性能:需要检查的字符数量,统计字符出现频率时所需的时间和空间,将频率转化为索引所需的时间和空间。
msd算法的性能取决于数据,因为他并非是比较排序,键的顺序并不重要,所需关注的只是键所对应的值:
用msd算法对基于大型字母表的字符串排序时,msd算法可能消耗大量的时间和空间,特别是在有大量重复字符串的情况下。
msd算法可以注意的地方:
- 小数组时可以使用改进后的插入排序。
- 对于含有大量等值键的子数组排序会比较慢。msd的最坏情况就是所有的键均相同。
- 额外空间的占用。为了进行切分,msd算法使用了两个辅助数组,一个是aux, 一个是count。本质上,使用aux只是为了保证稳定性。aux当然可以舍弃,但是这样就不会有稳定性了。
4.参考链接
- https://segmentfault.com/a/1190000012923917
- https://zh.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F
- https://zh.wikipedia.org/wiki/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F
- https://blog.csdn.net/liujianfeng1984/article/details/48488597
- https://blog.csdn.net/xuelabizp/article/details/50781616
【算法】计数排序、桶排序和基数排序详解
标签:注意 har 时间 ack 概念 name bucket 空间复杂度 地方
原文地址:https://www.cnblogs.com/imilano/p/9655754.html