链式基数排序(第十章 P286 算法10.15,10.16,10.17)

链式基数排序

 

概述:

 

基数排序(radix sort),属于“分配式排序”(distribution sort)。

基数排序也叫做多关键字排序,基数排序是一种借助“多关键字排序”的思想来实现“单关键字排序”的内部排序算法。

多关键字排序的方法:

n 个记录的序列 {R1, R2, …, Rn} 对关键字 (Ki0, Ki1, …, Ki(d-1) ) 有序是指:对于序列中任意两个记录 Ra 和 Rb  (1≤a < b≤n) 都满足下列(词典)有序关系:(Ka0, Ka1, …, Ka(d-1) ) <  (Kb0, Kb1, …, Kb(d-1) ) ,其中:K0  被称为最主位关键字, K(d-1)  被称为最次位关键字。多关键字排序按照从最主位关键字到最次位关键字或从最次位关键字到最主位关键字的顺序逐次排序,分两种方法:

最高位优先法(Most Significant Digit first),简称 MSD 法:先按 k0 排序分组,同一组中记录,关键字 k0 相等,再对各组按 k1 排序分成子组,之后,对后面的关键字继续这样的排序分组,直到按最次位关键字  kd  对各子组排序后,再将各组连接起来,便得到一个有序序列。

最低位优先法(Least Significant Digit first),简称 LSD 法:先从 k(d-1) 开始排序,再对 k(d-2) 进行排序,依次重复,直到对 k0 排序后便得到一个有序序列。

注意一点:LSD的基数排序适用于位数少的数列,如果位数多的话,使用MSD的效率会比较好。

 

 

排序过程:

注:这里使用LSD法进行排序

例:对于关键字序列 { 278, 109, 063, 930, 589, 184, 505, 269, 008,083 } 进行基数排序 。

可以将每个关键字 K 看成由三个单关键字组成,即 K= k1k2k3, 每个关键字的取值范围为 0≤ki≤9,所以每个关键字可取值的数目为 10。通常将关键字取值的数目称为基数,用 r 表示,在本例中 r =10。

对于关键字序列(AB, BD, ED)可以将每个关键字看成是由二个单字母关键字组成的复合关键字,并且每个关键字的取值范围为 “A~Z”,所以关键字的基数 r = 26。

对于由 d 位关键字组成的复合关键字,需要经过d 趟的“分配”与“收集”。 因此,若 d 值较大,基数排序的时间效率就会随之降低。

 

在计算机上实现基数排序时,为减少所需辅助存储空间,应采用链表存储 n 个待排记录,即链式基数排序。因为每个桶内的元素个数是未知的,所以需要借助链表结构来实施分配时向桶内仍记录的过程。

在每一趟分配进行时,改变记录的指针值,将链表中的记录分配到 10 个链队列中去,其中 f[i] 和 e[i] 分别为第 i 个队列的头指针和尾指针。具体作法为:

1、以静态链表存储待排记录,并令表头指针指向第一个记录; 

2、“分配” 时,按当前“关键字位”所取值,将记录分配到不同的 “链队列” 中,每个队列中记录的 “关键字位” 相同;

3、“收集”时,按当前关键字位取值从小到大将各队列首尾相链成一个链表; 

4、对每个关键字位均重复 2 和 3 两步。 

 

  • 初始转态

  • 首先按照各个数据的个位数字分配到0-9的10个区间内,第一趟分配之后结果如下

  • 分配结束后。接下来将所有空间中的数据按照序号由小到大依次重新收集起来,得到如下仍然无序的数据序列:

  • 这次按照十位数字进行分配,步骤同上,第二趟分配之后结果如下

  • 分配结束后。接下来将所有空间中的数据按照序号由小到大依次再次收集起来,得到如下仍然无序的数据序列

  • 这次按照百位数字进行分配,步骤同上,第三趟分配之后结果如下

 

  • 分配结束后。接下来将所有空间中的数据按照序号由小到大依次再次收集起来,得到有序序列。

 

 

算法性能:

借助桶编号(键)经过多次分配和采集,最终得到一个有序序列,在这个算法排序过程中,没有经过任何记录的比较,因此基数排序是很独特的排序算法。

时间复杂度:

待排序列为n个记录,d个关键码,关键码的取值范围为 r,其中,一趟分配时间复杂度为 O(n),一趟收集时间复杂度为O(r),共进行 d 趟分配和收集,所以链式基数排序的时间复杂度为 O( d·(n+r) ) 。

注意:这不是说这个时间复杂度一定优于O(n·log(n)),因为 d 的大小一般会受到 n 的影响。 

空间复杂度:

(2r 个队列指针 + n 个指针域空间),因为一个桶本质是一个链式队列,一共 r 个桶,每个队列有队头和队尾两个指针,就是2r 个队列指针。又原来的待排序列是一个单链表,那么自然需要 n 个next指针控件。

 

 

 

代码:

 

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */

typedef int InfoType; /* 定义其它数据项的类型 */
typedef int KeyType; /* 定义RedType类型的关键字为整型 */
typedef struct
{
	KeyType key; /* 关键字项 */
	InfoType otherinfo; /* 其它数据项 */
}RedType; /* 记录类型(同c10-1.h) */
typedef char KeysType; /* 定义关键字类型为字符型 */



/* ---------------------------    基数排序的数据类型     ------------------------------*/


#define MAX_NUM_OF_KEY 8 /* 关键字项数的最大值 */
#define RADIX 10 /* 关键字基数,此时是十进制整数的基数 */
#define MAX_SPACE 1000
typedef struct
{
	KeysType keys[MAX_NUM_OF_KEY]; /* 关键字 */
	InfoType otheritems; /* 其它数据项 */
	int next;
}SLCell; /* 静态链表的结点类型 */

typedef struct
{
	SLCell r[MAX_SPACE]; /* 静态链表的可利用空间,r[0]为头结点 */
	int keynum; /* 记录的当前关键字个数 */
	int recnum; /*  静态链表的当前长度 */
}SLList; /* 静态链表类型 */

typedef int ArrType[RADIX]; /* 指针数组类型 */


/* ------------------------------------------------------------------------------------------*/



void InitList(SLList *L, RedType D[], int n)
{ /* 初始化静态链表L(把数组D中的数据存于L中) */
	char c[MAX_NUM_OF_KEY], c1[MAX_NUM_OF_KEY];
	int i, j, max = D[0].key; /* max为关键字的最大值 */
	for (i = 1; i < n; i++)
		if (max < D[i].key)
			max = D[i].key;
	(*L).keynum = (int)(ceil(log10(max)));
	(*L).recnum = n;
	for (i = 1; i <= n; i++)
	{
		(*L).r[i].otheritems = D[i - 1].otherinfo;
		itoa(D[i - 1].key, c, 10); /* 将10进制整型转化为字符型,存入c */
		for (j = strlen(c); j < (*L).keynum; j++) /* 若c的长度<max的位数,在c前补'0' */
		{
			strcpy(c1, "0");
			strcat(c1, c);
			strcpy(c, c1);
		}
		for (j = 0; j < (*L).keynum; j++)
			(*L).r[i].keys[j] = c[(*L).keynum - 1 - j];
	}
}

int ord(char c)
{ /* 返回k的映射(个位整数) */
	return c - '0';
}

void Distribute(SLCell r[], int i, ArrType f, ArrType e) /* 算法10.15 */
{ /* 静态键表L的r域中记录已按(keys[0],...,keys[i-1])有序。本算法按 */
  /* 第i个关键字keys[i]建立RADIX个子表,使同一子表中记录的keys[i]相同。 */
  /* f[0..RADIX-1]和e[0..RADIX-1]分别指向各子表中第一个和最后一个记录 */
	int j, p;
	for (j = 0; j < RADIX; ++j)
		f[j] = 0; /* 各子表初始化为空表 */
	for (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个子表中 */
	}
}

int succ(int i)
{ /* 求后继函数 */
	return ++i;
}

void Collect(SLCell r[], ArrType f, ArrType e)
{ /* 本算法按keys[i]自小至大地将f[0..RADIX-1]所指各子表依次链接成 */
  /* 一个链表,e[0..RADIX-1]为各子表的尾指针。算法10.16 */
	int j, t;
	for (j = 0; !f[j]; j = succ(j)); /* 找第一个非空子表,succ为求后继函数 */
	r[0].next = f[j];
	t = e[j]; /* r[0].next指向第一个非空子表中第一个结点 */
	while (j < RADIX - 1)
	{
		for (j = succ(j); j < RADIX - 1 && !f[j]; j = succ(j)); /* 找下一个非空子表 */
		if (f[j])
		{ /* 链接两个非空子表 */
			r[t].next = f[j];
			t = e[j];
		}
	}
	r[t].next = 0; /* t指向最后一个非空子表中的最后一个结点 */
}

void printl(SLList L)
{ /* 按链表输出静态链表 */
	int i = L.r[0].next, j;
	while (i)
	{
		for (j = L.keynum - 1; j >= 0; j--)
			printf("%c", L.r[i].keys[j]);
		printf(" ");
		i = L.r[i].next;
	}
}

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

void print(SLList L)
{ /* 按数组序号输出静态链表 */
	int i, j;
	printf("keynum=%d recnum=%d\n", L.keynum, L.recnum);
	for (i = 1; i <= L.recnum; i++)
	{
		printf("keys=");
		for (j = L.keynum - 1; j >= 0; j--)
			printf("%c", L.r[i].keys[j]);
		printf(" otheritems=%d next=%d\n", L.r[i].otheritems, L.r[i].next);
	}
}

void Sort(SLList L, int adr[]) /* 改此句(类型) */
{ /* 求得adr[1..L.length],adr[i]为静态链表L的第i个最小记录的序号 */
	int i = 1, p = L.r[0].next;
	while (p)
	{
		adr[i++] = p;
		p = L.r[p].next;
	}
}

void Rearrange(SLList *L, int adr[]) /* 改此句(类型) */
{ /* adr给出静态链表L的有序次序,即L.r[adr[i]]是第i小的记录。 */
  /* 本算法按adr重排L.r,使其有序。算法10.18(L的类型有变) */
	int i, j, k;
	for (i = 1; i < (*L).recnum; ++i) /* 改此句(类型) */
		if (adr[i] != i)
		{
			j = i;
			(*L).r[0] = (*L).r[i]; /* 暂存记录(*L).r[i] */
			while (adr[j] != i)
			{ /* 调整(*L).r[adr[j]]的记录到位直到adr[j]=i为止 */
				k = adr[j];
				(*L).r[j] = (*L).r[k];
				adr[j] = j;
				j = k; /* 记录按序到位 */
			}
			(*L).r[j] = (*L).r[0];
			adr[j] = j;
		}
}

#define N 10
void main()
{
	RedType d[N] = { {278,1},{109,2},{63,3},{930,4},{589,5},{184,6},{505,7},{269,8},{8,9},{83,10} };
	SLList l;
	int *adr;
	InitList(&l, d, N);
	printf("排序前(next域还没赋值):\n");
	print(l);
	RadixSort(&l);
	printf("排序后(静态链表):\n");
	print(l);
	adr = (int*)malloc((l.recnum) * sizeof(int));
	Sort(l, adr);
	Rearrange(&l, adr);
	printf("排序后(重排记录):\n");
	print(l);
}

运行结果:

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值