【数据结构】排序算法---基数排序

news2024/11/24 4:23:35

在这里插入图片描述

文章目录

  • 1. 定义
  • 2. 算法步骤
    • 2.1 MSD基数排序
    • 2.2 LSD基数排序
  • 3. LSD 基数排序动图演示
  • 4. 性质
  • 5. 算法分析
  • 6. 代码实现
    • C语言
    • Python
    • Java
    • C++
    • Go
  • 结语

⚠本节要介绍的不是计数排序

1. 定义

基数排序(英语:Radix sort)是一种非比较型的排序算法,最早用于解决卡片排序的问题。基数排序将待排序的元素拆分为k个关键字,逐一对各个关键字排序后完成对所有元素的排序。

  • 如果是从第1关键字到第k关键字顺序进行比较,则该基数排序称为 MSD(Most Significant Digit first)基数排序——最高位优先法(从高位到低位);

  • 如果是从第k关键字到第1关键字顺序进行比较,则该基数排序称为 LSD(Least Significant Digit first)基数排序——最低位优先法(从低位到高位)。

基数排序是将整数按位数切割成不同的数字,然后按每个位数分别比较,由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

2. 算法步骤

2.1 MSD基数排序

将待排序的元素拆分为k个关键字,先对第1关键字进行稳定排序,然后对于每组 具有相同关键字的元素 再对第2关键字进行稳定排序(递归执行)……最后对于每组 具有相同关键字的元素 再对第k关键字进行稳定排序。

在这里插入图片描述

一般而言,我们默认基数排序是稳定的,所以在 MSD 基数排序中,我们也仅仅考虑借助 稳定算法(通常使用计数排序)完成内层对关键字的排序。

2.2 LSD基数排序

将待排序的元素拆分为k个关键字,然后先对 所有元素 的第k关键字进行稳定排序,再对 所有元素 的第k-1关键字进行稳定排序,再对 所有元素 的第k-2关键字进行稳定排序……最后对 所有元素 的第1关键字进行稳定排序,这样就完成了对整个待排序序列的稳定排序。

在这里插入图片描述

LSD 基数排序也需要借助一种 稳定算法 完成内层对关键字的排序。同样的,通常使用计数排序来完成。

3. LSD 基数排序动图演示

在这里插入图片描述

4. 性质

稳定性

如果对内层关键字的排序是稳定的,则 MSD 基数排序和 LSD 基数排序都是稳定的排序算法。

空间复杂度

MSD 基数排序和 LSD 基数排序的空间复杂度都为 O ( k + n ) O(k+n) O(k+n)

时间复杂度

基数排序每一位的比较可以使用线性排序,比如桶排序或者计数排序,当然需要保证如计数排序的稳定性。每次排序时间复杂度O(n),那么如果有k位,则时间复杂度为 O ( k ∗ n ) O(k*n) O(kn),如果k不大比如手机号11位,那么时间复杂度就是 O ( n ) O(n) O(n)

通常而言,基数排序比基于比较的排序算法(比如快速排序)要快。但由于需要额外的内存空间,因此当内存空间稀缺时,原地置换算法(比如快速排序)或许是个更好的选择。

一般来说,如果每个关键字的值域都不大,就可以使用 [计数排序]作为内层排序,此时的复杂度为 O ( k n + ∑ i = 1 k w i ) O(kn+{\sum_{i=1}^k}wi) O(kn+i=1kwi),其中 w i wi wi为第i关键字的值域大小。如果关键字值域很大,就可以直接使用基于比较的 O ( n k l o g n ) O(nklogn) O(nklogn)排序而无需使用基数排序了。

5. 算法分析

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要 O ( n ) O(n) O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要 O ( n ) O(n) O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是 O ( d ∗ 2 n ) O(d*2n) O(d2n),当然d要远远小于n,因此基本上还是线性级别的。

基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说 n > > k n>>k n>>k,因此额外空间需要大概n个左右。

6. 代码实现

C语言

#include<stdio.h>
#define MAX 20
//#define SHOWPASS
#define BASE 10

void print(int *a, int n) {
  int i;
  for (i = 0; i < n; i++) {
    printf("%d\t", a[i]);
  }
}

void radixsort(int *a, int n) {
  int i, b[MAX], m = a[0], exp = 1;

  for (i = 1; i < n; i++) {
    if (a[i] > m) {
      m = a[i];
    }
  }

  while (m / exp > 0) {
    int bucket[BASE] = { 0 };

    for (i = 0; i < n; i++) {
      bucket[(a[i] / exp) % BASE]++;
    }

    for (i = 1; i < BASE; i++) {
      bucket[i] += bucket[i - 1];
    }

    for (i = n - 1; i >= 0; i--) {
      b[--bucket[(a[i] / exp) % BASE]] = a[i];
    }

    for (i = 0; i < n; i++) {
      a[i] = b[i];
    }

    exp *= BASE;

#ifdef SHOWPASS
    printf("\nPASS   : ");
    print(a, n);
#endif
  }
}

int main() {
  int arr[MAX];
  int i, n;

  printf("Enter total elements (n <= %d) : ", MAX);
  scanf("%d", &n);
  n = n < MAX ? n : MAX;

  printf("Enter %d Elements : ", n);
  for (i = 0; i < n; i++) {
    scanf("%d", &arr[i]);
  }

  printf("\nARRAY  : ");
  print(&arr[0], n);

  radixsort(&arr[0], n);

  printf("\nSORTED : ");
  print(&arr[0], n);
  printf("\n");

  return 0;
}

Python

# coding=utf-8
def radix_sort(array):
    max_num = max(array)
    place = 1
    while max_num >= 10**place:
        place += 1
    for i in range(place):
        buckets = [[] for _ in range(10)]
        for num in array:
            radix = int(num/(10**i) % 10)
            buckets[radix].append(num)
        j = 0
        for k in range(10):
            for num in buckets[k]:
                array[j] = num
                j += 1
    return array
 
 
if __name__ == '__main__':
    array = [25, 17, 33, 17, 22, 13, 32, 15, 9, 25, 27, 18]
    print(radix_sort(array))

Java

/**
 * 基数排序
 * 考虑负数的情况还可以参考: https://code.i-harness.com/zh-CN/q/e98fa9
 */
public class RadixSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    /**
     * 获取最高位数
     */
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    protected int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}

C++

int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
    int maxData = data[0];              ///< 最大数
    /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
    for (int i = 1; i < n; ++i)
    {
        if (maxData < data[i])
            maxData = data[i];
    }
    int d = 1;
    int p = 10;
    while (maxData >= p)
    {
        //p *= 10; // Maybe overflow
        maxData /= 10;
        ++d;
    }
    return d;
/*    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;*/
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int *tmp = new int[n];
    int *count = new int[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}

Go

var (
	K int = 3 // 基数排序需要的全局变量
	RADIX int = 10
	queue [][]int
)
func radix_sort_queue_pop(qu []int) []int {
    if len(qu) == 0 {
        return qu // 如果数组为空,不做任何操作
    }
    // 删除第一个元素
    qu = qu[1:]
    return qu
}
func radix_sort_queue_push(qu []int, data int) []int {
	qu = append(qu, data)
	return qu
}
func radix_sort_get_key(value int, k int) int {
	key := 0
	for k >= 0 {
		key = value % 10
		value /= 10
		k--
	}
	return key
}
func radix_sort_distribute(arr []int, left int, right int, k int){
	// k表示是第几次分发数据
	for i:=left; i<right; i++ {
		key := radix_sort_get_key(arr[i], k)
		queue[key] = radix_sort_queue_push(queue[key], arr[i])
	}
}
func radix_sort_collect(arr []int) {
	k := 0
	for i:=0; i < RADIX; i++ {
		for len(queue[i]) != 0 {
			arr[k] = queue[i][0] // 先进先出
			k++
			queue[i] = radix_sort_queue_pop(queue[i])
		}
	}
}
func radix_sort_by_group(arr []int, left int, right int) {
	for i:=0; i<K; i++ {
		// 分发数据
		radix_sort_distribute(arr, left, right, i)
		// 回收数据
		radix_sort_collect(arr)
	}
}
func radix_sort(arr []int, sz int) {
	// 初始化队列
	queue = make([][]int, RADIX)
	left := 0
	right := sz;
	radix_sort_by_group(arr, left, right)
}

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

带你初步了解排序算法:https://blog.csdn.net/2301_80191662/article/details/142211265
直接插入排序:https://blog.csdn.net/2301_80191662/article/details/142300973
希尔排序:https://blog.csdn.net/2301_80191662/article/details/142302553
直接选择排序:https://blog.csdn.net/2301_80191662/article/details/142312028
堆排序:https://blog.csdn.net/2301_80191662/article/details/142312338
冒泡排序:https://blog.csdn.net/2301_80191662/article/details/142324131
快速排序:https://blog.csdn.net/2301_80191662/article/details/142324307
归并排序:https://blog.csdn.net/2301_80191662/article/details/142350640
计数排序:https://blog.csdn.net/2301_80191662/article/details/142350741
桶排序:https://blog.csdn.net/2301_80191662/article/details/142375338
基数排序:https://blog.csdn.net/2301_80191662/article/details/142375592
十大经典排序算法总结与分析:https://blog.csdn.net/2301_80191662/article/details/142211564

在这里插入图片描述

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

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

相关文章

基于ExtendSim的 电子制造 仿真模型

说明&#xff1a; 此模型表示电路板制造设施。该过程有4个步骤&#xff1a; *焊料制备 *组件放置 *烤箱 *检查 详情&#xff1a; *烤箱的容量为10张卡&#xff0c;但如果烤箱循环开始时仅能处理5张卡&#xff0c;则最多只能处理5张。 *如果检查员发现问题&#xff0c;他们将修理…

C++——map和set的使用以及map系列

目录 map和set的使用 1. 序列式容器和关联式容器 2. set系列的使⽤ 2.1 set和multiset参考⽂档 2.2 set类的介绍 2.3 set的构造和迭代器 2.4 set的增删查 set的增删查关注以下⼏个接⼝即可&#xff1a; 2.6 find和erase使⽤样例&#xff1a; lower_bound(); upper_bo…

Css_动态渐变圆圈旋转效果

1、效果图 2、实现代码 <template><div class"box"><div class"line"></div><div class"lineNew"></div></div> </template><script lang"ts" setup></script><styl…

MySQL篇(存储引擎 - InnoDB存储引擎架构)(持续更新迭代)

目录 一、逻辑存储结构 1. 表空间 2. 段 3. 区 4. 页 5. 行 二、架构 1. 简介 2. 内存结构&#xff08;四部分&#xff09; Buffer Pool Change Buffer Adaptive Hash Index Log Buffer 3. 磁盘结构&#xff08;七部分&#xff09; System Tablespace File-Per-…

pdf文件怎么直接翻译?使用这些工具让翻译变得简单

在全球化日益加深的职场环境中&#xff0c;处理外语PDF文件成为了许多职场人士面临的共同挑战。 面对这些“加密”的信息宝库&#xff0c;如何高效、准确地将英文pdf翻译成对应语言&#xff0c;成为了提升工作效率的关键。 以下是几款在PDF翻译领域表现出色的软件&#xff0c…

化繁为简:中介者模式如何管理复杂对象交互

化繁为简&#xff1a;中介者模式如何管理复杂对象交互 中介者模式 是一种行为型设计模式&#xff0c;定义了一个中介者对象&#xff0c;来封装一组对象之间的交互。中介者模式通过将对象之间的交互行为从多个对象中抽离出来&#xff0c;集中封装在一个中介者对象中&#xff0c;…

智能除螨仪——NV040D-SOP8语音芯片方案引领除螨仪新时代

随着物联网技术的快速发展&#xff0c;除螨仪作为家庭清洁的重要工具&#xff0c;其智能化、人性化的设计成为提升市场竞争力的关键。置入语音芯片的除螨仪&#xff0c;通过开机提示、工作状态反馈、操作指引、故障提醒等内容。用户可以更加直观地了解除螨仪的工作状态&#xf…

开发谷歌插件之GA埋点

目录 一、背景 二、踩坑 三、谷歌插件开发的GA埋点的实现方式 一、背景 开发了一个谷歌插件&#xff0c;领导需要对用户的一些行为进行分析&#xff0c;于是让我在代码里面加上GA埋点。由于我们的PC端的项目一直都有进行GA埋点&#xff0c;当时就想着&#xff0c;这不就是把…

Spring Cloud Alibaba-(4)Sentinel【流控和降级】

Spring Cloud Alibaba-&#xff08;1&#xff09;搭建项目环境 Spring Cloud Alibaba-&#xff08;2&#xff09;Nacos【服务注册与发现、配置管理】 Spring Cloud Alibaba-&#xff08;3&#xff09;OpenFeign【服务调用】 Spring Cloud Alibaba-&#xff08;4&#xff09;Sen…

界面控件Telerik UI for WinForms 2024 Q3概览 - 支持合并单元格等

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 本文将介绍界面组件Telerik UI for WinForms在今年第一…

Java语言程序设计基础篇_编程练习题***18.32 (游戏:骑士的旅途)

目录 题目&#xff1a;***18.32 (游戏:骑士的旅途) 习题思路 代码示例 输出结果 题目&#xff1a;***18.32 (游戏:骑士的旅途) 骑士的旅途是一个古老的谜题&#xff0c;它的目的是使骑从棋盘上的任意一个正方 形开始移动&#xff0c;经过其他的每个正方形一次&#xff0c;如…

R18 5G网络中 AI/ML技术特性及其在5GS和NG-RAN中的应用

随着5G技术的发展&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;在网络中的应用越来越广泛。本文将介绍R18 5G网络中AI/ML的新特性&#xff0c;包括在5G系统&#xff08;5GS&#xff09;中的应用、在新一代无线接入网&#xff08;NG-RAN…

AD中PCB元器件常用的对齐用法

1.shift 选中要对齐的元器件 2.按右键&#xff0c;选择对齐&#xff0c;或者按A&#xff0c;弹出对齐对菜单&#xff1b;&#xff08;切记不要选择多余的元器件或者线条&#xff0c;要不然也会根据它的位置来做对齐&#xff0c;按shift一个一个元器件选择&#xff09; 常用如下…

基于SpringBoot+Vue的在线学习平台

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

出现conda不是内部或外部命令,也不是可运行的程序或批处理文件。的解决办法

发现是我的环境变量不对&#xff0c;需要改成conda.exe所在的目录下 如果不知道自己conda.exe在哪的 可以下载个everything这个软件 找东西很快 找到后 点击环境变量-系统变量-Path-新建-&#xff08;你的conda.exe所在目录&#xff1a;绝对路径&#xff09; 完成上述操作…

【English】语法的整体结构

目录 &#x1f4cc;词类——"英语的本质是词与词之间的修饰关系" 名词( Noun ) 形容词( Adjective ) 动词( Verb ) 副词( Adverbs ) 修饰关系——"理解词与词之间的修饰关系与规则" &#x1f4cc;句子结构 句子的基本成分 五种基本句型和三种句子类型 关于…

ROC和AUC也不是评估机器学习性能的金标准

对于不平衡数据集&#xff0c;AUC值是分类器效果评估的常用标准。但如果在解释时不仔细&#xff0c;它也会有一些误导。以Davis and Goadrich (2006)中的模型为例。如图所示&#xff0c;左侧展示的是两个模型的ROC曲线&#xff0c;右侧展示的是precision-recall曲线 (PRC)。 Pr…

前端univer创建、编辑excel

前端univer创建、编辑excel 源码在线demo&#xff1a;https://codesandbox.io/p/sandbox/univer-q87kqg?file/src/Demo.jsx univer官网地址&#xff1a;https://univer.ai/zh-CN/guides/sheet/introduction 安装univer npm install univerjs/core univerjs/design univerjs…

大模型爬虫—ScrapeGraphAI

大模型爬虫—ScrapeGraphAI 一、介绍 ScrapeGraphAI是一个网络爬虫 Python 库,使用大型语言模型和直接图逻辑为网站和本地文档(XML,HTML,JSON 等)创建爬取管道。 只需告诉库您想提取哪些信息,它将为您完成! scrapegraphai有三种主要的爬取管道可用于从网站(或本地文…

dockerfile 添加arthas 监控插件。容器添加arthas监控

1. arthas官网&#xff1a; 简介 | arthas 2. arthas下载地址&#xff1a; Releases alibaba/arthas GitHub 3. 下载版本&#xff1a; 4. 下载压缩包后&#xff0c;解压缩&#xff0c;放入Dockerfile 同级目录 5. dockerfile 命令&#xff1a; RUN mkdir -p /opt/arthas…