前言:
计数排序、基数排序和桶排序是线性时间排序,不是用比较来确定顺序。
计数排序
计数排序假设n输入元素中的每一个都在[0,k]区间内的一个整数,其中k为输入数组中的最大整数。
计数排序的基本思想是:对一个输入元素x,先确定所有输入元素中小于x的元素个数,利用这一信息,直接可以把x放到输出数组中的位置上。比如,所有的输入元素中有17个元素小于x,那么排好序后x的位置序号就应该是18。如果有相同元素,自然要放到相邻的位置上。计数排序的原理:设被排序的数组为A,排序后存储到B,C为临时数组。所谓计数,首先是通过一个数组C[i]计算大小等于i的元素个数,此过程只需要一次循环遍历就可以;在此基础上,计算小于或者等于i的元素个数,也是一重循环就完成。下一步是关键:逆序循环,从length[A]到1,将A[i]放到B中第C[A[i]]个位置上。原理是:C[A[i]]表示小于等于a[i]的元素个数,正好是A[i]排序后应该在的位置。而且从length[A]到1逆序循环,可以保证相同元素间的相对顺序不变,这也是计数排序稳定性的体现。在数组A有附件属性的时候,稳定性是非常重要的。
//计数排序
void Counting_sort(int* A,int* B,int length_A,int k)
{//A为待排序数组,B为排序后输出,min为待排序元素的最小值,k为待排序元素的最大值
int C[12] = {0};//临时数组,数组长度为待排序元素最大值与最小值之差加1
int i,j;
for (j=0;j<length_A;j++)
{//统计相同数值元素的个数
C[A[j]]++;
}
for (i=1;i<=k;i++)
{//统计小于等于元素i的个数
C[i] += C[i-1];
}
for (j=length_A-1;j>=0;j--)
{
B[C[A[j]]-1] = A[j];
C[A[j]]--;
}
}
改进版
- 初始化一个计数数组,大小是输入数组中的最大的数;
- 遍历输入数组,遇到一个数就在计数数组对应的位置上加一;
- 把计数数组直接覆盖到输出数组(节约空间);
int maximum(int * array, int size){
int curr = 0;
int max = 0;
for(curr = 0; curr < size; curr++){
if(array[curr] > max){ max = array[curr]; }
}
return max;
}
void countingSort(int * array, int size){
int curr = 0;
int max = maximum(array, size);/* 找出输入数组中的最大值,以便分配计数数组的内存空间大小 */
/* 分配计数数组的内存空间,其内存空间大小为输入最大值加 1 ,并初始化为 0 */
int * counting_array = (int *)calloc(max+1, sizeof(int)); // Zeros out the array
/* 统计输入数组中相同元素对应的个数 */
for(curr = 0; curr < size; curr ++){
counting_array[array[curr]]++;
}
int num = 0;
curr = 0;
while(curr < size){
/* 当counting_array[num]不为 0 时,表示输入数组存在 num 的元素 */
while(curr < size && counting_array[num] > 0){
array[curr] = num;
counting_array[num]--;
curr++;
}
num++;
}
free(counting_array);
}
基数排序
基数排序是按多个关键字进行排列的,按照关键字的高低位进行排列。
以下举例:
假设对一组三位数进行排列:
120,121,132,140,154,106,104,107,158,139
假设按照从低位到高位排序,即先排序个位数,再到十位数,以此类推。
- 第一步按个位数排序后:120,140,121,132,154,104,106,107,158,139
- 第二步按十位数排序后:104,106,107,120,121,132,139,140,154,158
- 第三步按百位数排序后:104,106,107,120,121,132,139,140,154,158
void Radix_sort(int *arr,int number_of_data,int len)
{
int counter = 0;
int n =1;
int i,j;
while (counter<number_of_data)/* number_of_data是待排序元素的位数 */
{
int counter_for_index[10] = {0};//桶中的计数器 表示放了多少个元素
int tmp[10][10] = {{0}};
//将数据放入对应的桶中
for (i = 0;i<len;i++)
{
int index = (arr[i]/n)%10;/* 计算待排序元素的最低位 */
tmp[index][counter_for_index[index]++] = arr[i];
}
n*=10;
counter++;//位数计数器
//将放入桶中的数据写入原数组
int k = 0;
for (i= 0;i<10;i++)
{
for (j = 0;j<counter_for_index[i];j++)
{
arr[k++] = tmp[i][j];
}
}
}
}
桶排序
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,不受到O(n log n) 下限的影响。
桶排序的基本思想:桶排序(Bucket Sort),其基本思想是:设置若干个桶子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个桶子里(分配),然后按序号依次将各非空的桶子首尾连接起来(收集)。
图中A为待排序数组,B是链表实现的已排序数组,红色代表为空。
以下以链表形式给出桶排序的程序:
struct Node
{
double value;
Node *next;
};
//桶排序
void bucketSort(double* arr, int length)
{
Node key[10];//创建桶的个数
int number = 0;//桶的序号
Node *p, *q;//插入节点临时变量
int counter = 0;//桶中元素的个数
for(int i = 0; i < 10; i++)
{//创建空链表
key[i].value = 0;
key[i].next = NULL;
}
for(int i = 0; i < length; i++)
{//将数组元素插入到对应的桶中,并对每个桶进行排序
Node *insert = new Node();
insert->value = arr[i];
insert->next = NULL;
number = arr[i] * 10;//元素对应插入的桶序号
if(key[number].next == NULL)
{
key[number].next = insert;
}
else
{//若桶非空,则按一定顺序插入数据
p = &key[number];
q = key[number].next;
while((q != NULL) && (q->value <= arr[i]))
{
q = q->next;
p = p->next;
}
insert->next = q;
p->next = insert;
}
}
for(int i = 0; i < 10; i++)
{//各个桶首尾相接的输出元素
p = key[i].next;
if(p == NULL)
continue;
while(p != NULL)
{
arr[counter++] = p->value;
p = p->next;
}
}
}