基数排序
一、算法描述:
对于排序问题,大部分的方法是基于比较大小然后交换位置。基数排序就不是基于比较的。它将整形10进制按每位拆分,然后从低位到高位依次比较各个位。主要分为两个过程:
(1)分配,先从个位开始,根据位值(0-9)分别放到0~9号桶中(比如53,个位为3,则放入3号桶中)
(2)收集,再将放置在0~9号桶中的数据按顺序放到数组中
重复(1)(2)过程,从个位到最高位(比如32位无符号整形最大数4294967296,最高位10位)。最后一次收集之后,序列有序。
二、分析:
看完算法描述,可能还有些疑惑。这样怎么就能排好呢?
依然拿整数举例,整数的比较是先看位数,位数多的值较大。相同位置上,数字大的较大。可以说,整数比较是高位优先的。这里基数排序需要低位优先,现将按低位,再按高位。细心的人,也许发现了。在第一次收集之后,看最低位,是有序的。在第二次收集之后,倒数第二位是有序的,而且在倒数第二位相同的情况下,它的下一位的数字也是有序的。这样依次类推,最终整个序列中,最高位有序,相同位置相同数字时,下一位有序。这样整个就是按照整数大小比较的规则形成有序序列了。
这样看来,
时间复杂度 O(d*(n + r))
d :数字的位数
n :元素个数
r :数字的取值范围,如十进制数字,r= 10,十六进制的话,r= 16
空间复杂度
O(n + r)
这里需要桶的个数r,以及每个元素的指针n或者元素本身n
从这里的分析也可以看出,基数排序是有一些限制的。首先,需要知道待排序列中数字位数的最大值。其次,需要知道每一位的数字的取值范围。只有满足这两个条件才可以进行基数排序。
排扑克牌时,可以使用。花色权重 > 数字,而且相同花色,数字越大,牌越大。
三、代码:
给出两种数据结构的算法,第一种是最一般的使用链表作为桶,需要头指针和尾指针,每个元素都加入到链表中,再从链表里返回到序列中。
typedef struct JPoint
{
int value;
JPoint *pr;
} JPoint;
typedef struct JBarrel
{
///基数排序中,桶用链表队列实现
JPoint *head;
JPoint *rear;
} JBarrel;
void Initial_int(int *a,int n,int range)
{
int i;
srand( (unsigned)time( NULL ) );
for(i=0; i<n; i++)
a[i]=rand()%range+1;
}
void JishuSort_allocate(int *a,int n,JBarrel *B,int t)
{
int ten=1,i,temp;
for(i=0; i<t; i++)
ten*=10;
for(i=0; i<n; i++)
{
temp=(a[i]/ten)%10;
JPoint *px=new JPoint;
px->value=a[i];
px->pr=NULL;
if(B[temp].head)
{
B[temp].rear->pr=px;
B[temp].rear=px;
}
else
{
B[temp].head=B[temp].rear=px;
}
}
}
void JishuSort_collect(int *a,int n,JBarrel *B,int nB)
{
int i,j;
for(i=0,j=0;i<nB&&j<n;i++)
{
if(!B[i].head) continue; ///B[i]为空队列
if(B[i].head == B[i].rear) ///队列只有一个元素
{
a[j++]=B[i].head->value;
delete B[i].head;
}
else
{
JPoint *p=B[i].head;
while(p)
{
a[j++]=p->value;
JPoint *tem=p;
p=p->pr;
delete tem;
}
}
}
}
void JishuSort(int *a,int n,int k)
{
void print(int *a,int n);
int BarrelNUM=10;
JBarrel Barrel[BarrelNUM];
int i,j;
for(i=0; i<k; i++)
{
for(j=0; j<10; j++)
Barrel[j].head=Barrel[j].rear=NULL;
JishuSort_allocate(a,n,Barrel,i);
JishuSort_collect(a,n,Barrel,BarrelNUM);
//print(a,n);
}
}
<pre name="code" class="cpp">void print_Jint(Jint *a,int n)
{
int i=a[0].next;
while(i != 0)
{
cout<<a[i].value<<" ";
i=a[i].next;
}
cout<<endl;
}
int Weishu_cnt(int a)
{
/// 计算整个序列最大值的数字位数。
int cnt=1;
while(0 !=a/10 )
{
cnt++;
a=a/10;
}
return cnt;
}
第二种是使用静态链表,忘记的同学可以自行复习。使用静态链表的好处是可以减少赋值操作,因为第一种链表的数据结构中,分配时需要形成一个链表节点,这里赋值一次,收集时,赋值返回时有需要一次。如果使用静态链表,只需要操作数组的第二维(也就是下一个数的地址,类似于指针)的值,进行一些逻辑运算。可以减少不少操作时间,提高效率,节约空间。不过复杂度的量级不变,只不过是前面的系数减小。
静态链表可以看成,有物理顺序和逻辑顺序的序列。
这里Basket结构体用来记录桶中队首和队尾元素在序列中的位置。因为这里的桶,也是符合先进先出,所以是队列。
typedef struct Jint
{
int value;
int next;
} Jint,*JintP;
struct Basket
{
int head;
int rear;
};
void Initial_Jint(Jint *a,int n,int range)
{
/// 随机产生n个元素,使用静态链表结构
int i;
srand( (unsigned)time( NULL ) );
for(i=1; i<n; i++)
{
a[i].value=rand()%range+1;
a[i].next=i + 1;
}
a[0].next=1;
a[n-1].next = 0;
}
void JishuSort_allocate_G(Jint *a,int n,struct Basket *b,int k)
{
int ten=1,i,temp;
for(i=0; i<k; ++i)
ten*=10;
if(1 == ten)
{
<span style="white-space:pre"> </span>/// 第一次分配时,因为元素逻辑顺序和物理顺序一致,所以直接遍历,减少一点儿时间吧。
for(i = 1; i < n; ++i)
{
temp = (a[i].value / ten) % 10;
if(-1 == b[temp].head)
{
<span style="white-space:pre"> </span>/// 桶里没有元素。首插。下同。
b[temp].head = b[temp].rear = i;
}
else
{
int t = b[temp].rear;
a[t].next = i;
b[temp].rear = i;
}
}
}
else
{
<span style="white-space:pre"> </span>/// 这里,只能老老实实的按照逻辑顺序来遍历。
for(i = a[0].next; i > 0; )
{
temp=(a[i].value/ten)%10;
if(-1 == b[temp].head)
{
b[temp].head = b[temp].rear =i;
}
else
{
int t=b[temp].rear;
a[t].next = i;
b[temp].rear=i;
}
i = a[i].next;
}
}
}
void JishuSort_collect_G(Jint *a,int n,struct Basket *b)
{
void print_Jint(Jint *a,int n);
int i;
int Blast=0;
for(i = 0; i<10; ++i)
{
<span style="white-space:pre"> </span>/// 分配后桶里没有元素,则跳过。
if(-1 == b[i].head)
continue;
else
{
<span style="white-space:pre"> </span> /// 这个桶中的队头接到上一个桶的队尾,并记录当前桶队尾元素。Blast。
a[Blast].next=b[i].head;
Blast=b[i].rear;
}
}
a[Blast].next=0;
print_Jint(a,n);
}
void JishuSort_G(Jint *a,int n,int k)
{
void print_Jint(Jint *a,int n);
int i,j;
struct Basket Barrel[10];
for(i=0; i<k; i++)
{
for(j=0; j<10; ++j) {Barrel[j].head = Barrel[j].rear = -1;}
JishuSort_allocate_G(a,n,Barrel,i);
JishuSort_collect_G(a,n,Barrel);
//
}
}