基数排序是和前面所述各类排序方法完全不相同的一种排序方法。
从前面几篇文章的讨论可见,实现排序主要是通过关键字之间的比较和移动这两种操作。而实现基数排序不需要进行记录关键字间的比较。基数排序是一种借助多关键字排序的思想对单逻辑关键字排序的方法。
输入:链表的长度,链表中各个元素。
输出:排好序的链表中各个元素。
运行结果:
基数排序是借助分配和收集两种操作对单逻辑关键字进行排序的一种内部排序方法。
有的逻辑关键字可以看成由若干个关键字复合而成的。例如,若关键字是数值,且其值都在0<=K<=999范围内,则可把每一个十进制数字看成一个关键字,即可认为K由3个关键字组成(K0,K1,K2)组成,其中K0是百位数,K1是十位数,K2是个位数;由于由此分解而得的关键字Kj都在相同的范围内,只要从最低数位关键字起,按关键字的不同值将序列中记录分配到RADIX(关键字基数 此时是十进制整数的基数就是10,如果是Kj为字母的话为26)个队列中再收集之,如此重复d次,按这种方法实现排序称之为基数排序。
首先是辅助宏的定义:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define NULL 0
#define Radix 10 //关键字基数 此时是十进制整数的基数
#define KeySize 3 //关键字项数的最大值
typedef int Status;
typedef char *InfoType;
typedef int KeyType; //设关键字为整型
链表的存储结构定义:
typedef struct
{
KeyType key; //关键字
InfoType otherinfo;//其他数据项
}RedType;
//链表的存储结构定义
typedef struct LNode
{
RedType data;
struct LNode *next;
}LNode,*QueuePtr,*LinkList;
分配:按关键字的第j个分量进行分配 进入此过程时各箱子一定为空。
void Distribute(LinkList &L,LinkQueue B[],int j)
{
/*分配
按关键字的第j个分量进行分配 进入此过程时各
箱子一定为空*/
int i,k;
LNode *p;
while(L->next) //依次扫描每个记录 将其装箱
{
p=L->next;
L->next=p->next; //从链表中删取此结点
p->next=NULL; //结点别忘了与后面结点断开
k=p->data.key;
for(i=1;i<j;i++)
k/=10;
k=k%10; //取关键的第j位 从低位开始 数字k
EnQueue(B[k],p); //装箱
}
}
收集 :依次将各非空箱子中的记录收集起来,本过程结束时各箱子均为空.
void Collect(LinkList &L,LinkQueue B[])
{
/*收集
依次将各非空箱子中的记录收集起来,本过程
结束时各箱子均为空*/
int i,j=-1;
for(i=0;i<Radix;i++) //各箱子中记录数之和必为n
{
if(!QueueEmpty(B[i]))
{
//只要箱子不空,将上一个不空队列的尾指针连接到
//此队列的头指针
if(j!=-1)
B[j].rear->next=B[i].front->next;
else //第一个非空队列 直接与将链表头结点与队列头连接起来
L->next=B[i].front->next;
j=i; //保存上一个非空队列位置
}
}
for(i=0;i<Radix;i++) //将箱子清空
{
B[i].rear=B[i].front;
B[i].front->next=NULL;
}
}
基数排序:
void RadixSort(LinkList &L)
{
//基数排序
LinkQueue B[Radix];
int i;
for(i=0;i<Radix;i++)
InitQueue(B[i]);
for(i=1;i<=KeySize;i++)
{
Distribute(L,B,i); //第i位
Collect(L,B);
}
}
算法分析:
那么为啥用链式存储呢?因为顺序存储虽然简单,然是它的空间复杂度为O(radix + n),比链式存储高。而时间复杂度是相等的。
时间复杂度:O(d(n+radix)) 其中每一趟分配的时间复杂度为O(n),每一趟收集的时间复杂度为O(radix),整个排序需要d趟分配和收集。
空间复杂度:O(radix) 相比于顺序表存储,增加了n个指针域的空间。