前言:
计数排序是了解基数排序的基石之一,同时也是基数排序中用的最广泛的两种排序算法之一(还有一个是桶排序)。计数排序的时间复杂度很低,但是空间复杂度比较高,这是它的制约因素。
我写的这个计数排序不能解决数组中出现负数的问题。但是出现负数其实很简单——我这个的实现是数组向左偏移实现对齐;如果有负数的话,我只需要找到最小的负数,并且让数组中所有数字向右偏移对齐就可以了,实现起来也是基本差不多,就不另外写了。
思路:
对一堆数字进行排序不用两两比较,我们只需要创造许多容器(有序),把数组元素一一对应的放进去,然后顺序读取容器中的值就可以了。这个计数排序就是这么个思路。但是,我们如果把元素的值当做下标,未免太浪费空间,所以我选择将元素向左偏移,让最小的元素下标为0,节省一点点空间。
首先,我们需要一个辅助的计数数组和一个为了方便存储元素的和原数组一样大的数组空间。创建空间就最好先进行初始化,免得后面出问题。
那么计数数组的空间应该多大?应该是数组中值 max - min + 1 的大小。我写在外头单独封装。
int find_count_len(int* a, int len)
{
int min = a[0], max = a[0];
int i;
int count = 0;
for (i = 0; i < len; i++)
{
count++;
if (a[i] > max)max = a[i];
if (a[i] < min)min = a[i];
}
printf("传入元素个数:%d\n", count);
printf("数组最大值:%d\n数组最小值:%d\n", max, min);
printf("计数数组长度为:%d\n", max - min + 1);
return max - min + 1;
}
创建空间的同时也顺带找一下偏移量,其实就是数组中最小的元素
int count_arr_len = find_count_len(a, len);
int* count_arr = (int*)malloc(sizeof(int) * count_arr_len);
int i;
for (i = 0; i < count_arr_len; i++)
count_arr[i] = 0;
//找偏移量:就是数组中最小的值
int offset = a[0];
for (i = 1; i < len; i++)
if (a[i] < offset)offset = a[i];
//存放有序数组的空间
int* b = (int*)malloc(sizeof(int) * len);
然后我我们给计数数组开始计数:(每个相同的元素在相同的计数下标下+1),a[i] - offset 是偏移以后的计数数组的下标。
for (i = 0; i < len; i++)
count_arr[a[i] - offset]++;
接着累加计数数组,这一步的作用是在之后读入数字的时候可以从后往前排序,为了让计数排序成为稳定的排序,不打乱元素之间的相对位置。
//累加计数数组,得到偏移下标
for (i = 1; i < count_arr_len; i++)
count_arr[i] += count_arr[i - 1];
最后是最核心的排序部分,核心思想就是把原数组中的元素,通过计数数组得到他在有序数组中的下标,然后读进去,代码很简单,但需要理解。画个图,多用手模拟几遍就懂了。
for (i = len; i > 0; i--)
b[--count_arr[a[i - 1] - offset]] = a[i - 1];
最后就是我们排序的是传入的数组a,我们需要将辅助数组中的值迁移到a中。选择用哪种方式迁移都可以,除了地址互换(辅助数组是要free掉的,会报错)。
下面是排序的核心代码:
void generate_random_number(int*, int, int);
void swap(int*, int*);
int find_count_len(int* a, int len)
{
int min = a[0], max = a[0];
int i;
int count = 0;
for (i = 0; i < len; i++)
{
count++;
if (a[i] > max)max = a[i];
if (a[i] < min)min = a[i];
}
return max - min + 1;
}
void CountingSort(int* a, int len)
{
int count_arr_len = find_count_len(a, len);
int* count_arr = (int*)malloc(sizeof(int) * count_arr_len);
int i;
for (i = 0; i < count_arr_len; i++)
int offset = a[0];
for (i = 1; i < len; i++)
if (a[i] < offset)offset = a[i];
int* b = (int*)malloc(sizeof(int) * len);
//计数数组开始计数
for (i = 0; i < len; i++)
count_arr[a[i] - offset]++;
//累加计数数组,得到偏移下标
for (i = 1; i < count_arr_len; i++)
count_arr[i] += count_arr[i - 1];
for (i = len; i > 0; i--)
b[--count_arr[a[i - 1] - offset]] = a[i - 1];
for (i = 0; i < len; i++)
swap(&a[i], &b[i]);
free(count_arr);
count_arr = NULL;
free(b);
b = NULL;
}
示例代码:
这个排序由于我一开始写老是错,所以我写的时候加了很多的测试信息,因此随机数生成范围我把他变小了一点,这样计数数组方便进行观察。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define N 30
//之前写过了,就不再全写一遍了
void generate_random_number(int*, int, int);
void swap(int*, int*);
int find_count_len(int* a, int len)
{
int min = a[0], max = a[0];
int i;
int count = 0;
for (i = 0; i < len; i++)
{
count++;
if (a[i] > max)max = a[i];
if (a[i] < min)min = a[i];
}
printf("传入元素个数:%d\n", count);
printf("数组最大值:%d\n数组最小值:%d\n", max, min);
printf("计数数组长度为:%d\n", max - min + 1);
return max - min + 1;
}
void CountingSort(int* a, int len)
{
int count_arr_len = find_count_len(a, len);
int* count_arr = (int*)malloc(sizeof(int) * count_arr_len);
int i;
for (i = 0; i < count_arr_len; i++)
count_arr[i] = 0;
//找偏移量:就是数组中最小的值
int offset = a[0];
for (i = 1; i < len; i++)
if (a[i] < offset)offset = a[i];
printf("偏移量为:%d\n", offset);
//存放有序数组的空间
int* b = (int*)malloc(sizeof(int) * len);
//计数数组开始计数
for (i = 0; i < len; i++)
count_arr[a[i] - offset]++;
//test
printf("原计数数组:\n");
for (i = 0; i < count_arr_len; i++)
printf("%d ", count_arr[i]);
printf("\n");
//累加计数数组,得到偏移下标
for (i = 1; i < count_arr_len; i++)
count_arr[i] += count_arr[i - 1];
//test
printf("更新后计数数组:\n");
for (i = 0; i < count_arr_len; i++)
printf("%d ", count_arr[i]);
printf("\n");
for (i = len; i > 0; i--)
b[--count_arr[a[i - 1] - offset]] = a[i - 1];
//test
printf("数组b[]:\n");
for (i = 0; i < len; i++)
printf("%d ", b[i]);
printf("\n");
for (i = 0; i < len; i++)
swap(&a[i], &b[i]);
free(count_arr);
count_arr = NULL;
free(b);
b = NULL;
}
int main()//13
{
int arr[N + 5] = { 0 };
generate_random_number(arr, 0, 30);
CountingSort(arr, N);
printf("排序完数组为:\n");
for (int i = 0; i < N; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
测试结果: