基数排序

桶排序

在介绍基数排序之前,我们先看看桶排序的原理。

假设我们有N个学生,他们的成绩是0到100之间的整数(于是有M = 101个不同的成绩值)。如何在线性时间内将学生按成绩排序?
在这里插入图片描述
在这个例子中我们看到数据的特殊性,学生可以有很多,但是种类就只有101种,我们建立101个桶,每个桶一个成绩值,桶中的存放的是链表,相同成绩的学生在对应的桶中构成链表,扫描每个学生的成绩,依次将其插入到对应的桶中。

每个桶被初始化为一个空链表的头指针,也就有了101个空链表的头指针,初始时有101个空链表,也就有101个桶。现在假设扫描到一个同学的成绩为88,找到88对应的桶,将该元素插如到对应的表头里。

输出时,按照桶的位置,如果桶不为空,将桶中的元素依次输出,再输出下一个桶的元素。
伪码描述:

void Bucket_Sort(ElementType A[], int N)
{
	count[]初始化;
	while (读入1个学生成绩grade)
		将该生插入count[grade]链表;
	for (i = 0; i<M; i++) {
		if (count[i])
			输出整个count[i]链表;
	}
}

时间复杂度:O(N+M)
每个元素扫描一遍,每个桶的中链表的头节点扫描一遍。

当M<<N时,可以看成是一种线性的算法。

基数排序

再看一个例子,假设我们有N = 10 个整数,每个整数的值在0到999之间(于是有 M = 1000 个不同的值)。还有可能在线性时间内排序吗?
输入序列: 64, 8, 216, 512, 27, 729, 0, 1, 343, 125

这里的基数指的是进制,2进制基数等于2,8进制基数等于8,现在是10进制的数,基数等于10。

用“次位优先”(Least Significant Digit)
我们采取的策略是次位优先,比如512,个位2为最次的位置,百位5为主位,先从个位开始排序,再对十位,百位排序。

基数是10,我们就建立10个桶,先按照个位对所有元素排序,再个位排序的基础上按照十位对所有元素排序,最后在前面排序的基础上按照百位排序。
在这里插入图片描述
设元素个数为N,整数进制为B,LSD的趟数为P,则最坏时间复杂度是:T=O(P(N+B))
一共有P趟,在每一每一趟趟中要对B个桶,及所有元素扫描一遍,所有每一趟访问元素个数为N+B。

次位优先完整c++代码

//基数排序 - 次位优先
#include<iostream>
using namespace std;
typedef int ElementType;

#define MaxDigit 4	//假设元素最多有MaxDigit个关键字
#define Radix 10	//基数全是同样的Radix

//桶元素结点
typedef struct Node *PtrToNode;
struct Node {
	int key;
	PtrToNode next;
};

//桶头结点
typedef struct HeadNode {
	PtrToNode head, tail;
}Bucket[Radix];
//typedef struct HeadNode Bucket[Radix];

int GetDigit(int X, int D)//获得当前元素的当前位数字
{//默认次位D=1, 主位D<=MaxDigit
	int d, i;
	for (i = 1; i <= D; i++)
	{
		d = X % Radix;
		X /= Radix;
	}
	return d;
}

//基数排序 - 次位优先
void LSDRadixSort(ElementType A[], int N)
{
	int D, Di, i;
	Bucket B;
	PtrToNode tmp, p, List = NULL;

	for (i = 0; i<Radix; i++) //初始化每个桶为空链表
		B[i].head = B[i].tail = NULL;
	for (i = 0; i<N; i++)	//将原始序列逆序存入初始链表List
	{
		tmp = (PtrToNode)malloc(sizeof(struct Node));
		tmp->key = A[i];
		tmp->next = List;
		List = tmp;
	}
	//下面开始排序
	for (D = 1; D <= MaxDigit; D++)//对数据的每一位循环处理
	{
		//下面是分配的过程
		p = List;
		while (p)
		{
			Di = GetDigit(p->key, D); //获得当前元素的当前位数字
			
			tmp = p; p = p->next; 	//从List中摘除
			
			tmp->next = NULL;
			if (B[Di].head == NULL)
				B[Di].head = B[Di].tail = tmp;
			else			//插入B[Di]号桶尾
			{
				B[Di].tail->next = tmp;
				B[Di].tail = tmp;
			}
		}
		//下面是收集的过程
		List = NULL;
		//将每个桶的元素顺序收集入List,
		//因为每次都将桶的元素插入List表头,所以从关键字大的开始
		for (Di = Radix - 1; Di >= 0; Di--)
		{
			if (B[Di].head)//如果桶不为空,整桶插入List表头
			{
				B[Di].tail->next = List;
				List = B[Di].head;
				B[Di].head = B[Di].tail = NULL; //清空桶
			}
		}
	}
	//将List倒入A[]并释放空间,这时的List是已经排好序的
	for (i = 0; i<N; i++)
	{
		tmp = List;
		List = List->next;
		A[i] = tmp->key;
		free(tmp);
	}
}

int main()
{
	int a[] = { 524, 346, 91, 258, 439, 613, 257, 10 };
	int len = sizeof(a) / sizeof(a[0]);
	LSDRadixSort(a, len);
	for (int i = 0; i < len; i++)
		cout << a[i] << " ";
	return 0;
}

主位优先完整c++代码

//基数排序 - 主位优先
#include<iostream>
using namespace std;
typedef int ElementType;

#define MaxDigit 4	//假设元素最多有MaxDigit个关键字
#define Radix 10	//假设基数全是同样的Radix

typedef struct Node *PtrToNode;//桶元素结点
struct Node
{
	int key;
	PtrToNode next;
};

typedef struct HeadNode {	//桶头结点
	PtrToNode head, tail;
}Bucket[Radix];

int GetDigit(int X, int D)	//默认次位D=1, 主位D<=MaxDigit
{
	int d, i;
	for (i = 1; i <= D; i++)
	{
		d = X % Radix;
		X /= Radix;
	}
	return d;
}

//核心递归函数: 对A[L]...A[R]的第D位数进行排序
void MSD(ElementType A[], int L, int R, int D)
{
	int Di, i, j;
	Bucket B;
	PtrToNode tmp, p, List = NULL;
	if (D == 0) return; 		//递归终止条件

	for (i = 0; i<Radix; i++) 	//初始化每个桶为空链表
		B[i].head = B[i].tail = NULL;
	for (i = L; i <= R; i++) 	//将原始序列逆序存入初始链表List
	{
		tmp = (PtrToNode)malloc(sizeof(struct Node));
		tmp->key = A[i];
		tmp->next = List;
		List = tmp;
	}
	//下面是分配的过程
	p = List;
	while (p)
	{
		Di = GetDigit(p->key, D); //获得当前元素的当前位数字
		
		tmp = p; p = p->next;	//从List中摘除
		//插入B[Di]号桶
		if (B[Di].head == NULL)
			B[Di].tail = tmp;
		tmp->next = B[Di].head;
		B[Di].head = tmp;
	}
	//下面是收集的过程
	i = j = L; //i, j记录当前要处理的A[]的左右端下标
	for (Di = 0; Di<Radix; Di++)	//对于每个桶
	{
		if (B[Di].head) 	//将非空的桶整桶倒入A[], 递归排序
		{
			p = B[Di].head;
			while (p)
			{
				tmp = p;
				p = p->next;
				A[j++] = tmp->key;
				free(tmp);
			}
			
			MSD(A, i, j - 1, D - 1);//递归对该桶数据排序, 位数减1,j指向桶最后一个元素的下一个
			i = j; //为下一个桶对应的A[]左端
		}
	}
}

void MSDRadixSort(ElementType A[], int N)//统一接口
{ 
	MSD(A, 0, N - 1, MaxDigit);
}

int main()
{
	int a[] = { 524, 346, 91, 258, 439, 613, 257, 10 };
	int len = sizeof(a) / sizeof(a[0]);
	MSDRadixSort(a, len);
	for (int i = 0; i < len; i++)
		cout << a[i] << " ";
	return 0;
}

多关键字的排序

基数排序可以用来处理多关键字的排序。下面来看个例子:
一副扑克牌是按2种关键字排序的,花色和面值。
花色为主关键字,面值为次关键字。
在这里插入图片描述
在排序的时候,次关键字对应低位,主关键字对应高位。

除了次位优先排序,还有主位优先排序。
用“主位优先” ( Most Significant Digit ) 排序:为花色建4个桶,在每个桶内调用排序算法分别排序,最后合并结果。
在这里插入图片描述
这种方法还需要在每个桶里面用排序算法。

下面来看用“次位优先” ( Least Significant Digit ) 排序:为面值建13个桶,将元素扫描一遍插入相应的桶中,再为花色建4个桶,从面值桶的第一个开始扫面,依次将每种花色放入对应的桶中,最后合并就是排序结果。
在这里插入图片描述

主位优先(MSD) 和次位优先(LSD)各有各的适用场合,不一定谁比谁好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值