数据结构与算法之美 | 排序(3)

这篇具有很好参考价值的文章主要介绍了数据结构与算法之美 | 排序(3)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

线性排序(Linear sort)

桶排序(Bucket sort)

基本思想

  • 将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。
  • 桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
  • 桶排序常常用在外部排序[^1]中。

我们有 10 GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10 GB 的数据都加载到内存中。这个时候该怎么办?

解决方法

  1. 我们可以先扫描一遍文件,看订单金额所处的数据范围。

    假设经过扫描之后我们得到,订单金额最小是 1 元,最大是 10 万元。

  2. 我们将所有订单根据金额划分到 100 个桶里。

    第一个桶我们存储金额在 1 元到 1000 元之内的订单,第二桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。

  3. 理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中。

    1)每个小文件中存储大约 100 MB 的订单数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。

    2)待所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。

  4. 非理想情况下,订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的 。

    有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。

  5. 针对这些划分之后还是比较大的文件,我们可以继续划分。

    比如,订单金额在 1 元到 1000 元之间的比较多,我们就将这个区间继续划分为 10 个小区间,1 元到 100 元,101 元到 200 元,201 元到 300 元…901 元到 1000 元。如果划分之后,101 元到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。

# bucket sort in Python

import math

def bucket_sort(order_data):
    '''
    使用桶排序算法对订单数据进行排序
    
    参数:
    	order_data(list):待排序列表
    
    返回值:
    	sorted_data(list): 已完成排序列表
    
    '''
    # 找到最大的订单金额
    max_amount = max(order_data)

    # 确定桶的数量
    num_buckets = math.ceil(max_amount / 100)

    # 创建空桶
    buckets = [[] for _ in range(num_buckets)]

    # 将订单数据放入对应的桶中
    for amount in order_data:
        # 划分为100个桶
        bucket_index = amount // 100
        buckets[bucket_index].append(amount)

    # 对每个桶中的数据进行排序
    for bucket in buckets:
        bucket.sort()

    # 合并所有桶的数据
    sorted_data = []
    for bucket in buckets:
        sorted_data.extend(bucket)

    return sorted_data

# 示例订单数据(假设为整数列表)
order_data = [102, 75, 158, 42, 95, 205, 300, 280, 150, 50, 180, 220, 500, 350]

# 使用桶排序进行排序
sorted_orders = bucket_sort(order_data)

# 打印排序结果
print(sorted_orders)

桶排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均情况时间复杂度为 O ( n ) O(n) O(n)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( n + k ) O(n+k) O(n+k)

  • 稳定性:是一个稳定的排序算法


计数排序(Count Sort)

基本思想

  • 计数排序是一种特殊的桶排序

  • 通过计算数组中每个唯一元素的出现次数来对数组的元素进行排序。

  • 计数存储在辅助数组中,排序是通过将计数映射为辅助数组的索引来完成的

高考查分数的时候,系统会显示我们的成绩以及所在省的排名。如果你所在的省有 50 万考生,如何通过成绩快速排序得出名次呢?

解决方法:

  1. 考生的满分是 900 分,最小是 0 分,这个数据的范围很小,所以我们可以分成 901 个桶,对应分数从 0 分到 900 分。
  2. 根据考生的成绩,我们将这 50 万考生划分到这 901 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。
  3. 我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是 O(n)。
# count sort in Python

def counting_sort(arr):
    """
    计数排序,arr是数组。
    假设数组中存储的都是非负整数
    
    参数:
    	arr (List[int]): 输入的数组

    返回值:
        None: 原地排序,直接修改输入的数组arr
    """

    n = len(arr)
    if n <= 1:
        return

    # 查找数组中数据的范围
    max_val = arr[0]
    for i in range(1, n):
        if max_val < arr[i]:
            max_val = arr[i]
            
	# 申请一个计数数组count,下标大小[0, max_val]
    count = [0] * (max_val + 1)  

    # 统计每个元素在输入数组中出现的次数
    for i in range(n):
        count[arr[i]] += 1

    # 依次累加
    for i in range(1, max_val + 1):
        # 帮助计算每个元素应该放置在排序结果数组中的正确索引位置
        # count[i] 保存的值表示了输入数组中小于等于 i 的元素的个数
        count[i] = count[i - 1] + count[i]

    # 临时数组result,存储排序之后的结果
    result = [0] * n

    # 从输入数组的最后一个元素开始向前遍历,即从右往左,从后往前遍历。
    for i in range(n - 1, -1, -1):
        index = count[arr[i]] - 1
        result[index] = arr[i]
        count[arr[i]] -= 1

    # 将结果拷贝给arr数组
    for i in range(n):
        arr[i] = result[i]

计数排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),平均情况时间复杂度为 O ( n + k ) O(n+k) O(n+k)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( N ) O(N) O(N)

  • 稳定性:是一个稳定的排序算法

注意:

  • 计数排序只能用在数据范围不大的场景中,如果数据范围 K 比要排序的数据 n 大很多,就不适合用计数排序了

  • 计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数

基数排序(Radix sort)

基本思想

  1. 首先对具有相同位值的各个数字进行分组来对元素进行排序。
  2. 然后,根据元素的递增/递减顺序对元素进行排序。
# Radix sort in Python

def countingSort(array, place):
    '''
	使用计数排序根据位数对元素进行排序。
	
	参数:
        array (List[int]): 输入的数组。
        place (int): 当前位数。

	返回值:
    	None
	'''
    size = len(array)
    output = [0] * size
    count = [0] * 10

    # # 统计每个数字出现的次数
    for i in range(0, size):
        index = array[i] // place
        count[index % 10] += 1

    # 计算数字累计出现的次数
    for i in range(1, 10):
        count[i] += count[i - 1]

    # 按照位数顺序将元素放置到输出数组中
    i = size - 1
    while i >= 0:
        index = array[i] // place
        output[count[index % 10] - 1] = array[i]
        count[index % 10] -= 1
        i -= 1
        
	# 将输出数组复制回原始数组
    for i in range(0, size):
        array[i] = output[i]



def radixSort(array):
    """
	实现基数排序的主函数。
	
	参数:
    	array (List[int]): 输入的数组。

	返回值:
    	None
	"""
   
    max_element = max(array)

    # 根据数字的位数进行计数排序
    place = 1
    while max_element // place > 0:
        countingSort(array, place)
        place *= 10


data = [121, 432, 564, 23, 1, 45, 788]
radixSort(data)
print(data)

基数排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),平均情况时间复杂度为 O ( n + k ) O(n+k) O(n+k)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( N ) O(N) O(N)

  • 稳定性:是一个稳定的排序算法

注意文章来源地址https://www.toymoban.com/news/detail-480346.html

  • 基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。
  • 除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 O(n) 了

参考文献

  • 排序(下):如何用快排思想在O(n)内查找第K大元素?极客时间. 2018
  • https://www.programiz.com/dsa/radix-sort

到了这里,关于数据结构与算法之美 | 排序(3)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构与算法之美学习笔记:42 | 动态规划实战:如何实现搜索引擎中的拼写纠错功能?

    本节课程思维导图: 利用 Trie 树,可以实现搜索引擎的提示功能,这样可以节省用户输入搜索的时间。实际上,搜索引擎在用户体验方面的优化还有很多,比如你可能经常会用的拼写纠错功能。 当你在搜索框中,一不小心输错单词时,搜索引擎会非常智能地检

    2024年02月03日
    浏览(57)
  • 数据结构与算法之美学习笔记:40 | 初识动态规划:如何巧妙解决“双十一”购物时的凑单问题?

    本节课程思维导图: 淘宝的“双十一”购物节有各种促销活动,比如“满 200 元减 50 元”。假设你女朋友的购物车中有 n 个(n100)想买的商品,她希望从里面选几个,在凑够满减条件的前提下,让选出来的商品价格总和最大程度地接近满减条件(200 元),这样就可以极大限

    2024年02月03日
    浏览(47)
  • 算法 数据结构分类 数据结构类型介绍 数据结构线性非线性结构 算法合集 (一)

     数据结构分为:                            a.线性结构                            b.非线性结构  a.线性结构:                       数据与结构存在一对一的线性关系; a . 线性结构 存储 分为:                                   顺序存储

    2024年02月10日
    浏览(49)
  • 数据结构与算法之美学习笔记:41 | 动态规划理论:一篇文章带你彻底搞懂最优子结构、无后效性和重复子问题

    本节课程思维导图: 今天,我主要讲动态规划的一些理论知识。学完这节内容,可以帮你解决这样几个问题:什么样的问题可以用动态规划解决?解决动态规划问题的一般思考过程是什么样的?贪心、分治、回溯、动态规划这四种算法思想又有什么区别和联系? 什么样的问

    2024年02月02日
    浏览(64)
  • 【Python数据结构与算法】线性结构小结

    🌈个人主页: Aileen_0v0 🔥系列专栏:PYTHON学习系列专栏 💫\\\"没有罗马,那就自己创造罗马~\\\"   目录 线性数据结构Linear DS 1.栈Stack 栈的两种实现 1.左为栈顶,时间复杂度为O(n) 2.右为栈顶,时间复杂度O(1)   2.队列Queue 3.双端队列Deque 4.列表List 5.链表 a.无序链表的实现 b.有序链表的实

    2024年02月04日
    浏览(40)
  • 数据结构与算法 - 线性表

    编程要求 本关任务是实现 step1/Seqlist.cpp 中的SL_InsAt、SL_DelAt和SL_DelValue三个操作函数,以实现线性表中数据的插入、删除与查找等功能。具体要求如下: SL_InsAT: 在顺序表的位置i插入结点x,即插入d[i]之前,i的有效范围[0,slist-len]; SL_DelAt:删除顺序表slist的第i号结点, i的有

    2024年02月01日
    浏览(81)
  • Rust 数据结构与算法:2线性数据结构 之 栈

    1、线性数据结构 数组、栈、队列、双端队列、链表这类数据结构都是保存数据的容器,数据项之间的顺序由添加或删除时的顺序决定,数据项一旦被添加,其相对于前后元素就会一直保持位置不变,诸如此类的数据结构被称为线性数据结构。线性数据结构有两端,称为“左

    2024年02月21日
    浏览(45)
  • 数据结构——排序算法——归并排序

    在第二个列表向第一个列表逐个插入的过程中,由于第二个列表已经有序,所以后续插入的元素一定不会在前面插入的元素之前。在逐个插入的过程中,每次插入时,只需要从上次插入的位置开始,继续向后寻找插入位置即可。这样一来,我们最多只需要将两个有序数组遍历

    2024年02月09日
    浏览(41)
  • 【排序算法】数据结构排序详解

    前言: 今天我们将讲解我们数据结构初阶的最后一部分知识的学习,也是最为“炸裂”的知识---------排序算法的讲解!!!! 排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,

    2023年04月08日
    浏览(49)
  • 【数据结构与算法_01_线性表】线性表

    定义 ● 线性表 具有相同数据类型**(同类型)**的n个数据元素有限序列 ● 三方面 ● 定义 逻辑结构 ● 相同数据类型 ● 每个数据元素所占的空间相同 ● 有限 ● 有限个元素 ● 序列 ● 是有次序的 ● 基本操作 操作— 基本操作 运算 ● 创建线性表【initList(L)】 ● 初始化线

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包