前面介绍的计数排序、基数排序是两种线性时间(即时间复杂度为 O ( n ) O(n) O(n))的排序算法,这一篇介绍另外一种也是线性时间的排序方法:桶排序。
桶排序的前提是要排序的元素呈均匀分布。其原理是先将要排序的元素分到有限数量的桶里,然后对每个桶里的元素分别排序,最后再按顺序输出。步骤如下:
举一个简单的例子更方便理解桶排序的原理。比如对一个班的学生成绩排序,成绩范围是0 ~ 100分,分10个桶,0 ~ 9分的放一个桶,10 ~ 19分的放一个桶,以此类推,90 ~ 100分的放一个桶。先在每个桶内进行排序,然后按桶的顺序输出,最终成绩就排好序了。(这里要注意一般情况下学生成绩都是正态分布,而不是均匀分布,所以桶排序的效率并不一定好。)
按照以上原理我们来用代码实现。
下面就是用C语言实现的代码。
/* 元素结构体 */
typedef struct _elem {
int value;
struct _elem *next;
} elem_t;
void insertion_sort(elem_t **head)
{
elem_t *q, *p, *p_next, *r;
/* 表空或者只有一个节点,不需排序 */
if (*head==NULL || (*head)->next==NULL) return;
/* 分有序区和无序区,每次从无序区拿出一个插入到有序区 */
/* 在有序区头部增加一个元素以方便后面的插入,因*head可能被改掉 */
q = (elem_t*)malloc(sizeof(elem_t));
if (q == NULL) return;
/* q指向有序区的头,有序区开始只有一个元素 */
q->next = *head;
/* p指向无序区的头,无序区从第二个元素开始 */
p = q->next->next;
/* 将有序区与无序区分开 */
q->next->next = NULL;
while (p) { //无序区还存在有元素
p_next = p->next; //记录无序区的下一个元素
/* 在已排好序的链表q中找到p应该插入的位置 */
r = q;
while (r->next && r->next->value <= p->value) r=r->next;
/* 将p插入到r前面 */
p->next = r->next;
r->next = p;
p = p_next; //处理下一个无序区中的元素
}
*head = q->next;
free(q);
}
/* 假设a中元素值均匀分布 */
void bucket_sort(int a[], int n)
{
int i, j;
int max, min, step;
int bucket_num = 5;
elem_t *p, *q;
/* 申请bucket_num个桶 */
elem_t** b = (elem_t**)malloc(bucket_num*sizeof(elem_t*));
if (b==NULL) return;
/* 桶置空 */
for (i=0; i<bucket_num; i++) {
b[i] = NULL;
}
/* 找出数组a中的最大值和最小值 */
max = min = a[0];
for (i=1; i<n; i++) {
if (a[i] > max) max = a[i];
if (a[i] < min) min = a[i];
}
/* 计算桶与桶的间隔 */
step = (max - min) / bucket_num + 1;
for (i=0; i<n; i++) {
/* 计算a[i]属于哪个桶 */
j = (a[i] - min)/step;
/* 用a[i]的值构造元素p */
p = (elem_t*)malloc(sizeof(elem_t));
if (p==NULL) break;
p->value = a[i];
p->next = NULL;
/* 将元素插入对应桶的最后 */
if (b[j]==NULL) {
b[j] = p;
} else {
q = b[j];
while (q->next) q = q->next;
q->next = p;
}
}
/* 对每个桶中的元素进行排序 */
for (i=0; i<bucket_num; i++) {
insertion_sort(&b[i]);
}
/* 桶中元素按顺序输出到a中,并释放元素空间 */
j = 0;
for (i=0; i<bucket_num; i++) {
q = b[i];
while (q) {
a[j++] = q->value;
p = q;
q = q->next;
free(p);
}
}
free(b);
}
为了验证此函数的效果,加上了如下辅助代码,对3个数组进行排序,运行结果在最后,可见排序成功。
#include
#include
#include
#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20
void show_array(int a[], int n);
void bucket_sort(int a[], int n);
void main()
{
int array1[SIZE_ARRAY_1]={11,224,32,9,0};
int array2[SIZE_ARRAY_2]={1000,53,229,1,229,222};
int array3[SIZE_ARRAY_3];
for(int i=0; i<SIZE_ARRAY_3; i++) {
array3[i] = (int)((10000.0*rand())/(RAND_MAX+1.0));
}
printf("Before sort, ");
show_array(array1, SIZE_ARRAY_1);
bucket_sort(array1, SIZE_ARRAY_1);
printf("After sort, ");
show_array(array1, SIZE_ARRAY_1);
printf("Before sort, ");
show_array(array2, SIZE_ARRAY_2);
bucket_sort(array2, SIZE_ARRAY_2);
printf("After sort, ");
show_array(array2, SIZE_ARRAY_2);
printf("Before sort, ");
show_array(array3, SIZE_ARRAY_3);
bucket_sort(array3, SIZE_ARRAY_3);
printf("After sort, ");
show_array(array3, SIZE_ARRAY_3);
}
void show_array(int a[], int n)
{
if(n>0)
printf("This array has %d items: ", n);
else
printf("Error: array size should bigger than zero.\n");
for(int i=0; i<n; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
运行结果:
Before sort, This array has 5 items: 11 224 32 9 0
After sort, This array has 5 items: 0 9 11 32 224
Before sort, This array has 6 items: 1000 53 229 1 229 222
After sort, This array has 6 items: 1 53 222 229 229 1000
Before sort, This array has 20 items: 8401 3943 7830 7984 9116 1975 3352 7682 2777 5539 4773 6288 3647 5134 9522 9161 6357 7172 1416 6069
After sort, This array has 20 items: 1416 1975 2777 3352 3647 3943 4773 5134 5539 6069 6288 6357 7172 7682 7830 7984 8401 9116 9161 9522
可以证明桶排序的时间复杂度为 O ( n ) O(n) O(n),因为其假设输入数据为均匀分布。
因为桶排序需要另外的空间来存储桶和桶中元素,其量级为 n n n,所以桶排序的空间复杂度为 O ( n ) O(n) O(n)。
稳定。