「数组」计数排序|桶排序|基数排序(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

#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。

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

#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();
	}
}
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]。

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

...

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

例如:

     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中提供。

#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();
		}
	}
}

总结

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

  • 18
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值