排序算法8——基数排序

【基数排序】
基数排序的算法思想:基数排序不同于前面的各种排序算法,前面的排序算法都是基于元素之间的比较好实现的,而基数排序则是利用分类进行排序的方法。

【算法思想】

基数排序是一种多关键字排序算法。基数排序通过对所有元素根据关键字进行分类,然后按照关键字的顺序将这些元素收集起来,通过这样的方法完成对元素序列的排序。因此基数排序算法分成两个分部:分配和收集。

具体算法描述:假设第i个元素a_i的关键字为key_i,key_i是由d位个十进制组成,即key_i=ki^d ki^d-1 ... ki^1,其中ki^1为最低位,ki^d为最高位。关键字的每一位数字都可以作为一个子关键字。首先将每一个元素依次按照每个关键字进行排序并收集,直到按照所有的关键字都排序、收集完毕,这样就完成了排序过程。

【示例】
例如,一组元素序列为325,138,29,214,927,631,732,205。这组元素的位数最多是3,在排序之前首先将所有元素都转换为3位数字组成的数,不够3位的在前面添加0,即325,138,029,214,927,631,732,205。对这组元素进行基数排序需要进行3趟分配和收集。其次需要对该元素序列的关键字的最低位即个位上的数字进行分配和收集,再对十位数字进行分配和收集,最后是对最高位的数字进行分配和收集。一般情况下,采用链表实现基数排序。

对最低位进行分配和收集的过程如下图所示。


其中,数组f[i]保存第i个链表的头指针,数组r[i]保存第i个链表的尾指针。

对十位数字的分配和收集的过程如下图所示。


对百位数字的分配和收集的过程如下图所示。


由上很容易看出,经过第1趟排序即对个位数字作为关键字进行分配后,关键字被分为10类,个位数组相同的数字被划分为一类,然后对分配后的元素进行收集之后,得到以个位数字非递减排列的元素。同理,经过第2趟分配和收集后,得到以十位数字非递减排列的元素序列。经过第3趟分配和收集后,得到最终排序结果。

code:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#define MaxSize 200	/*待排序元素的最大个数*/
#define N 8         	/*待排序元素的实际个数*/
#define MaxNumKey 6	/*关键字项数的最大值*/
#define Radix 10		/*关键字基数,10表示十进制数字可以分为十组*/
/*静态链表的结点,存放待排序元素*/
typedef struct
{
	int key[MaxNumKey]; 	/*关键字*/
	int next;
}SListCell;
/*静态链表,存放元素序列*/
typedef struct
{
	SListCell data[MaxSize];		/*存储元素,data[0]为头结点*/
	int keynum;				/*每个元素的当前关键字个数*/
	int length;					/*静态链表的当前长度*/
}SList;
typedef int addr[Radix]; /*指针数组类型,用来指向每个链表的第1个结点和最后一个结点*/
void DispList(SList L); 			/*输出链表中的元素*/
void DispStaticList(SList L); 		/*以静态链表的形式输出元素*/
void InitList(SList *L, int d[], int n);
int trans(char c); 				/*将字符转换为数字*/
void Distribute(SListCell data[], int i, addr f, addr r); 	/*分配*/
void Collect(SListCell data[], addr f, addr r); 		/*收集*/
void RadixSort(SList *L); 					/*基数排序*/
void Distribute(SListCell data[], int i, addr f, addr r)
/*为data数组中的第i个关键字key[i]建立Radix个子表,使同一子表中元素的key[i]相同*/
/*f[0..Radix-1]和r[0..Radix-1]分别指向各个子表中第一个和最后一个元素*/
{
	int j, p;
	for (j = 0; j<Radix; j++)			/*初始化各个子表*/
		f[j] = 0;
	for (p = data[0].next; p; p = data[p].next)
	{
		j = trans(data[p].key[i]);		/*将关键字转换为数字*/
		if (!f[j])				/*f[j]是空表,则f[j]指示第一个元素*/
			f[j] = p;
		else
			data[r[j]].next = p;
		r[j] = p;		/*将p所指的结点插入第j个子表中*/
	}
}
void Collect(SListCell data[], addr f, addr r)
/*收集,按key[i]将f[0..Radix-1]所指各子表依次链接成一个静态链表*/
{
	int j, t;
	for (j = 0; !f[j]; j++);	/*找第一个非空子表,succ为求后继函数*/
	data[0].next = f[j];
	t = r[j];			/*r[0].next指向第一个非空子表中第一个结点*/
	while (j<Radix - 1)
	{
		for (j = j + 1; j<Radix - 1 && !f[j]; j++);		/*找下一个非空子表*/
		if (f[j])		/*将非空链表连接在一起*/
		{
			data[t].next = f[j];
			t = r[j];
		}
	}
	data[t].next = 0;		/*t指向最后一个非空子表中的最后一个结点*/
}
void RadixSort(SList *L)
/*基数排序,使得L成为按关键字非递减的静态链表,L.r[0]为头结点*/
{
	int i;
	addr f, r;
	for (i = 0; i<(*L).keynum; i++)	/*由低位到高位依次对各关键字进行分配
									和收集*/
	{
		Distribute((*L).data, i, f, r);		/*第i趟分配*/
		Collect((*L).data, f, r);			/*第i趟收集*/
		printf("第%d趟收集后:", i + 1);
		DispStaticList(*L);
	}
}
void InitList(SList *L, int a[], int n)
/*初始化静态链表L*/
{
	char ch[MaxNumKey], ch2[MaxNumKey];
	int i, j, max = a[0];
	for (i = 1; i<n; i++)			/*将最大的元素存入max*/
		if (max<a[i])
			max = a[i];
	(*L).keynum = (int)(log10(max)) + 1;	/*求子关键字的个数*/
	(*L).length = n;					/*待排序个数*/
	for (i = 1; i <= n; i++)
	{
		itoa(a[i - 1], ch, 10);	/*将整型转化为字符,并存入ch*/
		for (j = strlen(ch); j<(*L).keynum; j++)/*若ch的长度<max的位数,则在ch前补'0'*/
		{
			strcpy(ch2, "0");
			strcat(ch2, ch);
			strcpy(ch, ch2);
		}
		for (j = 0; j<(*L).keynum; j++)	/*将每个元素的各位数存入key,作为关键字*/
			(*L).data[i].key[j] = ch[(*L).keynum - 1 - j];
	}
	for (i = 0; i<(*L).length; ++i)			/*初始化静态链表*/
		(*L).data[i].next = i + 1;
	(*L).data[(*L).length].next = 0;
}
void main()
{
	int d[N] = { 325,138,29,214,927,631,732,205 };
	SList L;
	InitList(&L, d, N);
	printf("待排序元素个数是%d个,关键字个数为%d个\n", L.length,
		L.keynum);
	printf("排序前的元素序列:");
	DispStaticList(L);
	printf("排序前的元素的存放情况:\n");
	DispList(L);
	RadixSort(&L);
	printf("排序后元素的存放情况:\n");
	DispList(L);
	getchar();
}
void DispList(SList L)
/*按数组序号形式输出静态链表*/
{
	int i, j;
	printf("序号 关键字 地址\n");
	for (i = 1; i <= L.length; i++)
	{
		printf("%2d    ", i);
		for (j = L.keynum - 1; j >= 0; j--)
			printf("%c", L.data[i].key[j]);
		printf("    %d\n", L.data[i].next);
	}
}
void DispStaticList(SList L)
/*按链表形式输出静态链表*/
{
	int i = L.data[0].next, j;
	while (i)
	{
		for (j = L.keynum - 1; j >= 0; j--)
			printf("%c", L.data[i].key[j]);
		printf(" ");
		i = L.data[i].next;
	}
	printf("\n");
}
int trans(char c)
/*将字符c转化为对应的整数*/
{
	return c - '0';
}

结果:

【主要用途】

基数排序算法实现复杂,它是一种多关键字排序算法,属于分类排序。因为基数排序算法不需要过多比较,所以在数据较多的情况下,采用基数排序算法的效率要优于前面谈到的排序算法。

【稳定性与复杂度】

基数排序是一种稳定的排序算法。
基数排序算法的时间复杂度为O(d(n+r)),其中,n表示待排序的元素个数,d表示关键字个数,r表示基数。其中一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(r)。

基数排序需要2*r个指向链式队列的辅助空间。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值