【基数排序】

基数排序(Radix Sorting)
  基数排序是和前面所述各类排序方法完全不相同的一种排序方法。前述各类排序方法都是建立在关键字比较的基础上,而基数排序不比较关键字的大小,它是根据关键字中各位的值,通过对待排序记录进行若干趟“分配”与“收集”来实现排序的,是一种借助于多关键字排序的思想对单关键字排序的方法。

一、多关键字的排序

  先看一个具体例子。已知扑克牌中52张牌的次序关系为

♣2<♣3<...<♣A<2<3<...<A<2<3<...<A<♠2<♠3<...<♠A

  每一张牌有两个“关键字”:花色(♣<<<)和面值(2<3<...<A),且“花色”的地位高于“面值”,在比较任意两种牌面的大小时,必须先比较“花色”,若“花色”相同,则再比较面值。

  由此,将扑克牌整理成如上所述次序关系时,有以下两种排序法:

1.最高位优先法:先按不同“花色”分成有次序的4堆,每一堆的牌均具有相同的“花色”,然后分别对每一堆按“面值”大小整理有序。

2.最低位优先法:这是一种“分配”与“收集”交替进行的方法。

1)先按不同“面值”分成13堆。

2)然后将这13堆牌自小至大叠在一起(“3”在“2”之上,“4”在“3”之上,...,最上面的4张“A”)。

3)然后将每堆按照面值的次序收集到一起,再重新对这些牌按不同“花色”分成4堆。

4)最后将这4堆牌按花色的次序再收集到一起(♣在最下面,♠在最上面)。

二、链式基数排序

[算法思想]

  基数排序的思想类似于上述“最低位优先法”的洗牌过程,是借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法。有的逻辑关键字可以看成由若干个关键字复合而成的。

  例如,若关键字是数值,且其值都在0<=K<=999范围内,则可把每一个十进制数字看成一个关键字,即可认为K由3个关键字(K0,K1,K2)组成,其中K0是百位数,K1是十位数,K2是个位数;

  又若关键字K是由5个字母组成的单词,则可看成是由5个关键字(K0,K1,K2,K3,K4)组成,其中K(j-1)是(自左至右的)第j个字母。由于如此分解而得的每个关键字Kj都在相同范围内(对数字,0<=Kj<=9,对字母‘A’<=Kj<=‘Z’),故可以按照“分配”和“收集”的方法进行排序。

  假设记录的逻辑关键字由d个“关键字”组成,每个关键字可能去rd个值。只要从最低数位关键字起,按关键字的不同值将序列中记录“分配”到rd个队列中后再“收集”之,如此重复d次完成排序。按照这种方法实现排序称之为基数排序,其中“基”指的是rd的取值范围,在上述两种关键字的情况下,rd分别为“10”和“26”。

  具体实现时,一般采用链式基数排序。

  先看一个具体的例子。首先以链表存储n个待排记录,并令表头指针指向第一个记录,如图(a)所示,然后通过以下三趟“分配”和“收集”操作来完成排序。

  在第一趟分配对最低数位关键字(个位数)进行,改变记录的指针值将链表中的记录分配到10个链队列中去,每个队列中的关键字的个数相等,如图(b)所示,其中f[i]和e[i]分别为第i队列的头指针和尾指针;第一趟收集是改变所有非空队列的队尾记录的指针域,令其指向下一个非空队列的队头记录,重新将10个队列中的记录链成一个链表,如图(c)所示;

  第二趟分配和第二趟收集是对十位数进行的,其过程和个位数相同。分配和收集结果分别如图(d)和图(e)所示。

第三趟分配和第三趟收集是对百位数进行的,过程同上,分配和收集结果分别如图(f)和图(g)所示。至此排序完毕



算法实现时采用静态链表,以便于更有效地存储和重排记录。相关数据类型的定义如下:

#define MAXNUM_KEY 8          /* 关键字项数的最大值 */
#define RADIX 10              /* 关键字基数,此时是十进制整数的基数 */
#define MAX_SPACE 10000
typedef struct
{
	KeysType keys[MAXNUM_KEY];    /* 关键字 */
	InfoType otheritems;          /* 其他数据项 */
	int next;
}SLCell;                      /* 静态链表的结点类型*/
typedef struct
{
	SLCell r[MAX_SPACE];      /* 静态链表的可利用空间,r[0]为头结点*/
	int keynum;               /* 记录的当前关键字个数 */    
	int recnum;               /* 静态链表的当前长度 */
}SLList;                      /* 静态链表类型 */ 
typedef int ArrType[RADIX]    /* 指针数组类型 */
[算法描述]

/* 分配 */
void Distribute(SLCell &r, int i, ArrType &f, ArrType &e)
{
	/* 静态链表L的r域中记录已按(keys[0],...,keys[i-1])有序 */
	/* 本算法按照第i个关键字建立RADIX个子表,使同一子表中记录的keys[i]相同 */
	/* f[0..RADIX-1]和e[0..RADIX-1]分别指向各子表中第一个和最后一个记录 */
	for(int j=0;j<RADIX;++j)          /* 各子表初始化为空表 */
		for(int p=r[0].next;p;p=r[p].next)
		{
			j=ord(r[p].keys[i]);      /* ord将记录中第i个关键字映射到[0..RADIX-1] */
			if(!f[j]) f[j]=p;
			else r[e[j]].next=p;
			e[j]=p;                   /* 将p所指的结点插入第j个子表中 */
		}
}
/* 收集 */
void Collect(SLCell &r,int i,ArrType f,ArrType e)
{
	/* 本算法按keys[i]自小至大地将f[0..RADIX-1]所指各子表依次链接成一个链表 */
	/* e[0..RADIX-1]为各子表的尾指针 */
	int j;
	for(j=0;!f[j];j=succ(j));      /* 找到第一个非空子表,succ为求后继函数 */
	r[0].next=f[j];t=e[j];             /* r[0].next指向一个非空子表中的第一个结点 */
	while(j<RADIX)
	{
		for(j=succ(j);j<RADIX-1&&!f[i];j=succ(j));
		if(f[j]) {r[t].next=f[j];t=e[j];}
	}
	r[t].next=0;
}

void RadixSort(SLList &L)
{
	/* L是采用静态链表表示的顺序表 */
	/* 对L做基数排序,使得L成为按关键字自小到大的有序静态链表,L.r[0]为头结点 */
	for(int i=0;i<L.recnum;++i) L.r[i].next=i+1;
	L.r[L.recnum].next=0;                 /* 将L改造为静态链表 */
	for(int i=0;i<L.keynum;++i)           /* 按最低位优先依次对各关键字进行分配和收集 */
	{
		Distribute(L.r,i,f,e);            /* 第i趟分配 */
		Collect(L.r,i,f,e);				  /* 第i趟收集 */
	}
	
}
[算法分析]

1.时间复杂度

     对于n个记录(加色每个记录含d个关键字,每个关键字的取值范围为rd个值)进行链式基数排序时,每一趟分配的时间复杂度为O(n),每一趟收集的时间复杂度为O(rd),整个排序需进行d趟分配和收集,所以时间复杂度为O(d(n+rd))。

2.空间复杂度

     所需辅助空间为2rd个队列指针,另外由于需用链表做存储结构,则相对于其他以顺序结构存储记录的排序方法而言,还增加了n个指针域的空间,所以空间复杂度为O(n+rd)。

[算法特点]

(1)是稳定排序。

(2)可用于链式结构,也可用于顺序结构。

(3)时间复杂度可以突破基于关键字比较一类方法的下界(nlog2n),达到O(n)。

(4)基数排序可使用条件有严格的要求:需要知道各级关键字的主次关系和各级关键字的取值范围。


附:虽然知道了基数排序的思想,但代码实现的过程不太了解。

这里给出一个写的比较好的基数排序总结


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值