数据结构与算法学习笔记13:桶排序+基数排序
桶排序(BucketSort)
- 在面试的海量数据处理题目中,如对每天数以亿计的数据进行排序,直接排序是一件很恐怖的事情,内存也无法容纳如此多的数据,这时桶排序就可以有效地降低数据的数量级,再对降低了数量级的数据进行排序,可以得到比较良好的效果。
针对于位数相同的小数的排序,将数据分成多个组,各组之内各自排序。利用的是哈希的思想。
平均时间复杂度: O ( N + C ) O(N+C) O(N+C)
最佳时间复杂度: O ( N ) O(N) O(N)
最差时间复杂度: O ( n 2 ) O(n ^ 2) O(n2)
空间复杂度: O ( N + M ) O(N + M) O(N+M)
对于待排序序列大小为 N,共分为 M 个桶,主要步骤有:N 次循环,将每个元素装入对应的桶中
O
(
N
)
O(N)
O(N);M 次循环,对每个桶中的数据进行排序(平均每个桶有
N
M
\frac{N}{M}
MN 个元素),一般使用较为快速的排序算法,快速的排序算法时间复杂度为
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN),因此每个桶的时间复杂度为
∑
O
(
N
M
∗
l
o
g
N
M
)
∑ O(\frac{N}{M}*log\frac{N}{M})
∑O(MN∗logMN),总的来说,整个桶排序的时间复杂度为:
O
(
N
+
C
)
,
C
=
N
∗
(
l
o
g
N
−
l
o
g
M
)
O(N + C),C=N*(logN-logM)
O(N+C),C=N∗(logN−logM),推导过程根据上述步骤完成如下:
O
(
N
)
+
O
(
M
∗
(
N
M
∗
l
o
g
(
N
M
)
)
)
=
O
(
N
)
+
O
(
N
∗
(
l
o
g
(
N
M
)
)
=
O
(
N
)
+
O
(
C
)
=
O
(
N
∗
(
l
o
g
(
N
M
)
+
1
)
)
O(N)+O(M∗(\frac{N}{M}∗log(\frac{N}{M}))) = O(N)+O(N∗(log(\frac{N}{M})) = O(N)+O(C)= O(N∗(log(\frac{N}{M})+1))
O(N)+O(M∗(MN∗log(MN)))=O(N)+O(N∗(log(MN))=O(N)+O(C)=O(N∗(log(MN)+1))
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到
O
(
N
)
O(N)
O(N).
额外空间复杂度: O ( N + M ) O(N + M) O(N+M);桶排序其本身是稳定的,但整体稳定性还需要取决于桶内排序使用的算法。
-
桶排序的限制:数据很容易分成n个桶,且桶与桶之间有着天然的大小顺序,数据在各个桶内分布均匀。
-
步骤:
1、根据数组取值范围"创建"一定数量的"桶"
2、遍历数组,把每个元素放到对应的"桶"中
3、当存在“一个桶中有多个元素”的情况时,要先使用合适的排序算法对各个痛内的元素进行排序
4、按照顺序遍历桶中的每个元素,依次放回数组中
("桶"是一种容器,这个容器可以用多种数据结构实现,包括数组、队列或者栈。)
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node{
int nValue;
struct Node *pNext;
}List;
void Sort(List *pHead){
if (pHead == NULL ||pHead->pNext == NULL) return;
//根本没有或者只有一个,那都不需要排序,直接return
//冒泡排序
List *pNode = pHead; //趟数
List *pTemp = NULL; //遍历
while(pNode->pNext != NULL){
pTemp = pHead;
while (pTemp->pNext != NULL) {
if(pTemp->nValue > pTemp->pNext->nValue){
pTemp->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
pTemp->pNext->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
pTemp->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
}
pTemp = pTemp->pNext;
}
pNode = pNode->pNext;
}
}
void BucketSort(int arr[],int nLength){
if(arr == NULL || nLength <= 0)
return;
//最大值最小值
int nMax = arr[0];
int nMin = arr[0];
for(int i = 0;i < nLength;i++){
if(arr[i] > nMax){
nMax = arr[i];
}
if (arr[i] < nMin) {
nMin = arr[i];
}
}
//计算数字位数
int nCount = 1;
int nNum = nMax;
while(nNum){
nNum = nNum / 10; //每次去掉数字最后一位
nCount *= 10; //循环一次计数器+1
}
nCount /= 10;
//拆分高位
int nMinIndex = nMin / nCount % 10;
int nMaxIndex = nMax / nCount % 10;
//申请桶子
List **pBucket = NULL;
pBucket = (List**)malloc(sizeof(List*)*(nMaxIndex - nMinIndex + 1));
memset(pBucket, 0, sizeof(List*)*(nMaxIndex - nMinIndex + 1));
//元素入桶
List *pTemp = NULL;
int nIndex;
for(int i = 0;i < nLength;i++){
nIndex = arr[i] / nCount % 10 - nMinIndex;
//头插
pTemp = (List*)malloc(sizeof(List));
pTemp->nValue = arr[i];
pTemp->pNext = pBucket[nIndex];
pBucket[nIndex] = pTemp;
}
//各桶内元素排序
for(int i = 0;i < nMaxIndex - nMinIndex + 1;i++){
Sort(pBucket[i]);
}
//将桶内元素放回原数组
nNum = 0;
for(int i = 0; i < nMaxIndex - nMinIndex + 1;i++){
pTemp = pBucket[i];
while (pTemp) {
arr[nNum] = pTemp->nValue;
nNum++;
pTemp = pTemp->pNext;
}
}
//释放
List *pDel = NULL;
for(int i = 0; i < nMaxIndex - nMinIndex + 1;i++){
pTemp = pBucket[i];
while (pTemp) {
pDel = pTemp;
pTemp = pTemp->pNext;
free(pDel);
pDel = NULL;
}
}
free(pBucket);
pBucket = NULL;
}
void Print(int arr[],int nLength){
if(arr == NULL || nLength <= 0)
return;
for (int i = 0; i < nLength; i++) {
printf("%d ",arr[i]);
}
printf("\n");
}
int main(){
int arr[] = {101,722,811,119,221,119,662,743,838};
BucketSort(arr, sizeof(arr)/sizeof(arr[0]));
Print(arr, sizeof(arr)/sizeof(arr[0]));
return 0;
}
基数排序(RadixSort)
利用数字规律排序,基于非比较的排序,稳定!
LSD:低位优先
MSD:高位优先
时间复杂度: O ( d ∗ ( n + r ) ) O(d*(n+r)) O(d∗(n+r))
空间复杂度: O ( n + r ) O(n+r) O(n+r)
稳定性:稳定
-
设待排序的数组 R [ 1.. n ] R[1..n] R[1..n],数组中最大的数是 d d d位数,基数为 r r r(如基数为10,即10进制,最大有10种可能,即最多需要10个桶来映射数组元素)。处理一位数,需要将数组元素映射到 r r r个桶中,映射完成后还需要收集,相当于遍历数组一遍,则时间复杂度为 O ( n + r ) O(n+r) O(n+r)。所以,总的时间复杂度为 O ( d ∗ ( n + r ) ) O(d*(n+r)) O(d∗(n+r))
-
基数排序过程中,用到一个计数器数组,长度为 r r r,以及桶内元素的个数,所以空间复杂度为 O ( n + r ) O(n+r) O(n+r)
-
基数排序基于分别排序,分别收集,所以是稳定的。
![img](https://i-blog.csdnimg.cn/blog_migrate/6efa0aad45343c527fd7346c221fa7a5.png)
-
步骤:
1、遍历序列找出最大的数,确定最大的数是几位数;
2、申请表头(10),按位处理(LSD或者MSD),
3、按位入桶,采用尾添加,放回
4、下位重复上一步骤
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node{
int nValue;
struct Node *pNext;
}Radix;
void RadixSort(int arr[],int nLength){
if(arr == NULL || nLength <= 0)
return;
//最大值
int nMax = arr[0];
for(int i = 0;i < nLength;i++){
if(arr[i] > nMax){
nMax = arr[i];
}
}
//计算最大值位数
int nCount = 1;
while (nMax) {
nCount *= 10;
nMax /= 10;
}nCount /= 10;
//申请表头
Radix **pRadix = NULL;
pRadix = (Radix**)malloc(sizeof(Radix*)*10);
memset( pRadix, 0, sizeof(Radix*)*10);
//按位处理
for (int i = 1; i <= nCount; i *= 10) {
//入组
int nIndex;
Radix *pTemp = NULL;
for(int j = 0;j < nLength;j++){
pTemp = (Radix*)malloc(sizeof(Radix));
pTemp->nValue = arr[j];
pTemp->pNext = NULL;
nIndex = arr[j]/i % 10;
//尾添加
if (pRadix[nIndex] == NULL) {
pRadix[nIndex] = pTemp;
}else{
Radix *pNode = pRadix[nIndex];
while (pNode->pNext != NULL) {
pNode = pNode->pNext;
}
pNode->pNext = pTemp;
}
}
//放回原数组
int k = 0;
for(int i = 0; i < 10;i++){
pTemp = pRadix[i];
while (pTemp) {
arr[k] = pTemp->nValue;
k++;
pTemp = pTemp->pNext;
}
}
//释放
Radix *pDel = NULL;
for(int i = 0; i < 10;i++){
pTemp = pRadix[i];
while (pTemp) {
pDel = pTemp;
pTemp = pTemp->pNext;
free(pDel);
pDel = NULL;
}
}
//表头清空
memset(pRadix, 0, sizeof(Radix*)*10);
}
free(pRadix);
pRadix = NULL;
}
void Print(int arr[],int nLength){
if(arr == NULL || nLength <= 0)
return;
for (int i = 0; i < nLength; i++) {
printf("%d ",arr[i]);
}
printf("\n");
}
int main(){
int arr[] = {11,72,811,1,32,17,22,156,11};
RadixSort(arr, sizeof(arr)/sizeof(arr[0]));
Print(arr, sizeof(arr)/sizeof(arr[0]));
return 0;
}