三种线性排序算法(计数、基数、桶排序)的简单实现

一、计数排序

计数排序假设n个输入元素中的每一个都是介于0到k之间的整数。此处k为某个整数(输入数据在一个小范围内)。

基本思想:

计数排序的基本思想是对每一个输入元素x,确定出小于x的元素的个数。然后再将x直接放置在它在最终输出数组中的位置上。

如下图所示:

由于数组中可能有相等的数,在处理时需要注意。

时间复杂度和空间复杂度分析

算法总时间Θ(k + n)。当k=O(n)时,计数排序的运行时间是Θ(n)。

空间复杂度是O(n+k)。需要两个辅助数组:存放排序结果的数组B[n],存放临时结果的C[k]。

计数排序是稳定的排序算法:具有相同值的元素在输出数组中的相对次序与它们在输入数组中的相对次序相同。

代码实现:

#include<iostream>
#include<vector>
using namespace std;

void countSort(vector<int> &nums , vector<int> &result)
{
	int len = nums.size();
	if(len == 0)
		return;
	int maxNum = INT_MIN;
	for(int i = 0 ; i < len ; i++)
		maxNum = max(maxNum , nums[i]);	//找出最大值,用于确定辅助数组的大小

	vector<int> tmp(maxNum+1 , 0);//申请辅助数组,注意要+1,因为最大数字的下标对应maxNum(注:这里的maxNum就是上面的k)

	for(int i = 0 ; i < len ; i++)
		tmp[nums[i]]++;		//统计每个数字出现的次数

	for(int i = 1 ; i <= maxNum ; i++)
	{
		tmp[i] += tmp[i-1];	//把出现的次数累加
	}

	for(int i = len-1 ; i >= 0 ; i--)
	{
		result[tmp[nums[i]]-1] = nums[i];//这里有个-1是因为result是从0开始,与书上不同
		tmp[nums[i]]--;
	}
}

int main()
{
	int data[] = {2,5,3,0,2,3,0,3};
	//int data[] = {2,3,7,1,9,4,4,4,6,0};
	int len = sizeof(data) / sizeof(int);
	vector<int> nums(data , data+len);
	vector<int> result(len , 0);
	cout<<"排序前的数组为:";
	for(int i = 0 ; i < len ; i++)
		cout<<nums[i]<<" ";
	cout<<endl;
	countSort(nums , result);
	cout<<"排序后的数组为:";
	for(int i = 0 ; i < len ; i++)
		cout<<result[i]<<" ";
	cout<<endl;
	return 0;
}

程序执行结果:

[[email protected] cctmp]# ./a.out
排序前的数组为:2 5 3 0 2 3 0 3
排序后的数组为:0 0 2 2 3 3 3 5
[[email protected] cctmp]#

二、基数排序

基本思想:

基数排序是从低位到高位依次对所有的数进行排序。如果所有的数最高位数是d,那么先按最低有效位数字进行排序,得到一个结果。然后往高位重复这个过程。

排序过程如图所示:

需要注意的是,按位排序必须是稳定的排序算法。经常采用上面的计数排序。

时间复杂度和空间复杂度分析

给定n个d位数,每一个数位可能取值中数是k,如果所用的稳定的按位排序时间复杂度是Θ(n+k),基数排序时间复杂度是Θ(d(n+k))。空间复杂度O(n+k)。

当d为常数,k=O(n)时,基数排序有线性时间复杂度。

关于如何将每个关键字分解成若干数位方面,有另外一个定理:

给定n个b维数和任何正整数r<=b,基数排序能在Θ((b/r)(n+2^r))时间内对这些数进行排序。

这里,对一个值r<=b,将每个关键字看做是有d = floor(b/r)个数字,每个数字含有r位,再进行计数排序。

上述式子可以推导得到Θ(n)复杂度。

但是这并不意味着基数排序比基于比较的排序算法比如快排更好!因为隐含在记号中的常数因子是不同的。哪一个排序算法更好取决于底层机器的实现特性,比如快排同==排通常可以更有效地利用硬件缓存。同时还取决于输入数据。而且利用计数排序作为中间稳定排序不是原地排序。

代码实现:

#include<iostream>
#include<vector>
using namespace std;

//求最大数字的位数
int maxbit(vector<int> &nums)
{
	int len = nums.size();
	if(len == 0)
		return 0;
	int maxNum = nums[0];
	for(int i = 1 ; i < len ; i++)
		maxNum = max(maxNum , nums[i]);
	int d = 0;
	while(maxNum > 0)
	{
		maxNum /= 10;
		++d;
	}
	return d;
}

void radixSort(vector<int> &nums)
{
	int d = maxbit(nums);
	int len = nums.size();
	vector<int> tmp(len , 0);
	vector<int> count(10 , 0);
	int radix = 1 ,i , j , k;

	//从后向前对每一位排序,使用计数排序法
	for(i = 1 ; i <= d ; i++)
	{
		count.assign(10 , 0);//把count清零
		for(j = 0 ; j < len ; j++)
		{
			k = (nums[j] / radix) % 10;//k为nums[j]的倒数第i位数
			count[k]++;		//统计第k位出现的次数
		}
		for(j = 1 ; j < 10 ; j++)
			count[j] += count[j-1];

		for(j = len-1 ; j >= 0 ; j--)
		{
			k = (nums[j] / radix) % 10;
			tmp[count[k]-1] = nums[j];//与计数的区别在这里
			count[k]--;
		}
		for(j = 0 ; j < len ; j++)
			nums[j] = tmp[j];	//更新排序后的数组

		radix *= 10;
	}
}

int main()
{
	int data[] = {17243,5,213,17312,11858,3098,13288};
	int len = sizeof(data) / sizeof(int);
	vector<int> nums(data , data+len);
	cout<<"排序前的数组为:";
	for(int i = 0 ; i < len ; i++)
		cout<<nums[i]<<" ";
	cout<<endl;
	radixSort(nums);
	cout<<"排序后的数组为:";
	for(int i = 0 ; i < len ; i++)
		cout<<nums[i]<<" ";
	cout<<endl;
	return 0;
}

程序执行结果:

[[email protected] cctmp]# ./a.out
排序前的数组为:17243   5       213     17312   11858   3098    13288
排序后的数组为:5       213     3098    11858   13288   17243   17312

三、桶排序

基本思想:

将区间[0,1)分成n个相同大小的子区间,或称为桶。然后将n个输入元素分布到各个桶中去。每个桶中的元素用一个链表来存储。

如图所示:

简而言之就是把所有数字放到对应的BUCKET_NUM 个桶中(比如图中采用除数法(/10)),每个桶维持一个链表,对链表排序。然后对这些有序链表合并即可。

时间和空间复杂度分析

时间复杂度是O(n)。空间复杂度是O(n)。需要一个辅助数组来存放桶(链表)。

即使输入不满足均匀分布,桶排序也仍然可以以线性时间运行,只要输入满足这样一个条件:各个桶尺寸的平方和与总的元素呈线性关系。桶排序是稳定排序算法。

代码实现:

#include<iostream>
#include<vector>
#include<time.h>
using namespace std;

const int BUCKET_NUM = 10;	//桶的个数
const int NUM_SIZE = 20;	//数字个数

//桶内的链表结点
struct ListNode
{
	int mData;
	ListNode *mNext;
	ListNode(){}
	ListNode(int data):mData(data),mNext(NULL){}
};

ListNode *insert(ListNode * , int);	//把点插入链表
ListNode *merge(ListNode * , ListNode *);	//合并两个有序链表

//桶排序算法
void bucketSort(vector<int> &nums)
{
	ListNode *buckets[BUCKET_NUM] = {NULL};	//建立BUCKET_NUM个桶(指针数组,数组内的每个元素是一个指向链表的指针)
	int len = nums.size();
	if(len == 0)
		return;
	for(int i = 0 ; i < len ; i++)
	{
		int index = nums[i] / BUCKET_NUM;	//找出该数字插入到哪个桶中
		ListNode *head = buckets[index];	//取得该桶的头结点
		buckets[index] = insert(head , nums[i]);	//把数字插入到桶中
	}

	//合并所有桶(桶内数字有序)
	ListNode *head = buckets[0];
	for(int i = 1 ; i < BUCKET_NUM ; i++)
	{
		head = merge(head ,buckets[i]);
	}

	//把排好序的数字赋给数组
	for(int i = 0 ; i < len ; i++)
	{
		nums[i] = head->mData;
		head = head->mNext;
	}
}

//把新结点插入到链表中合适的位置,使链表有序(其实是插入排序)
ListNode *insert(ListNode *head , int val)
{
	//如果链表为空,或者val比头结点小,则新结点作为头结点
	if(head == NULL || val < head->mData)
	{
		ListNode *newHead = new ListNode(val);
		newHead->mNext = head;
		head = newHead;
		return head;
	}

	ListNode *pre = head , *cur = head->mNext;

	ListNode *newnode = new ListNode(val);//新结点

	//找到新结点要插入的位置
	while(cur != NULL && cur->mData <= val)
	{
		pre = cur;
		cur = cur->mNext;
	}
	newnode->mNext = cur;
	pre->mNext = newnode;
	return head;
}

//合并两个有序链表
ListNode *merge(ListNode *head1 , ListNode *head2)
{
	if(head1 == NULL)
		return head2;
	if(head2 == NULL)
		return head1;
	ListNode dummyNode ;
	ListNode *p = &dummyNode;
	ListNode *p1 = head1 , *p2 = head2;
	while(p1 && p2)
	{
		if(p1->mData <= p2->mData)
		{
			p->mNext = p1;
			p1 = p1->mNext;
		}
		else
		{
			p->mNext = p2;
			p2 = p2->mNext;
		}
		p = p->mNext;
	}
	if(p1)
		p->mNext = p1;
	if(p2)
		p->mNext = p2;
	return dummyNode.mNext;
}
int main()
{
	srand((unsigned)time(NULL));
	vector<int> nums;
	for(int i = 0 ; i < NUM_SIZE ; i++)
	{
		int num = rand() % 100;	//这里取每个数字<100
		nums.push_back(num);
	}
	cout<<"排序前的数组为: ";
	for(int i = 0 ; i < NUM_SIZE ; i++)
		cout<<nums[i]<<" " ;
	cout<<endl;
	bucketSort(nums);
	cout<<"排序后的数组为: ";
	for(int i = 0 ; i < NUM_SIZE ; i++)
		cout<<nums[i]<<" ";
	cout<<endl;
	return 0;
}

程序执行结果:

[[email protected] cctmp]# ./a.out
排序前的数组为: 20 89 4 50 96 29 92 81 67 24 16 84 73 83 17 46 71 11 82 9
排序后的数组为: 4 9 11 16 17 20 24 29 46 50 67 71 73 81 82 83 84 89 92 96
[[email protected] cctmp]#

版权声明:转载请注明出处。

时间: 2024-10-20 07:16:44

三种线性排序算法(计数、基数、桶排序)的简单实现的相关文章

九大排序算法及其实现- 插入.冒泡.选择.归并.快速.堆排序.计数.基数.桶排序

  闲着的时候看到一篇“九大排序算法在总结”,瞬间觉得之前数据结构其实都有学过,但当初大多数都只是老师随口带过,并没有仔细研究一下.遂觉:这是欠下的账,现在该还了.   排序按照空间分类: In-place sort不占用额外内存或占用常数的内存 插入排序.选择排序.冒泡排序.堆排序.快速排序. Out-place sort:归并排序.计数排序.基数排序.桶排序. 或者按照稳定性分类: stable sort:插入排序.冒泡排序.归并排序.计数排序.基数排序.桶排序. unstable sort

排序算法四(桶排序)

一.桶排序算法的引入. 之前我们已经说过了计数排序的算法. 这个时候我们如果有这样的一个待排序数据序列: int x[14]={-10, 2, 3, 7, 20, 23, 25, 40, 41, 43,60, 80, 90, 100}; 我们如果按照计数排序的算法,那么待排序数据的范围是:-10 到 100 我们为了实现对于这个数据集的遍历,这个时候需要额外的开辟一个大小为(100- (-10)+ 1)的空间,这个时候的空间复杂度达远远超过我们的预计,也就是这个造成了空间的浪费. 所以我们可以说

常见排序算法导读(11)[桶排序]

上一节讲了基数排序(Radix Sort),这一节介绍桶排序(Bucket Sort or Bin Sort).和基数排序一样,桶排序也是一种分布式排序. 桶排序(Bucket Sort)的基本思想 将待排对象序列按照一定hash算法分发到N个桶中 对每一个桶的待排对象进行排序 从头到尾遍历N个桶,收集所有非空的桶里的有序对象(子序列)组成一个统一的有序对象序列 在每一个桶中,如果采用链式存储的话,1.和2.可以合并在一起操作,也就是在分发的过程中保证每一个桶的对象桶内有序. 例如: 设有5个桶

线性排序算法---- 计数排序, 基数排序, 桶排序

排序算法里,除了比较排序算法(堆排序,归并排序,快速排序),还有一类经典的排序算法-------线性时间排序算法.听名字就让人兴奋! 线性时间排序,顾名思义,算法复杂度为线性时间O(n) , 非常快,比快速排序还要快的存在,简直逆天.下面我们来仔细看看三种逆天的线性排序算法, 计数排序,基数排序和桶排序. 1计数排序  counting Sort 计数排序 假设 n个 输入元素中的每一个都是在 0 到 k 区间内的一个整数,当 k= O(N) 时,排序运行的时间为 O(N). 计数排序的基本思想

线性排序之基数排序,桶排序,计数排序

基数排序 计数排序 桶排序 基数排序,桶排序,计数排序是三种线性排序方法,突破了比较排序的O(nlogn)的限制.但是只适用于特定的情况. 基数排序 以下为维基百科的描述: 基数排序 : 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零.然后,从最低位开始,依次进行一次排序.这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列. 基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digit

排序算法(七)非比较排序:计数排序、基数排序、桶排序

前面讲的是比较排序算法,主要有冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等. 非比较排序算法:计数排序,基数排序,桶排序.在一定条件下,它们的时间复杂度可以达到O(n). 一,计数排序(Counting Sort) (1)算法简介 计数排序(Counting sort)是一种稳定的排序算法.计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数.然后根据数组C来将A中的元素排到正确的位置.它只能对整数进行排序. (2)算法描述和实现 得到待排序数的范围(在

计数排序、基数排序与桶排序

一.计数排序 稳定. 当输入的元素是n 个小区间(0到k)内整数时,它的运行时间是 O(n + k),空间复杂度是O(n). const int K = 100; //计数排序:假设输入数据都属于一个小区间内的整数,可用于解决如年龄排序类的问题 //Input:A[0, ..., n-1], 0 <= A[i] < K //Output:B[0, ..., n-1], sorting of A //Aux storage C[0, ..., K) void CountSort(int A[],

经典排序算法 - 计数排序Counting sort

经典排序算法 - 计数排序Counting sort 注意与基数排序区分,这是两个不同的排序 计数排序的过程类似小学选班干部的过程,如某某人10票,作者9票,那某某人是班长,作者是副班长 大体分两部分,第一部分是拉选票和投票,第二部分是根据你的票数入桶 看下具体的过程,一共需要三个数组,分别是待排数组,票箱数组,和桶数组 var unsorted = new int[] { 6, 2, 4, 1, 5, 9 };  //待排数组 var ballot = new int[unsorted.Len

排序算法总结----运算类排序

运算排序 第一:计数排序 1:原理 对于每个输入数,确定小于该数的个数.这样可以直接把数放在输出数组的位置. 2:性能 最差时间复杂度 最优时间复杂度 平均时间复杂度 最差空间复杂度 注:稳定算法 3:应用 适合0~100的范围的数,当然可以和基排序结合而扩展数的范围. 4:实现 void CountingSort(int *A, int *B, int array_size, int k) { int i, value, pos; int * C=new int[k+1]; for(i=0;

排序算法总结----比较类排序

概述:排序算法可分为比较性的排序,以及运算性的排序:这里详细介绍这些排序的原理,性能,实现,以及应用场合. 前面是维基百科的介绍,这里介绍几个比较典型的算法. 理论 计算复杂性理论 大O符号 全序关系 列表 稳定性 比较排序 自适应排序 排序网络 整数排序 交换排序 冒泡排序 鸡尾酒排序 奇偶排序 梳排序 侏儒排序 快速排序 臭皮匠排序 Bogo排序 选择排序 选择排序 堆排序 Smooth排序 笛卡尔树排序 锦标赛排序 循环排序 插入排序 插入排序 希尔排序 二叉查找树排序 图书馆排序 Pat