线性时间排序算法:计数排序、桶排序与基数排序详解

news2024/12/13 0:56:41

引言

排序算法的时间复杂度通常被限制在O(n log n),如快速排序、归并排序和堆排序。然而,在某些特定场景下,线性时间排序算法(时间复杂度为O(n))则可以更高效地完成任务。

今天,我们将深入探讨计数排序桶排序基数排序的核心思想和实现方法。它们利用了数据的特点和分布规律,通过避免比较操作,达到了更优的时间复杂度。尽管适用范围有限,但在合适的场景下,它们可以成为绝佳的排序工具。


一、计数排序(Counting Sort)

1.1 算法思想

计数排序是一种基于计数统计的排序算法,适用于元素范围有限且为非负整数的数据。其核心思想是:

  1. 根据数据的值,统计每个元素出现的次数。
  2. 累加计数,确定每个元素在结果数组中的位置。
  3. 根据计数结果,将元素放置到正确位置。
1.2 算法过程

假设待排序的数组为 arr[],范围为 [0, k)

  1. 计数统计:创建一个大小为k的计数数组count[],统计每个值的出现次数。
  2. 累加计数:对count[]进行累加,用于确定每个元素的最终位置。
  3. 输出结果:遍历原数组,根据count[]中的位置信息,将元素存入结果数组。
1.3 C语言实现
​
#include <stdio.h>
#include <stdlib.h>

void countingSort(int arr[], int n, int maxValue) {
    int* count = (int*)calloc(maxValue + 1, sizeof(int)); // 初始化计数数组
    int* output = (int*)malloc(n * sizeof(int));          // 输出数组

    // 统计每个元素的出现次数
    for (int i = 0; i < n; i++) {
        count[arr[i]]++;
    }

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

    // 根据计数将元素放置到输出数组中
    for (int i = n - 1; i >= 0; i--) {  // 从后往前遍历保证稳定性
        output[count[arr[i]] - 1] = arr[i];
        count[arr[i]]--;
    }

    // 将结果拷贝回原数组
    for (int i = 0; i < n; i++) {
        arr[i] = output[i];
    }

    free(count);
    free(output);
}

int main() {
    int arr[] = {4, 2, 2, 8, 3, 3, 1};
    int n = sizeof(arr) / sizeof(arr[0]);
    int maxValue = 8;

    countingSort(arr, n, maxValue);

    printf("排序后的数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

​
1.4 时间和空间复杂度
  • 时间复杂度:O(n + k),其中n是数组大小,k是最大值范围。
  • 空间复杂度:O(n + k),需要额外的计数数组和输出数组。
1.5 特点与适用场景
  • 特点
    • 稳定排序。
    • 适用于元素范围小的整数数据。
  • 适用场景
    • 数据范围有限,数据分布均匀(如考试成绩统计)。

二、桶排序(Bucket Sort)

2.1 算法思想

桶排序通过将数据分配到若干个“桶”中,每个桶内的数据进行单独排序后再合并。其核心思想是分而治之

  1. 根据数据分布,将元素分配到不同的桶中。
  2. 对每个桶内的元素单独排序。
  3. 按顺序合并所有桶内的元素,得到最终结果。
2.2 算法过程
  1. 分桶:创建若干个桶,每个桶对应一个数据范围。
  2. 入桶:将元素根据其值分配到对应的桶中。
  3. 桶内排序:对每个桶单独排序。
  4. 合并结果:依次将所有桶的元素合并。
2.3 C语言实现
​
#include <stdio.h>
#include <stdlib.h>

// 链表节点
typedef struct Node {
    int value;
    struct Node* next;
} Node;

// 将元素插入链表(从小到大排序)
Node* insertSorted(Node* head, int value) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->value = value;
    newNode->next = NULL;

    if (!head || value < head->value) {
        newNode->next = head;
        return newNode;
    }

    Node* current = head;
    while (current->next && current->next->value < value) {
        current = current->next;
    }
    newNode->next = current->next;
    current->next = newNode;
    return head;
}

// 桶排序
void bucketSort(int arr[], int n) {
    int bucketCount = 10;  // 假设数据范围为[0, 100)
    Node** buckets = (Node**)calloc(bucketCount, sizeof(Node*));

    // 入桶
    for (int i = 0; i < n; i++) {
        int bucketIndex = arr[i] / 10;
        buckets[bucketIndex] = insertSorted(buckets[bucketIndex], arr[i]);
    }

    // 合并所有桶
    int index = 0;
    for (int i = 0; i < bucketCount; i++) {
        Node* current = buckets[i];
        while (current) {
            arr[index++] = current->value;
            Node* temp = current;
            current = current->next;
            free(temp);
        }
    }

    free(buckets);
}

int main() {
    int arr[] = {78, 17, 39, 26, 72, 94, 21, 12, 68, 36};
    int n = sizeof(arr) / sizeof(arr[0]);

    bucketSort(arr, n);

    printf("排序后的数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

​
2.4 时间和空间复杂度
  • 时间复杂度
    • 平均:O(n + k)(假设桶内排序为线性时间)。
    • 最坏:O(n²)(所有数据集中在一个桶内)。
  • 空间复杂度:O(n + k)。
2.5 特点与适用场景
  • 特点
    • 非比较排序,效率高。
    • 稳定排序。
  • 适用场景
    • 数据分布均匀,范围已知(如浮点数排序)。

三、基数排序(Radix Sort)

3.1 算法思想

基数排序通过对数据的(如个位、十位、百位)依次排序来完成整体排序。它使用稳定的排序算法(如计数排序)作为子过程。

3.2 算法过程
  1. 从最低位开始,对数组按每一位的值进行排序。
  2. 每次排序完成后,按当前位的顺序重新组织数组。
  3. 重复上述过程,直到最高位。
3.3 C语言实现
void countingSortForRadix(int arr[], int n, int exp) {
    int* output = (int*)malloc(n * sizeof(int));
    int count[10] = {0};

    // 统计每个数字出现次数
    for (int i = 0; i < n; i++) {
        count[(arr[i] / exp) % 10]++;
    }

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

    // 按当前位排序
    for (int i = n - 1; i >= 0; i--) {
        int digit = (arr[i] / exp) % 10;
        output[count[digit] - 1] = arr[i];
        count[digit]--;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = output[i];
    }

    free(output);
}

void radixSort(int arr[], int n) {
    int maxValue = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > maxValue) maxValue = arr[i];
    }

    for (int exp = 1; maxValue / exp > 0; exp *= 10) {
        countingSortForRadix(arr, n, exp);
    }
}

int main() {
    int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
    int n = sizeof(arr) / sizeof(arr[0]);

    radixSort(arr, n);

    printf("排序后的数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

四、总结与展望

特性计数排序桶排序基数排序
时间复杂度O(n + k)O(n + k)O(d × (n + k))
空间复杂度O(n + k)O(n + k)O(n + k)
稳定性稳定稳定稳定
适用场景整数数据,小范围数据分布均匀整数数据,位数有限

线性时间排序算法在特定场景下效率极高,适合对数据范围明确、特点清晰的任务。下一篇文章中,我们将总结排序算法的整体特点,并通过实战案例展示如何选择合适的排序算法。

计数排序、桶排序和基数排序打破了O(n log n)的限制,在合适的场景中展现了极高的效率。希望通过这篇文章,你能够深入理解它们的核心思想,并在实际问题中灵活应用。
如果有任何问题或建议,欢迎在评论区留言,我们一起探讨!🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2258496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++的一些经典算法

以下是C的一些经典算法&#xff1a; 一、排序算法 冒泡排序&#xff08;Bubble Sort&#xff09; 原理&#xff1a; 它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换…

35.1 thanos项目介绍和二进制部署

本节重点介绍 : 核心优点 无需维护存储&#xff0c;存储高可用&#xff1a; 利用廉价的公有云对象存储&#xff0c;高可用长时间存储&#xff0c;数据降采样&#xff1a;利用Compactor降采样完全适配原生prometheus查询接口&#xff1a;Query实现多级数据缓存配置 二进制部署 …

【PlantUML系列】状态图(六)

一、状态图的组成部分 状态&#xff1a;对象在其生命周期内可能处于的条件或情形&#xff0c;使用 state "State Name" as Statename 表示。初始状态&#xff1a;表示对象生命周期的开始&#xff0c;使用 [*] 表示。最终状态&#xff1a;表示对象生命周期的结束&…

Android 15(V)新功能适配,雕琢移动细节之美

Android 15&#xff0c;内部代号为Vanilla Ice Cream&#xff0c;是Android移动操作系统的最新主要版本&#xff0c;于2024年2月16日在开发者预览版1中发布。Android 15源代码于 2024年9月4日发布。Android 15稳定版于2024年10月15日发布。 以下是针对 Android 15&#xff08;…

【零成本抽象】基本概念与在C++中的实现

零成本抽象概念是由 Bjarne Stroustrup 提出的,他在 1994 年的著作中就有相关设想,2016 年其在 C++ 大会登台演讲时,明确阐述了 C++ 中的 “零成本抽象” 这一理念。 一、零成本抽象概念 Bjarne Stroustrup提出的零成本抽象概念,是指在编程中使用高级抽象机制时,不会产生…

android编译assets集成某文件太大更新导致git仓库变大

不知道大家有没有类似的困扰&#xff0c;你的工程assets文件过大&#xff0c;我曾经在某度车机地图团队工作过一段时间时候&#xff0c;每次发包会集成一个上百MB的文件。工作一段时间你的git仓库将会增加特别多。最后&#xff0c;你会发现你如果重新git clone这个仓库会非常大…

F5-TTS文本语音合成模型的使用和接口封装

F5-TTS文本语音生成模型 1. F5-TTS的简介 2024年10月8日&#xff0c;上海交通大学团队发布&#xff0c;F5-TTS (A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching) 是一款基于扩散Transformer和ConvNeXt V2的文本转语音 (TTS) 模型。F5-TTS旨在生成流…

克隆选择算法复现

克隆选择算法复现 基于克隆选择算法求解0 - 1背包问题的代码复现文档一、背景和意义&#xff08;一&#xff09;背景&#xff08;二&#xff09;意义 二、算法原理&#xff08;一&#xff09;克隆选择算法基础&#xff08;二&#xff09;受体编辑机制 三、算法流程&#xff08;…

Scala的隐式对象

Scala中&#xff0c;隐式对象&#xff08;implicit object&#xff09;是一种特殊的对象&#xff0c;它可以使得其成员&#xff08;如方法和值&#xff09;在特定的上下文中自动可用&#xff0c;而无需显式地传递它们。隐式对象通常与隐式参数和隐式转换一起使用&#xff0c;以…

观察者模式的理解和实践

引言 在软件开发中&#xff0c;设计模式是开发者们为了解决常见的设计问题而总结出来的一系列最佳实践。观察者模式&#xff08;Observer Pattern&#xff09;是其中一种非常经典且使用率极高的设计模式。它主要用于定义对象之间的一对多关系&#xff0c;使得当一个对象的状态发…

windows下Qt5自动编译配置QtMqtt环境(11)

文章目录 [toc]1、概述2、准备1.1 下载源码1.2 配置环境1.3 解释原理 3、编译4、验证5、参考6、视频 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt网络编程 &#x1f448; 1、概述 Qt默认是不包含mqtt库的&#xff0c;如果需要使用到mqtt库就只能自己编译配…

【6】数据分析检测(DataFrame 1)

学习目标3 昨天&#xff0c;我们学习了Series。 而Pandas的另一种数据类型&#xff1a;DataFrame&#xff0c;在许多特性上和Series有相似之处。 今天&#xff0c;我们将学习DataFrame的相关知识&#xff1a; 1. DataFrame的概念 2. 构造一个DataFrame 3. DataFrame的常用…

如何选择安全、可验证的技术?

澳大利亚信号局的澳大利亚网络安全中心 (ASD 的 ACSC) 发布了一份指导文件&#xff0c;题为《选择安全和可验证的技术》&#xff0c;旨在帮助组织在采购软件&#xff08;专有或开源&#xff09;、硬件&#xff08;例如物联网设备&#xff09;和云服务&#xff08;SaaS、MSP 服务…

趣味编程:猜拳小游戏

1.简介 这个系列的第一篇以猜拳小游戏开始&#xff0c;这是源于我们生活的灵感&#xff0c;在忙碌的时代中&#xff0c;我们每个人都在为自己的生活各自忙碌着&#xff0c;奔赴着自己所走向的那条路上&#xff0c;即使遍体鳞伤。 但是&#xff0c;生活虽然很苦&#xff0c;也不…

轮转数组

轮转数组 1、题目描述2、解答思路2.1、辅助数组2.2、原地反转 1、题目描述 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 2、解答思路 2.1、辅助数组 如果我们在原数组上通过覆盖元素会导致部分元素的丢失&#xff0c…

如何编译安装系统settings设置应用(5.0.0-Release)

本文介绍如何在OpenHarmony 5.0.0 r版本中修改系统设置应用&#xff0c;并且编译安装到开发板上 开发环境 1.dayu200开发板 2.OpenHarmony 5.0.0r 固件 3.API12 full sdk &#xff08;如果安装full sdk过程中出现报错hvigor ERROR: Cannot find module typescript,请参考 h…

学习记录,隐式对象,隐式类

隐式对象 格式&#xff1a;就是在对象前面加一个 implicit 作用&#xff1a;就是给函数当默认值&#xff01; 隐式类 隐式类 一个类 一个隐式转换函数 格式&#xff1a;在class 的前面&#xff0c;添加implicit 要点&#xff1a;要有一个参数&#xff0c;就要待被转换的类型…

第三部分:进阶概念 9.错误处理 --[JavaScript 新手村:开启编程之旅的第一步]

在JavaScript中&#xff0c;错误处理是确保应用程序稳定性和用户体验的重要部分。JavaScript提供了几种机制来捕获和处理运行时错误&#xff08;异常&#xff09;。以下是几种常见的错误处理方式&#xff1a; 1. try...catch 语句 try...catch 语句是JavaScript中处理错误和异…

Java面试之多线程状态(三)

此篇接上一篇Java面试之实现多线程(二) Java线程可以拥有自己的操作数栈、程序计数器、局部变量表等资源&#xff0c;它与同一进程内的其他线程共享该进程的所有资源。Java线程在生命周期内存在多种状态&#xff0c;可以通过Thread.State枚举类获取线程状态。如图所示有NEW(新建…

数据仓库:智控数据中枢

数据仓库 一. 什么是数据仓库&#xff1f;二. 传统数据库与数据仓库的区别&#xff1f;三. 数据仓库详解&#xff08;一&#xff09;. 数据分析&#xff08;二&#xff09;. 特点1. 面向主题2. 集成的3. 反应历史变化 四. 如何搭建数据仓库&#xff08;一&#xff09;. 数据平台…