基于C语言的计数排序算法

一、计数排序概述

        计数排序是一种非比较排序算法,它与其他基于比较的排序算法有着明显的不同。这种算法主要适用于整数排序,特别是当数值范围不是特别大的时候,它能够展现出非常高的效率。

        计数排序的时间复杂度为 Ο(n+k),其中 n 是数组长度,k 是数值范围。这意味着当数值范围相对较小时,计数排序的效率会非常高。例如,当处理一个包含 100 个整数的数组,且这些整数的范围在 0 到 99 之间时,计数排序可以快速地完成排序任务。

        然而,计数排序也有其局限性。它需要额外的空间来存储计数数组。这个计数数组的大小取决于数值范围 k。如果数值范围很大,那么所需的额外空间也会相应地增大。例如,对于一个包含 1000 个整数的数组,且这些整数的范围在 0 到 1000000 之间,计数排序需要创建一个大小为 1000001 的计数数组,这会占用大量的内存空间。

        计数排序的步骤主要包括确定数值范围、创建计数数组、统计每个数值出现的次数、累计计数、构建输出数组和复制排序结果等。首先,需要知道数组中的最大值和最小值,这有助于确定计数数组的大小。然后,创建一个大小等于数值范围的计数数组,并初始化为 0。接着,遍历待排序数组,对于数组中的每一个元素,在 count [] 中相应位置的计数加 1。这样 count [] 就记录了每个数值出现的次数。之后,遍历 count [],将前一个元素的值累加到当前元素上,得到的结果就是该数值在排序后数组中的正确位置。再创建一个新的数组 output [],并从后往前遍历待排序数组,根据 count [] 中记录的位置,将数值放入 output [] 中正确的索引位置。最后,将 output [] 中的数据复制回原数组。

        总之,计数排序是一种在特定情况下非常高效的排序算法,但也需要考虑其额外空间的占用问题。

二、计数排序原理与步骤

(一)确定数值范围

        在计数排序中,确定数值范围是非常关键的一步。通过遍历待排序数组,找出其中的最大值和最小值,可以确定数值的范围。例如,假设有一个待排序数组为 {4, 2, 2, 8, 3, 3, 1},通过遍历可以确定最大值为 8,最小值为 1,那么数值范围就是 [1, 8]。如果数据范围已知,就可以直接使用该范围,无需再进行遍历查找最大值和最小值的操作。确定数值范围有助于确定计数数组的大小,为后续的排序步骤提供基础。

(二)创建计数数组

        创建计数数组的大小等于数值范围。以刚才确定的 [1, 8] 范围为例,创建一个大小为 8 的数组 count [],初始值为 0。这样,每个位置对应一个可能的数值,初始时都没有出现过,所以计数为 0。这个计数数组将用于记录每个数值出现的次数。

(三)统计数值出现次数

        遍历待排序数组,对于数组中的每一个元素,在计数数组相应位置计数加 1。例如,当遇到第一个元素 4 时,count [4] 加 1;遇到第二个元素 2 时,count [2] 加 1,以此类推。继续以 {4, 2, 2, 8, 3, 3, 1} 这个数组为例,最后 count [] 变为 [0, 1, 2, 2, 1, 0, 0, 1]。通过这个步骤,计数数组记录了每个数值出现的次数。

(四)累计计数

        遍历计数数组,将前一个元素的值累加到当前元素上。比如,count [1] 不变;count [2] 变为 1 + 2 = 3;count [3] 变为 3 + 2 = 5;以此类推。最后 count [] 变为 [0, 1, 3, 5, 6, 6, 6, 7]。这个步骤的目的是得到每个数值在排序后数组中的正确位置。

(五)构建输出数组

        创建一个新的数组 output [] 用于存放排序后的结果。从后往前遍历待排序数组,根据计数数组记录的位置,将数值放入输出数组正确索引位置。以 {4, 2, 2, 8, 3, 3, 1} 这个数组为例,最后一个元素 1 应该放在 output [1] 的位置,即 output [count [1] - 1] = 1,然后 count [1] 减 1。重复此过程直到所有元素被放置完毕。这样可以保持稳定性,即相同的元素保持原有的相对顺序。

(六)复制排序结果

        最后将 output [] 中的数据复制回原数组。这样就完成了整个计数排序的过程。

三、代码实现与示例

(一)函数声明与随机数组生成

以下是 C 语言中计数排序的代码实现过程,首先是函数声明和随机数组生成部分。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 函数声明
// 创建并生成指定大小和范围的随机数组,返回数组指针
int* create_and_generate_random_array(int size, int range);
// 打印数组元素的函数,接受一个常量整数数组指针和数组大小
void print_array(const int *array, int size);
// 计数排序函数,接受待排序数组、数组大小和数据范围作为参数
void counting_sort(int *array, int size, int range);

int main() 
{
    int size = 10; // 数组大小,这里设定为10个元素
    int range = 100; // 数据范围,数组中的元素将在0到这个值 - 1之间随机生成

    // 创建并填充随机数组
    int *array = create_and_generate_random_array(size, range);
    if (array == NULL) 
    {
        // 如果内存分配失败(create_and_generate_random_array函数返回NULL)
        printf("Memory allocation failed\n");
        return 1;
    }

    // 打印原始数组
    printf("原始数组: ");
    print_array(array, size);

    // 调用计数排序函数对数组进行排序
    counting_sort(array, size, range);

    // 打印排序后的数组
    printf("排序后的数组: ");
    print_array(array, size);

    // 释放动态分配的数组内存
    free(array);

    return 0;
}

// 创建并生成随机数组
int* create_and_generate_random_array(int size, int range) 
{
    // 使用malloc动态分配足够的内存来存储size个整数
    int *array = (int *)malloc(sizeof(int) * size);
    if (array == NULL) 
    {
        // 如果内存分配失败,返回NULL
        return NULL;
    }
    // 设置随机数种子,使每次运行程序生成的随机数序列不同
    srand(time(NULL));
    // 循环生成随机数填充数组
    for (int i = 0; i < size; i++) 
    {
        // 生成0到range - 1之间的随机数并赋值给数组元素
        array[i] = rand() % range;
    }
    return array;
}

// 打印数组的函数
void print_array(const int *array, int size) 
{
    for (int i = 0; i < size; i++) 
    {
        // 逐个打印数组元素,元素之间用空格隔开
        printf("%d ", array[i]);
    }
    // 换行
    printf("\n");
}

// 计数排序函数(这里只是函数框架,需要进一步完善才能实现排序功能)
void counting_sort(int *array, int size, int range) 
{
    // 计数排序的基本思路是统计每个数出现的次数,然后根据次数将数重新放回原数组
    // 1. 创建计数数组并初始化为0
    int *count = (int *)malloc(sizeof(int) * range);
    for (int i = 0; i < range; i++) 
    {
        count[i] = 0;
    }

    // 2. 统计每个数在原数组中出现的次数
    for (int i = 0; i < size; i++) 
    {
        count[array[i]]++;
    }

    // 3. 根据计数数组重新构建原数组(这里还未完整实现)
    int index = 0;
    for (int i = 0; i < range; i++) 
    {
        while (count[i] > 0) 
        {
            array[index] = i;
            index++;
            count[i]--;
        }
    }

    // 释放计数数组的内存
    free(count);
}

(二)打印数组

接下来是打印数组的函数,用于输出数组的内容。

// 打印数组
void print_array(const int *array, int size) 
{
    for (int i = 0; i < size; i++) 
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

(三)计数排序函数

最后是计数排序函数,实现计数排序的核心逻辑。

// 计数排序
void counting_sort(int *array, int size, int range) 
{
    int *count = (int *)malloc((range + 1) * sizeof(int));
    int *output = (int *)malloc(size * sizeof(int));

    // 初始化计数数组
    for (int i = 0; i <= range; ++i) 
    {
        count[i] = 0;
    }

    // 统计每个数值出现的次数
    for (int i = 0; i < size; ++i) 
    {
        count[array[i]]++;
    }

    // 累积计数
    for (int i = 1; i <= range; ++i) 
    {
        count[i] += count[i - 1];
    }

    // 构建输出数组
    for (int i = size - 1; i >= 0; --i) 
    {
        output[count[array[i]] - 1] = array[i];
        count[array[i]]--;
    }

    // 复制排序结果到原数组
    for (int i = 0; i < size; ++i) 
    {
        array[i] = output[i];
    }

    // 释放临时数组
    free(count);
    free(output);
}

我们可以通过以下方式调用这些函数,对随机生成的数组进行计数排序并输出结果。

int main() 
{
    int size = 10; // 数组大小
    int range = 100; // 数据范围

    // 创建并填充随机数组
    int *array = create_and_generate_random_array(size, range);
    if (array == NULL) 
    {
        printf("Memory allocation failed\n");
        return 1;
    }

    // 打印原始数组
    printf("Original array:\n");
    print_array(array, size);

    // 进行计数排序
    counting_sort(array, size, range);

    // 打印排序后的数组
    printf("Sorted array:\n");
    print_array(array, size);

    // 释放内存
    free(array);
    return 0;
}

例如,对于一个随机生成的数组 {89, 45, 76, 23, 67, 54, 32, 98, 12, 78},经过计数排序后,将按照从小到大的顺序输出。计数排序在处理整数排序时,特别是当数值范围相对较小的情况下,能够高效地完成排序任务。

四、计数排序特点总结

(一)稳定性

        计数排序是一种稳定的算法。这意味着在排序过程中,相等元素的相对顺序不会改变。例如,对于数组 {3, 2, 2, 1},在计数排序后,两个 2 的相对顺序依然保持不变。这种稳定性在某些特定的应用场景中非常重要,比如需要保留元素原始顺序信息的情况。

(二)时间复杂度

        计数排序的时间复杂度为 ,其中 是待排序数组的长度, 是数值范围。当数值范围相对较小时,计数排序的效率非常高。例如,当处理一个包含 1000 个整数的数组,且这些整数的范围在 0 到 99 之间时, 相对 来说很小,此时计数排序可以快速地完成排序任务。但如果数值范围很大,比如对于一个包含 1000 个整数的数组,且这些整数的范围在 0 到 1000000 之间,虽然时间复杂度依然是 ,但由于 的值很大,实际运行时间可能会较长。

(三)空间复杂度

        计数排序的空间复杂度为 ,即取决于数值范围 。如果数值范围很大,那么所需的额外空间也会相应地增大。例如,对于一个包含 1000 个整数的数组,且这些整数的范围在 0 到 1000000 之间,计数排序需要创建一个大小为 1000001 的计数数组,这会占用大量的内存空间。

(四)适用场景

        计数排序适用于范围较为集中且重复数据较多的场景。例如,在统计学生考试成绩的分布情况时,成绩通常在一个相对较小的范围内,且可能有很多重复的分数。此时,计数排序可以高效地完成排序和统计任务。另外,计数排序只适合对整数进行排序,如果数据类型不是整数,就不能使用计数排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值