排序算法之线性时间排序:计数排序,基数排序,桶排序详解

news2025/5/20 16:46:32

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

  • 前言
  • 一、计数排序(Counting Sort)
    • 1.1 算法原理
    • 1.2 代码实现(Python)
    • 1.3 性能分析
    • 1.4 适用场景
  • 二、基数排序(Radix Sort)
    • 2.1 算法原理
    • 2.2 代码实现(Java)
    • 2.3 性能分析
    • 2.4 适用场景
  • 三、桶排序(Bucket Sort)
    • 3.1 算法原理
    • 3.2 代码实现(C++)
    • 3.3 性能分析
    • 3.4 适用场景
  • 四、三种线性时间排序算法的对比与应用选择
  • 总结

前言

在排序算法的大家族中,比较排序算法(如快速排序、归并排序等)的时间复杂度下限为 O ( n log ⁡ n ) O(n \log n) O(nlogn)。而线性时间排序算法另辟蹊径,在特定条件下能够实现 O ( n ) O(n) O(n)的时间复杂度,大大提高了排序效率。本文我将深入介绍计数排序、基数排序和桶排序这三种典型的线性时间排序算法,从原理、实现到性能分析,结合具体代码示例,带大家全面了解它们的应用场景与优势。

一、计数排序(Counting Sort)

1.1 算法原理

计数排序是一种非比较排序算法,它适用于待排序元素的取值范围相对较小的情况。其核心思想是:统计每个元素在数组中出现的次数,然后根据统计结果将元素依次放置到正确的位置上。

具体步骤如下:

确定范围:找出待排序数组中的最大值 m a x max max和最小值 m i n min min,计算出元素的取值范围 r a n g e = m a x − m i n + 1 range = max - min + 1 range=maxmin+1

统计计数:创建一个长度为 r a n g e range range的计数数组 c o u n t count count,用于统计每个元素出现的次数。遍历待排序数组,将每个元素 n u m num num对应的 c o u n t [ n u m − m i n ] count[num - min] count[nummin]的值加 1。

累计计数:对计数数组 c o u n t count count进行累计操作,即 c o u n t [ i ] = c o u n t [ i ] + c o u n t [ i − 1 ] count[i] = count[i] + count[i - 1] count[i]=count[i]+count[i1] i > 0 i > 0 i>0),此时 c o u n t [ i ] count[i] count[i]表示小于等于 i + m i n i + min i+min的元素个数。

排序输出:创建一个与原数组长度相同的结果数组 o u t p u t output output,倒序遍历原数组,根据 c o u n t count count数组确定每个元素在 o u t p u t output output数组中的位置,并将元素放入,同时将 c o u n t count count数组中对应的值减 1。
0

1.2 代码实现(Python)

def counting_sort(arr):
    if not arr:
        return arr
    max_val = max(arr)
    min_val = min(arr)
    range_val = max_val - min_val + 1
    count = [0] * range_val
    for num in arr:
        count[num - min_val] += 1
    for i in range(1, range_val):
        count[i] += count[i - 1]
    output = [0] * len(arr)
    for num in reversed(arr):
        output[count[num - min_val] - 1] = num
        count[num - min_val] -= 1
    return output

上述 Python 代码首先计算出元素的取值范围,创建计数数组并统计每个元素的出现次数,然后进行累计计数,最后根据计数结果将元素放入结果数组中,完成排序。

1.3 性能分析

时间复杂度:计数排序的时间复杂度为 O ( n + k ) O(n + k) O(n+k),其中 n n n是待排序数组的长度, k k k是元素的取值范围。当 k k k n n n同阶或 k k k相对 n n n较小时,算法的时间复杂度接近 O ( n ) O(n) O(n),表现出高效性。

空间复杂度:计数排序需要额外的计数数组和结果数组,空间复杂度为 O ( n + k ) O(n + k) O(n+k)

稳定性:计数排序是稳定的排序算法,因为在将元素放入结果数组时,是按照原数组的顺序倒序处理的,相同元素的相对顺序不会改变。

1.4 适用场景

计数排序适用于元素取值范围有限且相对较小的场景,例如对学生成绩(分数范围通常固定)进行排序,或者对一定范围内的整数进行排序等。

二、基数排序(Radix Sort)

2.1 算法原理

基数排序也是一种非比较排序算法,它基于数字的每一位(如个位、十位、百位等)进行排序。基数排序从最低位开始,依次对每一位数字进行排序,直到最高位排序完成,最终得到有序数组。

具体步骤如下(以十进制整数为例):

确定位数:找出待排序数组中最大的数,确定其位数 d d d,例如最大数是 987 987 987,则 d = 3 d = 3 d=3

按位排序:从个位(第 0 位)开始,依次对每一位进行排序。对于每一位,使用计数排序或其他稳定排序算法,根据该位数字的大小将元素分配到不同的桶中,然后按照桶的顺序依次取出元素,形成新的数组。重复这个过程,直到最高位排序完成。
1

2.2 代码实现(Java)

import java.util.ArrayList;
import java.util.List;

public class RadixSort {
    public static int[] radixSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return arr;
        }
        int max = getMax(arr);
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSort(arr, exp);
        }
        return arr;
    }

    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }
        return max;
    }

    private static void countingSort(int[] arr, int exp) {
        int[] output = new int[arr.length];
        int[] count = new int[10];

        for (int num : arr) {
            count[(num / exp) % 10]++;
        }

        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[(arr[i] / exp) % 10] - 1] = arr[i];
            count[(arr[i] / exp) % 10]--;
        }

        System.arraycopy(output, 0, arr, 0, arr.length);
    }

    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
        int[] sortedArr = radixSort(arr);
        for (int num : sortedArr) {
            System.out.print(num + " ");
        }
    }
}

在上述 Java 代码中,radixSort方法首先找到数组中的最大值,确定排序的位数。然后通过循环,从个位开始,每次调用countingSort方法对当前位进行排序,最终完成整个数组的排序。

2.3 性能分析

时间复杂度:假设待排序数组的长度为 n n n,元素的最大位数为 d d d,每一位上元素的取值范围为 k k k(如十进制中 k = 10 k = 10 k=10)。基数排序需要对每一位进行排序,每次排序的时间复杂度为 O ( n + k ) O(n + k) O(n+k),总共进行 d d d次,所以时间复杂度为 O ( d ( n + k ) ) O(d(n + k)) O(d(n+k))。当 d d d k k k相对固定时,时间复杂度接近 O ( n ) O(n) O(n)

空间复杂度:基数排序在每一位排序时需要额外的计数数组和临时数组,空间复杂度为 O ( n + k ) O(n + k) O(n+k)

稳定性:由于在每一位排序时使用的是稳定排序算法(如计数排序),所以基数排序是稳定的排序算法。

2.4 适用场景

基数排序适用于对整数进行排序,特别是当整数的位数相对固定且取值范围不是特别大时,效果较好。例如对身份证号码、银行卡号等固定长度的数字序列进行排序。

三、桶排序(Bucket Sort)

3.1 算法原理

桶排序的基本思想是将待排序数组分到有限数量的桶子里,每个桶子再分别进行排序(可以使用其他排序算法,如插入排序),最后将各个桶中的元素依次取出,得到有序数组。

具体步骤如下:

确定桶的数量和范围:根据待排序数组的特点,确定桶的数量 m m m。同时,找出数组中的最大值 m a x max max和最小值 m i n min min,计算每个桶的范围 r a n g e = ( m a x − m i n + 1 ) / m range = (max - min + 1) / m range=(maxmin+1)/m

分配元素到桶中:遍历待排序数组,根据元素的值将其分配到对应的桶中。通常可以通过公式 i n d e x = ( n u m − m i n ) / r a n g e index = (num - min) / range index=(nummin)/range计算元素应该放入的桶的索引。

对每个桶进行排序:使用合适的排序算法(如插入排序)对每个桶中的元素进行排序。

合并桶中元素:按照桶的顺序,依次将每个桶中的元素取出,放入结果数组中,得到最终的有序数组。
2

3.2 代码实现(C++)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void bucketSort(vector<double>& arr) {
    int n = arr.size();
    if (n == 0) return;
    vector<vector<double>> buckets(10);
    double max_val = *max_element(arr.begin(), arr.end());
    double min_val = *min_element(arr.begin(), arr.end());
    double range = (max_val - min_val + 1) / 10;

    for (double num : arr) {
        int index = (num - min_val) / range;
        buckets[index].push_back(num);
    }

    for (auto& bucket : buckets) {
        sort(bucket.begin(), bucket.end());
    }

    arr.clear();
    for (const auto& bucket : buckets) {
        for (double num : bucket) {
            arr.push_back(num);
        }
    }
}

上述 C++ 代码中,首先确定桶的数量为 10,计算每个桶的范围,然后将元素分配到对应的桶中。接着使用std::sort函数对每个桶中的元素进行排序,最后将各个桶中的元素合并到原数组中,完成排序。

3.3 性能分析

时间复杂度:桶排序的时间复杂度与桶的数量、每个桶中元素的数量以及桶内使用的排序算法有关。如果桶的数量合理,且每个桶中的元素数量较少,桶内排序时间复杂度较低,整体时间复杂度可以接近 O ( n ) O(n) O(n)。但在最坏情况下,所有元素都分配到同一个桶中,此时时间复杂度退化为桶内排序算法的最坏时间复杂度(如使用插入排序时为 O ( n 2 ) O(n^2) O(n2))。

空间复杂度:桶排序需要额外的空间存储桶和桶内的元素,空间复杂度为 O ( n + m ) O(n + m) O(n+m),其中 m m m是桶的数量。

稳定性:如果桶内使用的是稳定排序算法,那么桶排序是稳定的排序算法。

3.4 适用场景

桶排序适用于数据分布较为均匀的情况,例如对大量的浮点数进行排序,或者对一定范围内的整数,且数据分布比较分散时,桶排序能够发挥较好的性能。

四、三种线性时间排序算法的对比与应用选择

排序算法时间复杂度空间复杂度稳定性适用场景
计数排序 O ( n + k ) O(n + k) O(n+k) O ( n + k ) O(n + k) O(n+k)稳定元素取值范围有限且较小
基数排序 O ( d ( n + k ) ) O(d(n + k)) O(d(n+k)) O ( n + k ) O(n + k) O(n+k)稳定对整数排序,位数固定且取值范围不大
桶排序理想 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2) O ( n + m ) O(n + m) O(n+m)取决于桶内算法数据分布均匀的场景

在实际应用中,应根据数据的特点(如数据类型、取值范围、分布情况等)来选择合适的线性时间排序算法。如果数据满足相应算法的适用条件,线性时间排序算法能够带来比比较排序算法更高的效率,在处理大规模数据时具有显著优势。

总结

计数排序、基数排序和桶排序作为线性时间排序算法的代表,通过独特的设计思路突破了比较排序的时间限制,在特定场景下展现出高效性。它们的原理、实现方式和适用场景各有不同,理解并掌握这些算法,能够帮助我们在面对不同的排序需求时,选择最合适的解决方案。随着数据规模的不断增大,线性时间排序算法的应用价值也将愈发凸显,值得我们深入学习和研究。

That’s all, thanks for reading!
创作不易,点赞鼓励;
知识无价,收藏备用;
持续精彩,关注不错过!

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

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

相关文章

Linux | mdadm 创建软 RAID

注&#xff1a;本文为 “Linux mdadm RAID” 相关文章合辑。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 Linux 下用 mdadm 创建软 RAID 以及避坑 喵ฅ・&#xfecc;・ฅ Oct 31, 2023 前言 linux 下组软 raid 用 mdadm 命令&#xff0c;multi…

CodeEdit:macOS上一款可以让Xcode退休的IDE

CodeEdit 是一款轻量级、原生构建的代码编辑器&#xff0c;完全免费且开源。它使用纯 swift 实现&#xff0c;而且专为 macOS 设计&#xff0c;旨在为开发者提供更高效、更可靠的编程环境&#xff0c;同时释放 Mac 的全部潜力。 Stars 数21,719Forks 数1,081 主要特点 macOS 原…

LLaMA-Factory 微调 Qwen2-7B-Instruct

一、系统环境 使用的 autoDL 算力平台 1、下载基座模型 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com # &#xff08;可选&#xff09;配置 hf 国内镜像站huggingface-cli download --resume-download shenzhi-wang/Llama3-8B-Chinese-Chat -…

mac本地docker镜像上传指定虚拟机

在Mac本地将Docker镜像上传至指定虚拟机的完整步骤 1. 在Mac本地保存Docker镜像为文件 通过docker save命令将镜像打包为.tar文件&#xff0c;便于传输至虚拟机。 # 示例&#xff1a;保存名为"my_image"的镜像到当前目录 docker save -o my_image.tar my_image:ta…

从代码学习深度学习 - 风格迁移 PyTorch版

文章目录 前言方法 (Methodology)阅读内容和风格图像预处理和后处理抽取图像特征定义损失函数内容损失 (Content Loss)风格损失 (Style Loss)全变分损失 (Total Variation Loss)总损失函数初始化合成图像训练模型总结前言 大家好!欢迎来到我们的深度学习代码学习系列。今天,…

软件设计师考试《综合知识》设计模式之——工厂模式与抽象工厂模式考点分析

软件设计师考试《综合知识》工厂模式与抽象工厂模式考点分析 1. 分值占比与考察趋势&#xff08;75分制&#xff09; 年份题量分值占总分比例核心考点2023111.33%抽象工厂模式适用场景2022222.67%工厂方法 vs 抽象工厂区别2021111.33%工厂方法模式结构2020111.33%简单工厂模式…

轻量级离线版二维码工具的技术分析与开发指南

摘要 本文介绍一款基于本地化运行的轻量级二维码处理工具。该工具采用标准QR Code规范实现&#xff0c;具备完整的生成与识别功能。通过实测验证其核心功能表现及适用场景。 主要功能模块分析 编码生成模块&#xff1a;支持文本/URL等多种数据类型转换&#xff1b;提供尺寸调…

机器学习--特征工程具体案例

一、数据集介绍 sklearn库中的玩具数据集&#xff0c;葡萄酒数据集。在前两次发布的内容《机器学习基础中》有介绍。 1.1葡萄酒列标签名&#xff1a; wine.feature_names 结果&#xff1a; [alcohol, malic_acid, ash, alcalinity_of_ash, magnesium, total_phenols, flavanoi…

Unreal 从入门到精通之SceneCaptureComponent2D实现UI层3D物体360°预览

文章目录 前言SceneCaptureComponent2D实现步骤新建渲染目标新建材质UI控件激活3DPreview鼠标拖动旋转模型最后前言 我们在(电商展示/角色预览/装备查看)等应用场景中,经常会看到这种3D展示的页面。 即使用相机捕获一个3D的模型的视图,然后把这个视图显示在一个UI画布上,…

电机控制杂谈(25)——为什么对于一般PMSM系统而言相电流五、七次谐波电流会比较大?

1. 背景 最近都在写论文回复信。有个审稿人问了一个问题——为什么对于一般PMSM系统而言相电流五、七次谐波电流会比较大&#xff1f;同时&#xff0c;为什么相电流五、七次谐波电流会在dq基波旋转坐标系构成六次谐波电流&#xff1f; 回答这个问题挺简单的&#xff0c;但在网…

多模态大语言模型arxiv论文略读(七十八)

AID: Adapting Image2Video Diffusion Models for Instruction-guided Video Prediction ➡️ 论文标题&#xff1a;AID: Adapting Image2Video Diffusion Models for Instruction-guided Video Prediction ➡️ 论文作者&#xff1a;Zhen Xing, Qi Dai, Zejia Weng, Zuxuan W…

【C语言】易错题 经典题型

出错原因&#xff1a;之前运行起来的可执行程序没有关闭 关闭即可 平均数&#xff08;average&#xff09; 输入3个整数&#xff0c;输出它们的平均值&#xff0c;保留3位小数。 #include <stdio.h> int main() {int a, b, c;scanf("%d %d %d", &a, &…

说一说Node.js高性能开发中的I/O操作

众所周知&#xff0c;在软件开发的领域中&#xff0c;输入输出&#xff08;I/O&#xff09;操作是程序与外部世界交互的重要环节&#xff0c;比如从文件读取数据、向网络发送请求等。这段时间&#xff0c;也指导项目中一些项目的开发工作&#xff0c;发现在Node.js运用中&#…

应用层协议简介:以 HTTP 和 MQTT 为例

文章目录 应用层协议简介&#xff1a;什么是应用层协议&#xff1f;为什么需要应用层协议&#xff1f;什么是应用层协议&#xff1f;为什么需要应用层协议&#xff1f; HTTP 协议详解HTTP 协议特点HTTP 工作的基本原理HTTP 请求与响应示例为什么 Web 应用基于 HTTP 请求&#x…

LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetCode 131.分割回文串

LeetCode 39. 组合总和 需要注意的是题目已经明确了数组内的元素不重复&#xff08;重复的话需要执行去重操作&#xff09;&#xff0c;且元素都为正整数&#xff08;如果存在0&#xff0c;则会出现死循环&#xff09;。 思路1&#xff1a;暴力解法 对最后结果进行去重 每一…

如何在 Windows 11 或 10 上安装 Fliqlo 时钟屏保

了解如何在 Windows 11 或 10 上安装 Fliqlo,为您的 PC 或笔记本电脑屏幕添加一个翻转时钟屏保以显示时间。 Fliqlo 是一款适用于 Windows 和 macOS 平台的免费时钟屏保。它也适用于移动设备,但仅限于 iPhone 和 iPad。Fliqlo 的主要功能是在用户不活动时在 PC 或笔记本电脑…

国芯思辰| 轮速传感器AH741对标TLE7471应用于汽车车轮速度感应

在汽车应用中&#xff0c;轮速传感器可用于车轮速度感应&#xff0c;为 ABS、ESC 等安全系统提供精确的轮速信息&#xff0c;帮助这些系统更好地发挥作用&#xff0c;在紧急制动或车辆出现不稳定状态时&#xff0c;及时调整车轮的制动力或动力分配。 国芯思辰两线制差分式轮速…

小程序弹出层/抽屉封装 (抖音小程序)

最近忙于开发抖音小程序&#xff0c;最想吐槽的就是&#xff0c;既没有适配的UI框架&#xff0c;百度上还找不到关于抖音小程序的案列&#xff0c;我真的很裂开啊&#xff0c;于是我通过大模型封装了一套代码 效果如下 介绍 可以看到 这个弹出层是支持关闭和标题显示的&#xf…

电子电路原理第十六章(负反馈)

1927年8月,年轻的工程师哈罗德布莱克(Harold Black)从纽约斯塔顿岛坐渡轮去上班。为了打发时间,他粗略写下了关于一个新想法的几个方程式。后来又经过反复修改, 布莱克提交了这个创意的专利申请。起初这个全新的创意被认为像“永动机”一样愚蠢可笑,专利申请也遭到拒绝。但…

命令拼接符

Linux多命令顺序执行符号需要记住5个 【&#xff5c;】【||】【 ;】 【&】 【&&】 &#xff0c;在命令执行里面&#xff0c;如果服务器疏忽大意没做限制&#xff0c;黑客通过高命令拼接符&#xff0c;可以输入很多非法的操作。 ailx10 网络安全优秀回答者 互联网…