基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。实现的过程不需要之前的所有排序所需要的记录关键字比较,移动等操作。
多关键字排序:
多关键字排序通常有两种方法:
1、MSD(Most Significant Digit)法,最高为优先法
2、LSD(Least Significant Digit)法,最低位优先法
过程借助分配,收集两种操作。
数组基数排序:
过程演示:
第一步
以LSD为例,假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0:
1: 81
2 :22
3 :73 93 43
4 :14
5 :55 65
6:
7:
8 :28
9 :39
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0:
1 :14
2 :22 28
3 :39
4 :43
5 :55
6 :65
7 :73
8 :81
9 :93
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。
链式基数排序
过程演示:
算法代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_OF_KEY 8 //关键字项数的最大值
#define RADIX 10 //关键字基数,此时是十进制整数的基数
#define MAX_SPACE 10000
typedef int KeyType; //定义关键字类型为int
typedef char InfoType; //定义其他信息类型为char
typedef struct{
KeyType 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]; //指针数组类型
//分配函数
//此函数按第i个关键字keys[i]建立RADIX个子表,使同一子表中记录的keys[i]相同
//静态链表L的r域中记录已按(keys[0],...,keys[i-1])有序
void Distribute(SLCell *r, int i, ArrType f,ArrType e){
//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){//遍历静态链表,通过修改指针将一个静态链表变成RADIX个子表(允许有空)
j = ord(r[p].keys[i]);//ord将记录中第i个关键字映射到[0..RADIX-1],j代表第j个子表
if (!f[j]){//f[j]为空,f[i]和p指向一致(指向此子表的第一个记录)
f[j] = p;
}else{//f[j]不为空(即子表已经有记录),则往后插(通过e[j]获取要插入位置的前一个位置,即插入前子表的最后位置)
r[e[j]].next = p;
}
e[j] = p;//更改子表表尾指针
}
}
//收集函数
//本函数按keys[i]自小到大地将f[0...RADIX-1]所指各子表依次链接成一个链表
//e[0...RADIX-1]为各子表的尾指针
void Collect(SLCell *r, int i, ArrType f, ArrType e){
int j,t;
for (j = 0; !f[j]; j=succ(j));//找第一个非空子表,succ为求后继函数。第一个非空子表的第一个结点的下标为j
r[0].next = f[j];//r[0].next指向第一个非空子表中的第一个结点
t = e[j];//t为上一个非空子表的尾指针
while (j < RADIX){
for (j = succ(j); (j < RADIX - 1) && !f[j]; j = succ(j));//找到下一个非空子表
if (f[j]){ //链接两个非空子表
r[t].next = f[j]; //上一个非空子表的尾指针连接到本非空子表的表头
t = e[j]; //更新t
}
}
r[t].next = 0; //t指向最后一个非空子表中的最后一个结点
}
//此函数对L作基数排序,使得L成为按关键字自小到大的有序静态链表,L.r[0]为头结点
//L是采用静态链表表示的顺序表
void RadixSort(SLList *L){
int i;
//初始化静态链表
for ( i = 0; i < L->recnum; i++){
L->r[i].next = i + 1;
}
L->r[L->recnum].next = 0;
//按LSD依次对个关键字进行分配和收集
for ( i = 0; i <L->keynum; i++){
Distribute(L->r, i, f, e);//第i趟分配
Collect(L->r, i, f, e); //第i趟收集
}
}
评价:
基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。