「数组」计数排序|桶排序|基数排序(C++)

目录

概述

1.计数排序

思路

复杂度

Code

2.桶排序

思路

复杂度

Code

3.基数排序

思路

复杂度

Code

总结


概述

与以前我们遇到的所有排序都不同的是,今天我们介绍的计数排序|桶排序|基数排序是非比较排序

非比较排序用一种独到的方式来进行排序,但并不进行元素间的比较。

那是怎么排序的呢?

它预先设置了一些已经排好序的辅助空间,将待排序元素发配到其中,然后再依次取出。

简单来说,这种排序只关心元素的绝对特征,而不该关心元素的相对特征。


1.计数排序

思路

利用cnt数组对原数组进行元素统计。

定义长度为待排序数组arr最大元素值+1的辅助数组cnt[],遍历待排序数组arr[i],进行cnt[arr[i]]++操作。

意为:cnt[x]记录了原数组中x的出现次数。

将cnt中的数据依次释放回原数组,即完成排序。

复杂度

时间复杂度: O(w)

空间复杂度: O(w)

w:arr中的最大元素值。

看起来效率很高,但真的是这样吗?

事实上,计数排序只能用于稠密的小范围非负整数元素

一旦arr中的最大值极大且元素分布及其稀疏,都会造成极大量的空间占用和时间占用。

如果元素不能作为cnt数组下标(如负数或浮点数),计数排序都会直接失效。

(你可能会想到用哈希表代替cnt数组,但哈希值的生成也会占用时间,并有可能触发哈希碰撞)

Code

cpp 复制代码
#include <algorithm>
void count_sort(int arr[], int len) {
	int size = *max_element(arr, arr + len) + 1;
	int* cnt=new int[size]();
	for (int i = 0; i < len; i++)cnt[arr[i]]++;
	for (int j = 0, k = 0; j < size; j++)
		while(cnt[j])arr[k++] = cnt[j]--;
	delete[] cnt;
}

2.桶排序

思路

桶排序是优化过的计数排序,它在大范围内进行非比较类排序,小范围内进行比较类排序。

我们知道计数排序的痛点在于数组下标和数据范围。那怎么对此调整优化呢?

你可以认为计数排序也是一种特殊的桶排序,只不过每个桶里都只有一个元素。

桶排序是一类广义概念,它只是对元素进行了范围划分,并以此分装在不同的桶中,保证每个桶都代表一个数据范围,前一个桶的所有元素小于后一个桶的所有元素。在桶里则采用比较型排序(如插入排序),排序后,依次将桶中元素释放回原数组。

一般,我们定义原数组元素最大值为max_limit,最小值为min_limit。

cpp 复制代码
int num_of_buckets = (max_limit - min_limit) / len + 1;
int size_of_bucket = (max_limit - min_limit) / num_of_buckets + 1;

桶数量的计算方式是人为规定,你也可以选择其他的方式计算,或直接使用固定数量,无需纠结。

桶范围的计算方式则是固定的。

*注意*:+1是为了规避整除结果取0。

复杂度

时间复杂度: O(n+n²/k+k)

空间复杂度: O(n)

k:桶数量

复杂度分析:

时间分析:

元素依次入桶,复杂度为n。

桶内排序对n/k个元素进行插入排序,小范围插入排序复杂度为n,n*n/k=n²/k。

各个桶内元素释放回原数组时使用memcpy函数,k个桶的整体复杂度为k。

事实上,桶排序只能用于具有范围概念的元素

并且,对于我们提供的桶数量计算方式,我们期望桶排序的对象是稠密且均匀的,这样分桶数量几乎可以到达计数排序级别,否则会被动地分出大量空桶。

但是好在桶排序解决了计数排序的最大范围限制和元素类型限制。

Code

cpp 复制代码
#include <algorithm>
#include <vector>
void bucket_sort(int arr[], int len) {
	using bucket = vector<int>;
	int max_limit = *max_element(arr, arr + len);
	int min_limit = *min_element(arr, arr + len);
	int num_of_buckets = (max_limit - min_limit) / len + 1;
	int size_of_bucket = (max_limit - min_limit) / num_of_buckets + 1;
	vector<bucket>buckets(num_of_buckets);
	for (int i = 0; i < len; i++)buckets[(arr[i] - min_limit) / size_of_bucket].push_back(arr[i]);
	for (bucket& b : buckets)insertion_sort(b.data(), b.size());
	int k = 0;
	for (const bucket& b : buckets) {
		memcpy(arr + k, b.data(), b.size() * sizeof(int));
		k += b.size();
	}
}
cpp 复制代码
void insertion_sort(int arr[], int len) {
	for (int i = 1; i < len; i++) {
		int temp = arr[i], j = i - 1;
		for (; j >=0; j--) {
			if (temp<arr[j])arr[j + 1] = arr[j];
			else break;
		}
		arr[j+1] = temp;
	}
}

3.基数排序

思路

基数排序是另一种计数排序的优化方案,它规避了计数排序对稠密小范围数据的要求,但是它对元素类型的要求同样严苛,必须是整数,因为它是基于整数特征实现的算法。

计数排序只有十个桶buckets[10],表示[0,9]数字,将数据按位统计,即:

先对元素的个位进行计数排序,将个位同为x的元素放入buckets[x]。

将数据释放回原数组,现在所有元素的个位有序;

先对元素的十位进行计数排序,将十位同为x的元素放入buckets[x],十位为0则放入buckets[0]。

将数据释放回原数组,现在所有元素的十位有序;

...

直到所有元素的最高位有序,则整体有序。

例如:

cpp 复制代码
     i   0    1    2    3    4    5    6
nums[i]  1    22   31   45  671  398   6
按个位排序:
nums[i]  1    31   671  22   45   6   398
         *     *     *   *    *   *     *
按十位排序;
nums[i]  1    6    22   31   45  671  398
                   *    *    *    *    *
按百位排序:
nums[i]  1    6    22   31   45  398  671
                                 *    *

复杂度

时间复杂度: O(n*w)

空间复杂度: O(n)

w:最大元素的数量级+1

事实上,基数排序只能用于整数元素,但不对数据范围有任何要求

Code

*注意*:理论上,基数排序支持负整数排序,但我们并未在以下code中提供。

cpp 复制代码
#include <algorithm>
void radix_sort(int arr[], int len) {
	using bucket = vector<int>;
	bucket buckets[10];
	int limit = *max_element(arr, arr + len);
	for(int mask=1;mask<limit;mask*=10){
		for (int i = 0; i < len; i++)buckets[arr[i] / mask % 10].push_back(arr[i]);
		for (int j = 0, k = 0; j < 10; j++) {
			memcpy(arr + k, buckets[j].data(), buckets[j].size() * sizeof(int));
			k += buckets[j].size();
			buckets[j].clear();
		}
	}
}

总结

以上三种排序都是非比较类排序,它们只关注元素的绝对特征,而忽视相对特征,同时,也被称为时间换空间类型算法,虽然适用范围较为狭窄,但是在适用范围内时间效率较高。

相关推荐
liuming19928 分钟前
Halcon中histo_2dim(Operator)算子原理及应用详解
图像处理·人工智能·深度学习·算法·机器学习·计算机视觉·视觉检测
sc写算法25 分钟前
Hash 映射
数据结构·算法·哈希算法
雅妮yyn37 分钟前
头歌数据结构-排序的实现及其应用
数据结构·算法
煤泥做不到的!37 分钟前
挑战一个月基本掌握C++(第六天)了解函数,数字,数组,字符串
开发语言·c++
云边有个稻草人38 分钟前
【优选算法】—移动零(双指针算法)
算法·排序算法·双指针算法
智能与优化1 小时前
C++打造局域网聊天室第十一课: 程序关闭及线程的结束
开发语言·c++
小墨&晓末1 小时前
【PythonGui实战】自动摇号小程序
python·算法·小程序·系统安全
小王爱吃月亮糖2 小时前
C++进阶-1-单继承、多继承、虚继承
开发语言·c++·笔记·学习·visual studio
落魄君子2 小时前
SVM分类-支持向量机(Support Vector Machine)
神经网络·算法·支持向量机·分类
Am心若依旧4092 小时前
[c++进阶(三)]单例模式及特殊类的设计
java·c++·单例模式